From 370772047b1f47e64ffe3b0e34150ff4c59fcb34 Mon Sep 17 00:00:00 2001 From: JjungminLee Date: Mon, 13 Mar 2023 18:16:20 +0900 Subject: [PATCH] =?UTF-8?q?feat:refreshToken=EC=9C=BC=EB=A1=9C=20accessTok?= =?UTF-8?q?en=EB=B0=9C=EA=B8=89,=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../monggeul/domain/user/UserController.java | 33 ++++++++ .../domain/user/dto/PostLogoutRes.java | 12 +++ .../domain/user/service/UserService.java | 75 +++++++++++++------ .../global/config/error/ErrorCode.java | 11 +-- .../global/config/redis/RedisDao.java | 40 ++++++++++ .../config/security/SecurityConfig.java | 10 ++- .../security/jwt/JwtAuthenticationFilter.java | 34 +++++---- .../config/security/jwt/JwtTokenProvider.java | 71 ++++++++++++++++++ 8 files changed, 242 insertions(+), 44 deletions(-) create mode 100644 src/main/java/com/cmc/monggeul/domain/user/dto/PostLogoutRes.java create mode 100644 src/main/java/com/cmc/monggeul/global/config/redis/RedisDao.java diff --git a/src/main/java/com/cmc/monggeul/domain/user/UserController.java b/src/main/java/com/cmc/monggeul/domain/user/UserController.java index a21aad0..46e4fc3 100644 --- a/src/main/java/com/cmc/monggeul/domain/user/UserController.java +++ b/src/main/java/com/cmc/monggeul/domain/user/UserController.java @@ -12,14 +12,17 @@ import com.cmc.monggeul.global.config.oauth.google.GoogleOAuthService; import com.cmc.monggeul.global.config.oauth.google.GoogleOAuthToken; import com.cmc.monggeul.global.config.oauth.kakao.KakaoService; +import com.cmc.monggeul.global.config.redis.RedisDao; import com.cmc.monggeul.global.config.security.jwt.JwtAuthenticationFilter; import com.cmc.monggeul.global.config.security.jwt.JwtTokenProvider; import com.cmc.monggeul.global.config.security.jwt.TokenDto; +import com.fasterxml.jackson.core.JsonProcessingException; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -45,6 +48,8 @@ public class UserController { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final JwtTokenProvider jwtTokenProvider; + private final RedisDao redisDao; + // == 백엔드 카카오 로그인 테스트 == @GetMapping("/test/kakao/code") @@ -262,6 +267,34 @@ public ResponseEntity> getUserMyPage(HttpServletR } + // 리프레쉬토큰 발급 + @ApiOperation( + value = "[로그인] refreshToken을 통한 accessToken 재발급 ") + @GetMapping("/reissue") + public ResponseEntity> reissue(HttpServletRequest request) throws JsonProcessingException { + + String jwtToken=jwtAuthenticationFilter.getJwtFromRequest(request); + String userEmail=jwtTokenProvider.getUserEmailFromJWT(jwtToken); + PostLoginRes postLoginRes=userService.reissue(userEmail); + System.out.println(postLoginRes.getAccessToken()); + return ResponseEntity.ok(new BaseResponse<>(postLoginRes)); + + } + + // 로그아웃 + @ApiOperation( + value = "로그아웃") + @PostMapping("/logout") + public ResponseEntity> logout(HttpServletRequest request){ + String jwtToken=jwtAuthenticationFilter.getJwtFromRequest(request); + String userEmail=jwtTokenProvider.getUserEmailFromJWT(jwtToken); + + PostLogoutRes postLogoutRes=userService.logout(userEmail,jwtToken); + + + return ResponseEntity.ok(new BaseResponse<>(postLogoutRes)); + } + diff --git a/src/main/java/com/cmc/monggeul/domain/user/dto/PostLogoutRes.java b/src/main/java/com/cmc/monggeul/domain/user/dto/PostLogoutRes.java new file mode 100644 index 0000000..23447d6 --- /dev/null +++ b/src/main/java/com/cmc/monggeul/domain/user/dto/PostLogoutRes.java @@ -0,0 +1,12 @@ +package com.cmc.monggeul.domain.user.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class PostLogoutRes { + + private String userEmail; + +} diff --git a/src/main/java/com/cmc/monggeul/domain/user/service/UserService.java b/src/main/java/com/cmc/monggeul/domain/user/service/UserService.java index 3914582..d81f65a 100644 --- a/src/main/java/com/cmc/monggeul/domain/user/service/UserService.java +++ b/src/main/java/com/cmc/monggeul/domain/user/service/UserService.java @@ -15,12 +15,14 @@ import com.cmc.monggeul.global.config.error.exception.BaseException; import com.cmc.monggeul.global.config.oauth.google.GoogleOAuth; import com.cmc.monggeul.global.config.oauth.kakao.KakaoService; +import com.cmc.monggeul.global.config.redis.RedisDao; import com.cmc.monggeul.global.config.security.SecurityConfig; import com.cmc.monggeul.global.config.security.jwt.JwtTokenProvider; import com.cmc.monggeul.global.config.security.jwt.TokenDto; import com.cmc.monggeul.global.config.security.userDetails.CustomUserDetailService; import com.cmc.monggeul.global.util.AES256; import com.cmc.monggeul.global.util.GenerateCertNumber; +import com.fasterxml.jackson.core.JsonProcessingException; import io.lettuce.core.models.role.RedisInstance; import lombok.AllArgsConstructor; import lombok.NoArgsConstructor; @@ -33,6 +35,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; import javax.transaction.Transactional; import java.util.Date; @@ -46,6 +49,7 @@ @RequiredArgsConstructor public class UserService { + private final RedisDao redisDao; private final UserRepository userRepository; private final RoleRepository roleRepository; private final FamilyRepository familyRepository; @@ -67,6 +71,8 @@ public class UserService { + + public PostLoginRes kakaoLogin(PostKakaoLoginReq postKakaoLoginReq, KakaoUserDto kakaoUserDto) throws BaseException { // 신규 가입 유저일경우 @@ -111,10 +117,7 @@ public PostLoginRes kakaoLogin(PostKakaoLoginReq postKakaoLoginReq, KakaoUserDto // 4. RefreshToken Redis 저장 (expirationTime 설정을 통해 자동 삭제 처리) // refresh token을 복호화한 후 만료기한을 redis에 저장할 것 DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken()); - System.out.println(decodedJWT.getToken().toString()); - - redisTemplate.opsForValue() - .set("RT:" + authentication.getName(),tokenDto.getRefreshToken(), Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())), TimeUnit.MILLISECONDS); + redisDao.setValues(authentication,tokenDto.getRefreshToken(),Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime()))); return PostLoginRes.builder() .grantType(tokenDto.getGrantType()) @@ -168,12 +171,10 @@ public PostLoginRes googleLogin(PostGoogleLoginReq postGoogleLoginReq, GoogleUse // redis에 refresh token 저장 // 4. RefreshToken Redis 저장 (expirationTime 설정을 통해 자동 삭제 처리) // refresh token을 복호화한 후 만료기한을 redis에 저장할 것 - DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken()); - System.out.println(decodedJWT.getToken().toString()); - redisTemplate.opsForValue() - .set("RT:" + authentication.getName(),tokenDto.getRefreshToken(), Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())), TimeUnit.MILLISECONDS); + DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken()); + redisDao.setValues(authentication,tokenDto.getRefreshToken(),Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime()))); return PostLoginRes.builder() .grantType(tokenDto.getGrantType()) .accessToken(tokenDto.getAccessToken()) @@ -206,9 +207,6 @@ public PostLoginRes appleLogin(PostAppleLoginReq postAppleLoginReq) throws BaseE // 유저 role 저장 Role role=roleRepository.findByRoleCode(postAppleLoginReq.getRole()); - - - User userSave=User.builder() .name(postAppleLoginReq.getUserName()) .email(postAppleLoginReq.getAppleEmail()) @@ -225,21 +223,17 @@ public PostLoginRes appleLogin(PostAppleLoginReq postAppleLoginReq) throws BaseE throw new BaseException(ErrorCode.EMAIL_ALREADY_EXIST); } - - Authentication authentication=new UsernamePasswordAuthenticationToken(postAppleLoginReq.getAppleEmail(),null,null); TokenDto tokenDto= JwtTokenProvider.generateToken(authentication); Optional user=userRepository.findByEmail(postAppleLoginReq.getAppleEmail()); + // redis에 refresh token 저장 // 4. RefreshToken Redis 저장 (expirationTime 설정을 통해 자동 삭제 처리) // refresh token을 복호화한 후 만료기한을 redis에 저장할 것 - DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken()); - System.out.println(decodedJWT.getToken().toString()); - - redisTemplate.opsForValue() - .set("RT:" + authentication.getName(),tokenDto.getRefreshToken(), Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())), TimeUnit.MILLISECONDS); + DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken()); + redisDao.setValues(authentication,tokenDto.getRefreshToken(),Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime()))); return PostLoginRes.builder() .grantType(tokenDto.getGrantType()) .accessToken(tokenDto.getAccessToken()) @@ -539,10 +533,7 @@ public PostLoginRes login(PostLoginReq postLoginReq) throws ParseException { Authentication authentication=new UsernamePasswordAuthenticationToken(kakaoUserDto.getEmail(),null,null); TokenDto tokenDto= JwtTokenProvider.generateToken(authentication); DecodedJWT decodedJWT= JWT.decode(tokenDto.getRefreshToken()); - System.out.println(decodedJWT.getToken().toString()); - - redisTemplate.opsForValue() - .set("RT:" + authentication.getName(),tokenDto.getRefreshToken(), Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())), TimeUnit.MILLISECONDS); + redisDao.setValues(authentication,tokenDto.getRefreshToken(),Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime()))); postLoginRes= PostLoginRes.builder() .grantType(tokenDto.getGrantType()) @@ -602,6 +593,46 @@ public PostLoginRes login(PostLoginReq postLoginReq) throws ParseException { return postLoginRes; } + //리프레쉬토큰 발급 + + public PostLoginRes reissue(String userEmail) throws JsonProcessingException { + String rtkInRedis =redisDao.getValues(userEmail); + if (Objects.isNull(rtkInRedis)) throw new BaseException(ErrorCode.EXPIRED_AUTHENTICATION); + System.out.println("redis:"+rtkInRedis); + String rtkEmail=jwtTokenProvider.getUserEmailFromJWT(rtkInRedis); + TokenDto tokenDto=jwtTokenProvider.reissueAtk(userEmail,rtkEmail); + Optional user=userRepository.findByEmail(userEmail); + return PostLoginRes.builder() + .grantType(tokenDto.getGrantType()) + .accessToken(tokenDto.getAccessToken()) + .refreshToken(tokenDto.getRefreshToken()) + .profileImg(user.orElseThrow().getProfileImgUrl()) + .code(user.orElseThrow().getMatchingCode()) + .build(); + + + } + + public PostLogoutRes logout(String userEmail,String jwtToken){ + Optional user=userRepository.findByEmail(userEmail); + // 3. Redis 에서 해당 User email 로 저장된 Refresh Token 이 있는지 여부를 확인 후 있을 경우 삭제합니다. + if (redisDao.getValues(userEmail) != null) { + // Refresh Token 삭제 + redisDao.deleteValues(userEmail); + } + + // 4. 해당 Access Token 유효시간 가지고 와서 BlackList 로 저장하기 + Long expiration = jwtTokenProvider.getExpiration(jwtToken); + + redisTemplate.opsForValue() + .set(jwtToken, "logout", expiration, TimeUnit.MILLISECONDS); + + return PostLogoutRes.builder() + .userEmail(user.orElseThrow(()->new BaseException(ErrorCode.USER_NOT_EXIST)).getEmail()) + .build(); + + } + diff --git a/src/main/java/com/cmc/monggeul/global/config/error/ErrorCode.java b/src/main/java/com/cmc/monggeul/global/config/error/ErrorCode.java index f67abae..d4fa45e 100644 --- a/src/main/java/com/cmc/monggeul/global/config/error/ErrorCode.java +++ b/src/main/java/com/cmc/monggeul/global/config/error/ErrorCode.java @@ -19,15 +19,16 @@ public enum ErrorCode { // USER - TOKEN_NOT_EXIST(HttpStatus.UNAUTHORIZED, 403, "JWT Token이 존재하지 않습니다."), - INVALID_TOKEN(HttpStatus.UNAUTHORIZED, 403,"유효하지 않은 JWT Token 입니다."), - ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, 403,"만료된 Access Token 입니다."), - REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, 403,"만료된 Refresh Token 입니다."), + TOKEN_NOT_EXIST(HttpStatus.UNAUTHORIZED, 401, "JWT Token이 존재하지 않습니다."), + INVALID_TOKEN(HttpStatus.UNAUTHORIZED, 401,"유효하지 않은 JWT Token 입니다."), + ACCESS_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, 401,"만료된 Access Token 입니다."), + REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, 401,"만료된 Refresh Token 입니다."), FAIL_AUTHENTICATION(HttpStatus.UNAUTHORIZED, 403,"사용자 인증에 실패하였습니다."), + EXPIRED_AUTHENTICATION(HttpStatus.UNAUTHORIZED,403,"인증정보가 만료되었습니다."), EMAIL_ALREADY_EXIST(HttpStatus.BAD_REQUEST, 500,"이미 존재하는 이메일입니다."), - INVALID_KAKAO_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED,403,"유효하지 않은 Kakao Access Token입니다."), + INVALID_KAKAO_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED,401,"유효하지 않은 Kakao Access Token입니다."), UNAUTHORIZED(HttpStatus.UNAUTHORIZED,403,"인가되지 않은 사용자입니다."), USER_NOT_EXIST(HttpStatus.NOT_FOUND,404,"존재하지 않는 유저입니다."), diff --git a/src/main/java/com/cmc/monggeul/global/config/redis/RedisDao.java b/src/main/java/com/cmc/monggeul/global/config/redis/RedisDao.java new file mode 100644 index 0000000..49052d5 --- /dev/null +++ b/src/main/java/com/cmc/monggeul/global/config/redis/RedisDao.java @@ -0,0 +1,40 @@ +package com.cmc.monggeul.global.config.redis; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + + +@Component +@RequiredArgsConstructor +public class RedisDao { + + private final RedisTemplate redisTemplate; + + public void setValues(String key, String data) { + ValueOperations values = redisTemplate.opsForValue(); + values.set(key, data); + } + // redisTemplate.opsForValue() +// .set("RT:" + authentication.getName(),tokenDto.getRefreshToken(), Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime())), TimeUnit.MILLISECONDS); + + public void setValues(Authentication authentication, String refreshToken,Long time) { + ValueOperations values = redisTemplate.opsForValue(); + values.set(authentication.getName(),refreshToken,time , TimeUnit.MILLISECONDS); + } + + public String getValues(String key) { + ValueOperations values = redisTemplate.opsForValue(); + return (String) values.get(key); + } + + public void deleteValues(String key) { + redisTemplate.delete(key); + } +} \ No newline at end of file diff --git a/src/main/java/com/cmc/monggeul/global/config/security/SecurityConfig.java b/src/main/java/com/cmc/monggeul/global/config/security/SecurityConfig.java index b45c209..c8b56ba 100644 --- a/src/main/java/com/cmc/monggeul/global/config/security/SecurityConfig.java +++ b/src/main/java/com/cmc/monggeul/global/config/security/SecurityConfig.java @@ -1,5 +1,6 @@ package com.cmc.monggeul.global.config.security; +import com.cmc.monggeul.global.config.redis.RedisDao; import com.cmc.monggeul.global.config.security.jwt.JwtAuthenticationEntryPoint; import com.cmc.monggeul.global.config.security.jwt.JwtAuthenticationFilter; import com.cmc.monggeul.global.config.security.jwt.JwtTokenProvider; @@ -19,7 +20,9 @@ @AllArgsConstructor public class SecurityConfig { - private JwtTokenProvider jwtTokenProvider; + private final JwtTokenProvider jwtTokenProvider; + + private final RedisDao redisDao; private final JwtAuthenticationFilter jwtAuthenticationFilter; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @@ -32,11 +35,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //jwt 사용 .and() - .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and() + .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) + .and() .authorizeRequests() .anyRequest().permitAll() .and() - .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider,redisDao), UsernamePasswordAuthenticationFilter.class); diff --git a/src/main/java/com/cmc/monggeul/global/config/security/jwt/JwtAuthenticationFilter.java b/src/main/java/com/cmc/monggeul/global/config/security/jwt/JwtAuthenticationFilter.java index f1b2208..dd8c3d4 100644 --- a/src/main/java/com/cmc/monggeul/global/config/security/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/cmc/monggeul/global/config/security/jwt/JwtAuthenticationFilter.java @@ -2,27 +2,23 @@ import com.cmc.monggeul.global.config.error.BaseResponse; import com.cmc.monggeul.global.config.error.ErrorCode; -import com.cmc.monggeul.global.config.error.exception.BaseException; -import com.cmc.monggeul.global.config.error.exception.JwtException; +import com.cmc.monggeul.global.config.redis.RedisDao; import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; import lombok.RequiredArgsConstructor; -import org.apache.catalina.connector.Response; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Service; import org.springframework.util.AntPathMatcher; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import java.io.IOException; -import java.util.jar.JarException; import lombok.extern.slf4j.Slf4j; @@ -45,8 +41,8 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { - public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) { - } + private final JwtTokenProvider jwtTokenProvider; + private final RedisDao redisDao; @Override protected boolean shouldNotFilter(HttpServletRequest request) { //이 필터 안걸치는 path @@ -70,7 +66,8 @@ protected boolean shouldNotFilter(HttpServletRequest request) { //이 필터 안 pathMatcher.match("/swagger-ui/**", path)&&request.getMethod().equals("GET") || pathMatcher.match("/favicon.ico", path) || pathMatcher.match("/swagger-resources/**", path))|| - pathMatcher.match("/v3/api-docs", path)&&request.getMethod().equals("GET") ; + pathMatcher.match("/v3/api-docs", path)&&request.getMethod().equals("GET")|| + pathMatcher.match("/user/logout", path)&&request.getMethod().equals("POST"); } @@ -87,14 +84,23 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse sendErrorResponse(response,ErrorCode.TOKEN_NOT_EXIST); } else { + try{ - String userEmail = JwtTokenProvider.getUserEmailFromJWT(jwt); //jwt에서 사용자 id를 꺼낸다. + //Redis 에 해당 accessToken logout 여부 확인 + String isLogout = redisDao.getValues(jwt); + if (ObjectUtils.isEmpty(isLogout)) { + String userEmail = JwtTokenProvider.getUserEmailFromJWT(jwt); //jwt에서 사용자 id를 꺼낸다. + + UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userEmail, null, null); //id를 인증한다. + authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); //기본적으로 제공한 details 세팅 + SecurityContextHolder.getContext().setAuthentication(authentication); //세션에서 계속 사용하기 위해 securityContext에 Authentication 등록 - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userEmail, null, null); //id를 인증한다. - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); //기본적으로 제공한 details 세팅 - SecurityContextHolder.getContext().setAuthentication(authentication); //세션에서 계속 사용하기 위해 securityContext에 Authentication 등록 + + } filterChain.doFilter(request, response); + + // catch 구문 jwt 토큰 유효성 검사 }catch (IllegalArgumentException e) { log.error("an error occured during getting username from token", e); diff --git a/src/main/java/com/cmc/monggeul/global/config/security/jwt/JwtTokenProvider.java b/src/main/java/com/cmc/monggeul/global/config/security/jwt/JwtTokenProvider.java index e350476..3348d50 100644 --- a/src/main/java/com/cmc/monggeul/global/config/security/jwt/JwtTokenProvider.java +++ b/src/main/java/com/cmc/monggeul/global/config/security/jwt/JwtTokenProvider.java @@ -1,12 +1,33 @@ package com.cmc.monggeul.global.config.security.jwt; +import com.auth0.jwt.JWT; +import com.auth0.jwt.interfaces.DecodedJWT; +import com.cmc.monggeul.domain.user.entity.User; +import com.cmc.monggeul.global.config.error.ErrorCode; +import com.cmc.monggeul.global.config.error.exception.BaseException; +import com.cmc.monggeul.global.config.redis.RedisDao; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import io.jsonwebtoken.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import javax.security.auth.Subject; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collection; import java.util.Date; +import java.util.Objects; +import java.util.stream.Collectors; + @Slf4j @Component @@ -16,6 +37,8 @@ public class JwtTokenProvider { private long tokenValidTime = 30 * 60 * 1000L; + private static RedisDao redisDao; + // 토큰 유효시간 private static final int JWT_EXPIRATION_MS = 1000 * 60 * 60 * 24*14; @@ -38,6 +61,9 @@ public static TokenDto generateToken(Authentication authentication) { .setExpiration(expiryDate) // 만료 시간 세팅 .signWith(SignatureAlgorithm.HS256, JWT_SECRET) // 사용할 암호화 알고리즘, signature에 들어갈 secret 값 세팅 .compact(); + DecodedJWT decodedJWT= JWT.decode(refreshToken); + + //redisDao.setValues(authentication,refreshToken,Long.parseLong(String.valueOf(decodedJWT.getExpiresAt().getTime()))); return TokenDto.builder() .grantType("Bearer") @@ -58,6 +84,51 @@ public static String getUserEmailFromJWT(String token) { return claims.getSubject(); } + public Subject getSubject(String accessToken) throws JsonProcessingException { + String subjectStr = Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(accessToken).getBody().getSubject(); + return new ObjectMapper().readValue(subjectStr, Subject.class); + } + + + // refresh token으로 accessToken 재발급 + + public TokenDto reissueAtk(String userEmail,String rtkEmail) throws JsonProcessingException { + + if(!rtkEmail.equals(userEmail)){ + throw new BaseException(ErrorCode.EXPIRED_AUTHENTICATION); + } + Authentication authentication=new UsernamePasswordAuthenticationToken(userEmail,null,null); + + Date now = new Date(); + Date expiryDate = new Date(now.getTime() + JWT_EXPIRATION_MS); + + String accessToken=Jwts.builder() + .setSubject((String) authentication.getPrincipal()) // 사용자 + .setIssuedAt(new Date()) // 현재 시간 기반으로 생성 + .setExpiration(new Date(now.getTime()+1000 * 60 * 60 * 24)) // 만료 시간 세팅 (1일) + .signWith(SignatureAlgorithm.HS256, JWT_SECRET) // 사용할 암호화 알고리즘, signature에 들어갈 secret 값 세팅 + .compact(); + + String refreshToken=Jwts.builder() + .setSubject((String) authentication.getPrincipal()) // 사용자 + .setIssuedAt(new Date()) // 현재 시간 기반으로 생성 + .setExpiration(expiryDate) // 만료 시간 세팅 + .signWith(SignatureAlgorithm.HS256, JWT_SECRET) // 사용할 암호화 알고리즘, signature에 들어갈 secret 값 세팅 + .compact(); + return TokenDto.builder() + .grantType("Bearer") + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } + public Long getExpiration(String accessToken) { + // accessToken 남은 유효시간 + Date expiration = Jwts.parserBuilder().setSigningKey(JWT_SECRET).build().parseClaimsJws(accessToken).getBody().getExpiration(); + // 현재 시간 + Long now = new Date().getTime(); + return (expiration.getTime() - now); + } +