From 3ffe884b6be60ae1847d705c3afe8fffc2c0f09a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=A7=80=EB=AF=BC?= Date: Mon, 16 Dec 2024 15:07:03 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Feat:=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TokenBlacklist 엔티티 및 Repository 생성 - LogoutFilter를 통한 로그아웃 처리 구현 - SecurityConfig에 로그아웃 설정 추가 - JWTFilter에 블랙리스트 토큰 검증 로직 추가 Resolves: #38 --- .../.idea/modules/refrigerator.main.iml | 6 ++-- .../aggregate/user/TokenBlacklist.java | 16 +++++++++ .../refrigerator/config/SecurityConfig.java | 28 ++++++++++++--- .../controller/user/UserController.java | 5 ++- .../java/moja/refrigerator/jwt/JWTFilter.java | 11 +++++- .../moja/refrigerator/jwt/LogoutFilter.java | 34 +++++++++++++++++++ .../user/TokenBlacklistRepository.java | 8 +++++ .../service/user/UserService.java | 1 + .../service/user/UserServiceImpl.java | 8 +++-- 9 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 refrigerator/src/main/java/moja/refrigerator/aggregate/user/TokenBlacklist.java create mode 100644 refrigerator/src/main/java/moja/refrigerator/jwt/LogoutFilter.java create mode 100644 refrigerator/src/main/java/moja/refrigerator/repository/user/TokenBlacklistRepository.java diff --git a/refrigerator/.idea/modules/refrigerator.main.iml b/refrigerator/.idea/modules/refrigerator.main.iml index ac8e328..397c268 100644 --- a/refrigerator/.idea/modules/refrigerator.main.iml +++ b/refrigerator/.idea/modules/refrigerator.main.iml @@ -1,6 +1,9 @@ + + + @@ -17,5 +20,4 @@ - - + \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/aggregate/user/TokenBlacklist.java b/refrigerator/src/main/java/moja/refrigerator/aggregate/user/TokenBlacklist.java new file mode 100644 index 0000000..fc72650 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/aggregate/user/TokenBlacklist.java @@ -0,0 +1,16 @@ +package moja.refrigerator.aggregate.user; + +import jakarta.persistence.*; +import lombok.Data; + +@Entity +@Table(name = "tbl_token_blacklist") +@Data +public class TokenBlacklist { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long blacklistPk; + + @Column(nullable = false) + private String blacklistToken; +} diff --git a/refrigerator/src/main/java/moja/refrigerator/config/SecurityConfig.java b/refrigerator/src/main/java/moja/refrigerator/config/SecurityConfig.java index 462533f..44053ca 100644 --- a/refrigerator/src/main/java/moja/refrigerator/config/SecurityConfig.java +++ b/refrigerator/src/main/java/moja/refrigerator/config/SecurityConfig.java @@ -1,8 +1,11 @@ package moja.refrigerator.config; +import jakarta.servlet.http.HttpServletResponse; import moja.refrigerator.jwt.JWTFilter; import moja.refrigerator.jwt.JWTUtil; import moja.refrigerator.jwt.LoginFilter; +import moja.refrigerator.jwt.LogoutFilter; +import moja.refrigerator.repository.user.TokenBlacklistRepository; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -21,10 +24,14 @@ public class SecurityConfig { private final AuthenticationConfiguration authenticationConfiguration; // JWTUtil 주입 private final JWTUtil jwtUtil; + private final LogoutFilter logoutFilter; + private final TokenBlacklistRepository tokenBlacklistRepository; - public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil) { + public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil, LogoutFilter logoutFilter, TokenBlacklistRepository tokenBlacklistRepository) { this.authenticationConfiguration = authenticationConfiguration; this.jwtUtil = jwtUtil; + this.logoutFilter = logoutFilter; + this.tokenBlacklistRepository = tokenBlacklistRepository; } @Bean @@ -56,8 +63,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // URL 별 접근 권한 설정 http .authorizeHttpRequests((auth) -> auth - .requestMatchers("/**").permitAll() // 이 경로들은 모두 접근 가능 - .requestMatchers("/admin").hasRole("ADMIN") // admin 경로는 ADMIN 역할을 가진 사용자만 +// .requestMatchers("/**").permitAll() // 이 경로들은 모두 접근 가능 +// .requestMatchers("/admin").hasRole("ADMIN") // admin 경로는 ADMIN 역할을 가진 사용자만 .anyRequest().authenticated()); // 나머지는 인증된 사용자만 // 세션 관리 설정 @@ -66,11 +73,22 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .sessionCreationPolicy(SessionCreationPolicy.STATELESS)); // JWT 사용을 위한 세션리스 설정 // 로그인 필터 추가 - http - .addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class); http .addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class); + http + .addFilterAfter(new JWTFilter(jwtUtil, tokenBlacklistRepository), LoginFilter.class); + + http + .logout(logout -> logout + .logoutUrl("/logout") + .addLogoutHandler(logoutFilter) + .logoutSuccessHandler((request, response, authentication) -> { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_OK); + response.getWriter().write("로그아웃 되었습니다."); + })); + return http.build(); } } \ No newline at end of file diff --git a/refrigerator/src/main/java/moja/refrigerator/controller/user/UserController.java b/refrigerator/src/main/java/moja/refrigerator/controller/user/UserController.java index 7adacab..97692a3 100644 --- a/refrigerator/src/main/java/moja/refrigerator/controller/user/UserController.java +++ b/refrigerator/src/main/java/moja/refrigerator/controller/user/UserController.java @@ -1,7 +1,9 @@ package moja.refrigerator.controller.user; +import jakarta.servlet.http.HttpServletRequest; import moja.refrigerator.dto.user.request.UserCreateRequest; import moja.refrigerator.service.user.UserService; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -23,7 +25,8 @@ public String getMainPage() { // 회원 가입 처리 @PostMapping("/auth/join") - public void joinProcess(@RequestBody UserCreateRequest request) { + public ResponseEntity join(@RequestBody UserCreateRequest request) { userService.createUser(request); + return ResponseEntity.ok().body("회원가입이 완료되었습니다."); } } diff --git a/refrigerator/src/main/java/moja/refrigerator/jwt/JWTFilter.java b/refrigerator/src/main/java/moja/refrigerator/jwt/JWTFilter.java index e22cfe8..a0b4677 100644 --- a/refrigerator/src/main/java/moja/refrigerator/jwt/JWTFilter.java +++ b/refrigerator/src/main/java/moja/refrigerator/jwt/JWTFilter.java @@ -6,6 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import moja.refrigerator.aggregate.user.User; import moja.refrigerator.dto.user.CustomUserDetails; +import moja.refrigerator.repository.user.TokenBlacklistRepository; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -15,9 +16,11 @@ public class JWTFilter extends OncePerRequestFilter { private final JWTUtil jwtUtil; + private final TokenBlacklistRepository tokenBlacklistRepository; - public JWTFilter(JWTUtil jwtUtil) { + public JWTFilter(JWTUtil jwtUtil, TokenBlacklistRepository tokenBlacklistRepository) { this.jwtUtil = jwtUtil; + this.tokenBlacklistRepository = tokenBlacklistRepository; } @Override @@ -33,6 +36,12 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String token = authorization.split(" ")[1]; + // 블랙리스트 체크 + if (tokenBlacklistRepository.existsByBlacklistToken(token)) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + // 토큰 소멸 시간 검증 if (jwtUtil.isExpired(token)) { // 만료된 토큰이면 그냥 통과 diff --git a/refrigerator/src/main/java/moja/refrigerator/jwt/LogoutFilter.java b/refrigerator/src/main/java/moja/refrigerator/jwt/LogoutFilter.java new file mode 100644 index 0000000..971f73d --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/jwt/LogoutFilter.java @@ -0,0 +1,34 @@ +package moja.refrigerator.jwt; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import moja.refrigerator.aggregate.user.TokenBlacklist; +import moja.refrigerator.repository.user.TokenBlacklistRepository; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutHandler; +import org.springframework.stereotype.Component; + +@Component +public class LogoutFilter implements LogoutHandler { + private final TokenBlacklistRepository tokenBlacklistRepository; + + public LogoutFilter(TokenBlacklistRepository tokenBlacklistRepository) { + this.tokenBlacklistRepository = tokenBlacklistRepository; + } + + @Override + public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { + String authorization = request.getHeader("Authorization"); + + if (authorization == null || !authorization.startsWith("Bearer ")) { + throw new IllegalArgumentException("토큰이 유효하지 않습니다."); + } + + String token = authorization.split(" ")[1]; + + // 토큰을 블랙리스트에 추가 + TokenBlacklist blacklist = new TokenBlacklist(); + blacklist.setBlacklistToken(token); + tokenBlacklistRepository.save(blacklist); + } +} diff --git a/refrigerator/src/main/java/moja/refrigerator/repository/user/TokenBlacklistRepository.java b/refrigerator/src/main/java/moja/refrigerator/repository/user/TokenBlacklistRepository.java new file mode 100644 index 0000000..6d82bd7 --- /dev/null +++ b/refrigerator/src/main/java/moja/refrigerator/repository/user/TokenBlacklistRepository.java @@ -0,0 +1,8 @@ +package moja.refrigerator.repository.user; + +import moja.refrigerator.aggregate.user.TokenBlacklist; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface TokenBlacklistRepository extends JpaRepository { + boolean existsByBlacklistToken(String blacklistToken); +} diff --git a/refrigerator/src/main/java/moja/refrigerator/service/user/UserService.java b/refrigerator/src/main/java/moja/refrigerator/service/user/UserService.java index 80fa52c..a3cc1fa 100644 --- a/refrigerator/src/main/java/moja/refrigerator/service/user/UserService.java +++ b/refrigerator/src/main/java/moja/refrigerator/service/user/UserService.java @@ -1,5 +1,6 @@ package moja.refrigerator.service.user; +import jakarta.servlet.http.HttpServletRequest; import moja.refrigerator.dto.user.request.UserCreateRequest; public interface UserService { diff --git a/refrigerator/src/main/java/moja/refrigerator/service/user/UserServiceImpl.java b/refrigerator/src/main/java/moja/refrigerator/service/user/UserServiceImpl.java index 7cd888b..0bbb720 100644 --- a/refrigerator/src/main/java/moja/refrigerator/service/user/UserServiceImpl.java +++ b/refrigerator/src/main/java/moja/refrigerator/service/user/UserServiceImpl.java @@ -1,8 +1,11 @@ package moja.refrigerator.service.user; +import jakarta.servlet.http.HttpServletRequest; +import moja.refrigerator.aggregate.user.TokenBlacklist; import moja.refrigerator.aggregate.user.User; import moja.refrigerator.dto.user.request.UserCreateRequest; import moja.refrigerator.exception.user.DuplicateUserException; +import moja.refrigerator.repository.user.TokenBlacklistRepository; import moja.refrigerator.repository.user.UserRepository; import org.modelmapper.ModelMapper; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -17,11 +20,13 @@ public class UserServiceImpl implements UserService { private final UserRepository userRepository; private final BCryptPasswordEncoder passwordEncoder; private final ModelMapper modelMapper; + private final TokenBlacklistRepository tokenBlacklistRepository; - public UserServiceImpl(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder, ModelMapper modelMapper) { + public UserServiceImpl(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder, ModelMapper modelMapper, TokenBlacklistRepository tokenBlacklistRepository) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; this.modelMapper = modelMapper; + this.tokenBlacklistRepository = tokenBlacklistRepository; } @Override @@ -40,7 +45,6 @@ public void createUser(UserCreateRequest request) { userRepository.save(user); } - private void checkDuplicateUser(UserCreateRequest request) { List errors = new ArrayList<>(); if (userRepository.existsByUserId(request.getUserId())) { From 6baa83384c7ac7954ee2416fb82d018668ef5ae8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=A7=80=EB=AF=BC?= Date: Mon, 16 Dec 2024 15:15:00 +0900 Subject: [PATCH 2/2] =?UTF-8?q?Chore:=20URL=20=EC=A0=91=EA=B7=BC=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/moja/refrigerator/config/SecurityConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refrigerator/src/main/java/moja/refrigerator/config/SecurityConfig.java b/refrigerator/src/main/java/moja/refrigerator/config/SecurityConfig.java index 44053ca..f42e6c7 100644 --- a/refrigerator/src/main/java/moja/refrigerator/config/SecurityConfig.java +++ b/refrigerator/src/main/java/moja/refrigerator/config/SecurityConfig.java @@ -63,8 +63,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // URL 별 접근 권한 설정 http .authorizeHttpRequests((auth) -> auth -// .requestMatchers("/**").permitAll() // 이 경로들은 모두 접근 가능 -// .requestMatchers("/admin").hasRole("ADMIN") // admin 경로는 ADMIN 역할을 가진 사용자만 + .requestMatchers("/**").permitAll() // 이 경로들은 모두 접근 가능 + .requestMatchers("/admin").hasRole("ADMIN") // admin 경로는 ADMIN 역할을 가진 사용자만 .anyRequest().authenticated()); // 나머지는 인증된 사용자만 // 세션 관리 설정