From a5aa215b7099cb5f8e4ccdfeeeed85c875308bcd Mon Sep 17 00:00:00 2001 From: bjkim Date: Thu, 21 Aug 2025 19:45:07 +0900 Subject: [PATCH] [ADD] TODO: REFRESH TOKEN --- .../autoflow/controllers/AuthController.java | 66 +++++-------------- .../autoflow/security/WebSecurityConfig.java | 50 +++++++------- .../security/jwt/AuthTokenFilter.java | 22 +++++-- .../re/etri/autoflow/service/AuthService.java | 61 +++++++++++++++++ .../etri/autoflow/swagger/OpenAPIConfig.java | 1 + 5 files changed, 117 insertions(+), 83 deletions(-) create mode 100644 src/main/java/kr/re/etri/autoflow/service/AuthService.java diff --git a/src/main/java/kr/re/etri/autoflow/controllers/AuthController.java b/src/main/java/kr/re/etri/autoflow/controllers/AuthController.java index c58b44b..0703fef 100644 --- a/src/main/java/kr/re/etri/autoflow/controllers/AuthController.java +++ b/src/main/java/kr/re/etri/autoflow/controllers/AuthController.java @@ -7,14 +7,13 @@ import io.swagger.v3.oas.annotations.Parameter; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; +import kr.re.etri.autoflow.service.AuthService; +import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; @@ -43,11 +42,8 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/api/auth") +@RequiredArgsConstructor public class AuthController { - - @Autowired - AuthenticationManager authenticationManager; - @Autowired UserRepository userRepository; @@ -63,50 +59,18 @@ public class AuthController { @Autowired RefreshTokenService refreshTokenService; - @Operation(summary = "로그인", description = "사용자 인증 후 JWT 및 리프레시 토큰 쿠키를 반환합니다.") - @ApiResponses({ - @ApiResponse(responseCode = "200", description = "로그인 성공"), - @ApiResponse(responseCode = "401", description = "잘못된 사용자명 또는 비밀번호") - }) - @PostMapping("/signin") - public ResponseEntity authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { - Authentication authentication = authenticationManager.authenticate( - new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()) - ); - - SecurityContextHolder.getContext().setAuthentication(authentication); - UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); - - // 기존 refresh token 제거 - refreshTokenService.deleteByUserId(userDetails.getId()); - - // 새 JWT 및 RefreshToken 생성 - ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(userDetails); - RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId()); - ResponseCookie jwtRefreshCookie = jwtUtils.generateRefreshJwtCookie(refreshToken.getToken()); - - List roles = userDetails.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .toList(); - - UserInfoResponse userInfo = new UserInfoResponse( - userDetails.getId(), - userDetails.getUsername(), - userDetails.getEmail(), - userDetails.getAuthorities().stream() - .map(GrantedAuthority::getAuthority) - .collect(Collectors.toList()) - ); - - // 응답 바디에 userInfo와 쿠키 문자열 같이 넣기 - Map responseBody = new HashMap<>(); - responseBody.put("userInfo", userInfo); - responseBody.put("jwtCookie", jwtCookie.toString()); - responseBody.put("jwtRefreshCookie", jwtRefreshCookie.toString()); - - return ResponseEntity.ok(responseBody); - } - + private final AuthService authService; + + @Operation(summary = "로그인", description = "사용자 인증 후 JWT 및 리프레시 토큰 쿠키를 반환합니다.") + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "로그인 성공"), + @ApiResponse(responseCode = "401", description = "잘못된 사용자명 또는 비밀번호") + }) + @PostMapping("/signin") + public ResponseEntity signIn(@Valid @RequestBody LoginRequest request) { + Map response = authService.authenticate(request); + return ResponseEntity.ok(response); + } @Operation(summary = "회원가입", description = "새로운 사용자를 등록합니다.") @ApiResponses({ diff --git a/src/main/java/kr/re/etri/autoflow/security/WebSecurityConfig.java b/src/main/java/kr/re/etri/autoflow/security/WebSecurityConfig.java index 41acf36..3710bab 100644 --- a/src/main/java/kr/re/etri/autoflow/security/WebSecurityConfig.java +++ b/src/main/java/kr/re/etri/autoflow/security/WebSecurityConfig.java @@ -83,38 +83,38 @@ public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter { // http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); // } -// @Bean -// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { -// http.csrf(AbstractHttpConfigurer::disable) -// .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) -// .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) -// .authorizeHttpRequests(auth -> -// auth.requestMatchers("/api/auth/**").permitAll() -// .requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll() -// .requestMatchers("/api/test/**").permitAll() -// .anyRequest().authenticated() -// ); -// -// http.authenticationProvider(authenticationProvider()); -// -// http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); -// -// return http.build(); -// } - - // 임시 설정 @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(AbstractHttpConfigurer::disable) - .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) - .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> - auth.anyRequest().permitAll() // 모든 요청 허용 - ); + .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> + auth.requestMatchers("/api/auth/**").permitAll() + .requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll() + .requestMatchers("/api/test/**").permitAll() + .anyRequest().authenticated() + ); http.authenticationProvider(authenticationProvider()); + http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } + + // 임시 설정 +// @Bean +// public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { +// http.csrf(AbstractHttpConfigurer::disable) +// .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) +// .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) +// .authorizeHttpRequests(auth -> +// auth.anyRequest().permitAll() // 모든 요청 허용 +// ); +// +// http.authenticationProvider(authenticationProvider()); +// http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); +// +// return http.build(); +// } } diff --git a/src/main/java/kr/re/etri/autoflow/security/jwt/AuthTokenFilter.java b/src/main/java/kr/re/etri/autoflow/security/jwt/AuthTokenFilter.java index ad764f7..5a2f6cf 100644 --- a/src/main/java/kr/re/etri/autoflow/security/jwt/AuthTokenFilter.java +++ b/src/main/java/kr/re/etri/autoflow/security/jwt/AuthTokenFilter.java @@ -35,12 +35,12 @@ public class AuthTokenFilter extends OncePerRequestFilter { String username = jwtUtils.getUserNameFromJwtToken(jwt); UserDetails userDetails = userDetailsService.loadUserByUsername(username); - - UsernamePasswordAuthenticationToken authentication = + + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); - + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authentication); @@ -52,8 +52,16 @@ public class AuthTokenFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); } - private String parseJwt(HttpServletRequest request) { - String jwt = jwtUtils.getJwtFromCookies(request); - return jwt; - } + private String parseJwt(HttpServletRequest request) { + // 쿠키에서 JWT 가져오기 + String jwt = jwtUtils.getJwtFromCookies(request); + if (jwt != null) { + return jwt; + } + + // 헤더에서 JWT 가져오기 + jwt = request.getHeader("cuuva-jwt"); + return (jwt != null && !jwt.isEmpty()) ? jwt : null; + } + } diff --git a/src/main/java/kr/re/etri/autoflow/service/AuthService.java b/src/main/java/kr/re/etri/autoflow/service/AuthService.java new file mode 100644 index 0000000..1398822 --- /dev/null +++ b/src/main/java/kr/re/etri/autoflow/service/AuthService.java @@ -0,0 +1,61 @@ +package kr.re.etri.autoflow.service; + +import kr.re.etri.autoflow.models.RefreshToken; +import kr.re.etri.autoflow.payload.request.LoginRequest; +import kr.re.etri.autoflow.payload.response.UserInfoResponse; +import kr.re.etri.autoflow.security.jwt.JwtUtils; +import kr.re.etri.autoflow.security.services.RefreshTokenService; +import kr.re.etri.autoflow.security.services.UserDetailsImpl; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseCookie; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.Map; + +@Service +@RequiredArgsConstructor +public class AuthService { + + private final AuthenticationManager authenticationManager; + private final JwtUtils jwtUtils; + private final RefreshTokenService refreshTokenService; + + public Map authenticate(LoginRequest request) { + Authentication authentication = authenticationManager.authenticate( + new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()) + ); + + SecurityContextHolder.getContext().setAuthentication(authentication); + UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); + + // 기존 refresh token 제거 + refreshTokenService.deleteByUserId(userDetails.getId()); + + // 새 JWT 및 RefreshToken 생성 + ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(userDetails); + RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId()); + ResponseCookie jwtRefreshCookie = jwtUtils.generateRefreshJwtCookie(refreshToken.getToken()); + + UserInfoResponse userInfo = new UserInfoResponse( + userDetails.getId(), + userDetails.getUsername(), + userDetails.getEmail(), + userDetails.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .toList() + ); + + Map response = new HashMap<>(); + response.put("userInfo", userInfo); + response.put("jwtCookie", jwtCookie.toString()); + response.put("jwtRefreshCookie", jwtRefreshCookie.toString()); + + return response; + } +} diff --git a/src/main/java/kr/re/etri/autoflow/swagger/OpenAPIConfig.java b/src/main/java/kr/re/etri/autoflow/swagger/OpenAPIConfig.java index 53bf835..f33dbac 100644 --- a/src/main/java/kr/re/etri/autoflow/swagger/OpenAPIConfig.java +++ b/src/main/java/kr/re/etri/autoflow/swagger/OpenAPIConfig.java @@ -40,6 +40,7 @@ public class OpenAPIConfig { .addSecuritySchemes(SECURITY_SCHEME_ACCESS, new SecurityScheme() .name("cuuva-jwt") // 액세스 토큰 쿠키 이름 + .bearerFormat("JWT") .type(SecurityScheme.Type.APIKEY) .in(SecurityScheme.In.HEADER) )