diff --git a/build.gradle b/build.gradle index 26c04e0..0093ce5 100644 --- a/build.gradle +++ b/build.gradle @@ -63,6 +63,9 @@ dependencies { annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor "jakarta.persistence:jakarta.persistence-api" + + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' } diff --git a/src/main/java/com/codiary/backend/domain/member/controller/MemberController.java b/src/main/java/com/codiary/backend/domain/member/controller/MemberController.java index f55fa61..911eb0a 100644 --- a/src/main/java/com/codiary/backend/domain/member/controller/MemberController.java +++ b/src/main/java/com/codiary/backend/domain/member/controller/MemberController.java @@ -1,21 +1,17 @@ package com.codiary.backend.domain.member.controller; import com.codiary.backend.domain.member.converter.MemberConverter; +import com.codiary.backend.domain.member.dto.request.MemberRequestDTO; +import com.codiary.backend.domain.member.dto.response.MemberResponseDTO; import com.codiary.backend.domain.member.entity.Member; import com.codiary.backend.domain.member.service.MemberCommandService; import com.codiary.backend.domain.member.service.MemberQueryService; import com.codiary.backend.global.apiPayload.ApiResponse; -import com.codiary.backend.domain.member.dto.request.MemberRequestDTO; -import com.codiary.backend.domain.member.dto.response.MemberResponseDTO; +import com.codiary.backend.global.apiPayload.code.status.SuccessStatus; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import com.codiary.backend.global.apiPayload.code.status.SuccessStatus; import org.springframework.web.bind.annotation.*; @RequiredArgsConstructor @@ -48,26 +44,28 @@ public ApiResponse checkNicknameDuplication(@RequestParam String nicknam } @PostMapping("/login") - @Operation( - summary = "로그인" - ) + @Operation(summary = "로그인") public ApiResponse login(@Valid @RequestBody MemberRequestDTO.MemberLoginRequestDTO request) { return memberCommandService.login(request); } @PostMapping("/logout") @Operation(summary = "로그아웃") - public ApiResponse logout(@RequestHeader("Authorization") String token) { - Member member = memberCommandService.getRequester(); - String jwtToken = token.substring(7); - String response = memberCommandService.logout(jwtToken, member); + public ApiResponse logout(@Valid @RequestBody MemberRequestDTO.refreshRequestDTO request) { + String response = memberCommandService.logout(request.refreshToken()); + return ApiResponse.onSuccess(SuccessStatus.MEMBER_OK, response); + } + + @PostMapping("/refresh") + @Operation(summary = "액세스 토큰 재할당") + public ApiResponse refresh(@Valid @RequestBody MemberRequestDTO.refreshRequestDTO request) { + String jwtToken = request.refreshToken(); + MemberResponseDTO.TokenRefreshResponseDTO response = memberCommandService.refresh(jwtToken); return ApiResponse.onSuccess(SuccessStatus.MEMBER_OK, response); } @PatchMapping(path = "/profile-image", consumes = "multipart/form-data") - @Operation( - summary = "프로필 사진 설정" - ) + @Operation(summary = "프로필 사진 설정") public ApiResponse updateProfileImage(@ModelAttribute MemberRequestDTO.MemberProfileImageRequestDTO request) { Member member = memberCommandService.getRequester(); @@ -76,7 +74,7 @@ public ApiResponse updateProfileImage(@ModelAt @DeleteMapping("/profile-image") @Operation(summary = "프로필 사진 삭제") - public ApiResponse deleteProflieImage() { + public ApiResponse deleteProfileImage() { Member member = memberCommandService.getRequester(); return memberCommandService.deleteProfileImage(member); } diff --git a/src/main/java/com/codiary/backend/domain/member/dto/request/MemberRequestDTO.java b/src/main/java/com/codiary/backend/domain/member/dto/request/MemberRequestDTO.java index e09667c..3f0d4b2 100644 --- a/src/main/java/com/codiary/backend/domain/member/dto/request/MemberRequestDTO.java +++ b/src/main/java/com/codiary/backend/domain/member/dto/request/MemberRequestDTO.java @@ -1,8 +1,8 @@ package com.codiary.backend.domain.member.dto.request; +import com.codiary.backend.domain.member.entity.Member; import com.codiary.backend.global.apiPayload.code.status.ErrorStatus; import com.codiary.backend.global.apiPayload.exception.handler.MemberHandler; -import com.codiary.backend.domain.member.entity.Member; import com.fasterxml.jackson.annotation.JsonIgnore; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.web.multipart.MultipartFile; @@ -45,7 +45,8 @@ public UsernamePasswordAuthenticationToken toAuthentication() { } // 프로필 이미지 요청 DTO - public record MemberProfileImageRequestDTO(MultipartFile image) {} + public record MemberProfileImageRequestDTO(MultipartFile image) { + } // 회원 정보 요청 DTO public record MemberInfoRequestDTO( @@ -53,5 +54,10 @@ public record MemberInfoRequestDTO( String introduction, String github, String linkedin, - String discord) {} + String discord) { + } + + public record refreshRequestDTO( + String refreshToken) { + } } diff --git a/src/main/java/com/codiary/backend/domain/member/dto/response/MemberResponseDTO.java b/src/main/java/com/codiary/backend/domain/member/dto/response/MemberResponseDTO.java index e69294b..0b94768 100644 --- a/src/main/java/com/codiary/backend/domain/member/dto/response/MemberResponseDTO.java +++ b/src/main/java/com/codiary/backend/domain/member/dto/response/MemberResponseDTO.java @@ -14,6 +14,14 @@ public record MemberTokenResponseDTO ( TokenInfo tokenInfo, String email, String nickname, + Long memberId) { + } + + @Builder + public record TokenRefreshResponseDTO( + String accessToken, + String email, + String nickname, Long memberId) {} @Builder diff --git a/src/main/java/com/codiary/backend/domain/member/entity/Token.java b/src/main/java/com/codiary/backend/domain/member/entity/Token.java deleted file mode 100644 index 74b9ee2..0000000 --- a/src/main/java/com/codiary/backend/domain/member/entity/Token.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.codiary.backend.domain.member.entity; - -import jakarta.persistence.*; -import lombok.*; - -import java.util.Date; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Token { - - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @Column(nullable = false, unique = true) - private String notAvailableToken; - - @Column(nullable = false) - @Temporal(TemporalType.TIMESTAMP) - private Date expiryTime; - - @Builder - public Token(String notAvailableToken, Date expiryTime) { - this.notAvailableToken = notAvailableToken; - this.expiryTime = expiryTime; - } -} diff --git a/src/main/java/com/codiary/backend/domain/member/repository/TokenRepository.java b/src/main/java/com/codiary/backend/domain/member/repository/TokenRepository.java deleted file mode 100644 index b97d210..0000000 --- a/src/main/java/com/codiary/backend/domain/member/repository/TokenRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.codiary.backend.domain.member.repository; - -import com.codiary.backend.domain.member.entity.Token; -import org.springframework.data.jpa.repository.JpaRepository; - -import java.util.Date; - -public interface TokenRepository extends JpaRepository { - - Boolean existsByNotAvailableToken(String token); - void deleteByExpiryTimeBefore(Date expiryTime); -} diff --git a/src/main/java/com/codiary/backend/domain/member/scheduler/TokenCleanupScheduler.java b/src/main/java/com/codiary/backend/domain/member/scheduler/TokenCleanupScheduler.java deleted file mode 100644 index 22cfa2f..0000000 --- a/src/main/java/com/codiary/backend/domain/member/scheduler/TokenCleanupScheduler.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.codiary.backend.domain.member.scheduler; - -import com.codiary.backend.domain.member.repository.TokenRepository; -import jakarta.transaction.Transactional; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -import java.util.Date; - -@Component -public class TokenCleanupScheduler { - - @Autowired - private TokenRepository tokenRepository; - - @Scheduled(cron = "0 0 * * * *") - @Transactional - public void removeExpiredToken() { - System.out.println("실행"); - tokenRepository.deleteByExpiryTimeBefore(new Date()); - } -} diff --git a/src/main/java/com/codiary/backend/domain/member/service/MemberCommandService.java b/src/main/java/com/codiary/backend/domain/member/service/MemberCommandService.java index 07dbb02..e587740 100644 --- a/src/main/java/com/codiary/backend/domain/member/service/MemberCommandService.java +++ b/src/main/java/com/codiary/backend/domain/member/service/MemberCommandService.java @@ -4,11 +4,9 @@ import com.codiary.backend.domain.member.dto.response.MemberResponseDTO; import com.codiary.backend.domain.member.entity.Member; import com.codiary.backend.domain.member.entity.MemberImage; -import com.codiary.backend.domain.member.entity.Token; import com.codiary.backend.domain.member.entity.Uuid; import com.codiary.backend.domain.member.repository.MemberImageRepository; import com.codiary.backend.domain.member.repository.MemberRepository; -import com.codiary.backend.domain.member.repository.TokenRepository; import com.codiary.backend.domain.member.repository.UuidRepository; import com.codiary.backend.global.apiPayload.ApiResponse; import com.codiary.backend.global.apiPayload.code.status.ErrorStatus; @@ -19,6 +17,7 @@ import com.codiary.backend.global.jwt.TokenInfo; import com.codiary.backend.global.s3.AmazonS3Manager; import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.core.Authentication; @@ -29,6 +28,7 @@ import java.util.Date; import java.util.UUID; +import java.util.concurrent.TimeUnit; @Service @RequiredArgsConstructor @@ -42,7 +42,7 @@ public class MemberCommandService { private final UuidRepository uuidRepository; private final AmazonS3Manager s3Manager; private final MemberImageRepository memberImageRepository; - private final TokenRepository tokenRepository; + private final RedisTemplate redisTemplate; @Transactional public ApiResponse signUp(MemberRequestDTO.MemberSignUpRequestDTO signUpRequest) { @@ -119,18 +119,43 @@ public ApiResponse login(MemberRequest } @Transactional - public String logout(String token, Member member) { - if (!tokenRepository.existsByNotAvailableToken(token)) { - Date expiryTime = new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24); - Token tokenEntity = Token.builder() - .expiryTime(expiryTime) - .notAvailableToken(token) - .build(); - tokenRepository.save(tokenEntity); + public String logout(String refreshToken) { + if (redisTemplate.hasKey(refreshToken)) { + throw new MemberHandler(ErrorStatus.MEMBER_ALREADY_LOGGED_OUT); + } + + if (jwtTokenProvider.validateToken(refreshToken)) { + Date expirationDate = jwtTokenProvider.getExpirationTimeFromToken(refreshToken); + long expirationTime = (expirationDate.getTime() - (new Date()).getTime()) / 1000; + redisTemplate.opsForValue().set(refreshToken, "blacklisted", expirationTime, TimeUnit.SECONDS); + } else { + throw new MemberHandler(ErrorStatus.MEMBER_WRONG_TOKEN); } return "로그아웃되었습니다."; } + @Transactional + public MemberResponseDTO.TokenRefreshResponseDTO refresh(String refreshToken) { + if (!jwtTokenProvider.validateToken(refreshToken)) { + throw new MemberHandler(ErrorStatus.MEMBER_WRONG_TOKEN); + } + + if (redisTemplate.hasKey(refreshToken)) { + throw new MemberHandler(ErrorStatus.MEMBER_ALREADY_LOGGED_OUT); + } + + String userEmail = jwtTokenProvider.getUserEmailFromToken(refreshToken); + String newAccessToken = jwtTokenProvider.createAccessToken(userEmail); + Member member = memberRepository.findByEmail(userEmail).orElseThrow(() -> new MemberHandler(ErrorStatus.MEMBER_NOT_FOUND)); + + return MemberResponseDTO.TokenRefreshResponseDTO.builder() + .accessToken(newAccessToken) + .email(userEmail) + .nickname(member.getNickname()) + .memberId(member.getMemberId()) + .build(); + } + @Transactional public Member getRequester() { String userEmail = SecurityUtil.getCurrentMemberEmail(); diff --git a/src/main/java/com/codiary/backend/global/apiPayload/code/status/ErrorStatus.java b/src/main/java/com/codiary/backend/global/apiPayload/code/status/ErrorStatus.java index 41880dd..be6f7e6 100644 --- a/src/main/java/com/codiary/backend/global/apiPayload/code/status/ErrorStatus.java +++ b/src/main/java/com/codiary/backend/global/apiPayload/code/status/ErrorStatus.java @@ -12,8 +12,8 @@ public enum ErrorStatus implements BaseErrorCode { // 가장 일반적인 응답 _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."), - _BAD_REQUEST(HttpStatus.BAD_REQUEST,"COMMON400","잘못된 요청입니다."), - _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), + _BAD_REQUEST(HttpStatus.BAD_REQUEST, "COMMON400", "잘못된 요청입니다."), + _UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "COMMON401", "인증이 필요합니다."), _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), // 회원 관려 에러 1000 @@ -27,6 +27,9 @@ public enum ErrorStatus implements BaseErrorCode { MEMBER_WRONG_PASSWORD(HttpStatus.BAD_REQUEST, "MEMBER_1008", "비밀번호 형식이 올바르지 않습니다."), MEMBER_EMAIL_ALREADY_EXISTS(HttpStatus.CONFLICT, "MEMBER_1009", "이미 가입된 이메일입니다."), MEMBER_NICKNAME_ALREADY_EXISTS(HttpStatus.CONFLICT, "MEMBER_1010", "이미 존재하는 닉네임입니다."), + MEMBER_REFRESH_FAIL(HttpStatus.BAD_REQUEST, "MEMBER_1011", "토큰 갱신에 실패했습니다."), + MEMBER_ALREADY_LOGGED_OUT(HttpStatus.BAD_REQUEST, "MEMBER_1012", "로그아웃된 유저입니다."), + MEMBER_WRONG_TOKEN(HttpStatus.BAD_REQUEST, "MEMBER_1013", "잘못된 토큰입니다."), MEMBER_SELF_FOLLOW(HttpStatus.BAD_REQUEST, "MEMBER_1100", "셀프 팔로우 기능은 제공하지 않습니다"), TECH_STACK_ALREADY_EXISTS(HttpStatus.CONFLICT, "MEMBER_1111", "이미 존재하는 기술스택입니다."), PROJECT_ALREADY_EXISTS(HttpStatus.CONFLICT, "MEMBER_1112", "이미 존재하는 프로젝트입니다."), @@ -52,7 +55,6 @@ public enum ErrorStatus implements BaseErrorCode { COMMENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "COMMENT_4005", "댓글이 없습니다."), - // 북마크 관련 에러 6000 // BOOKMARK_CREATE_UNAUTHORIZED(HttpStatus.BAD_REQUEST, "BOOKMARK_6001", "북마크 추가 권한이 없습니다."), // BOOKMARK_VIEW_UNAUTHORIZED(HttpStatus.BAD_REQUEST, "BOOKMARK_6002", "북마크 게시글 리스트 조회 권한이 없습니다."), @@ -66,11 +68,7 @@ public enum ErrorStatus implements BaseErrorCode { MEMBERCATEGORY_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBERCATEGORY_8001", "회원별 관심 카테고리가 없습니다."), // 프로젝트 관련 에러 9000 - PROJECT_NOT_FOUND(HttpStatus.BAD_REQUEST, "PROJECT_3009", "프로젝트가 없습니다.") - - - ; - + PROJECT_NOT_FOUND(HttpStatus.BAD_REQUEST, "PROJECT_3009", "프로젝트가 없습니다."); private final HttpStatus httpStatus; diff --git a/src/main/java/com/codiary/backend/global/config/RedisConfig.java b/src/main/java/com/codiary/backend/global/config/RedisConfig.java new file mode 100644 index 0000000..e64b1bb --- /dev/null +++ b/src/main/java/com/codiary/backend/global/config/RedisConfig.java @@ -0,0 +1,32 @@ +package com.codiary.backend.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public LettuceConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + return redisTemplate; + } +} diff --git a/src/main/java/com/codiary/backend/global/config/SecurityConfig.java b/src/main/java/com/codiary/backend/global/config/SecurityConfig.java index 4206368..0f4776c 100644 --- a/src/main/java/com/codiary/backend/global/config/SecurityConfig.java +++ b/src/main/java/com/codiary/backend/global/config/SecurityConfig.java @@ -3,7 +3,6 @@ import com.codiary.backend.global.jwt.EmailPasswordAuthenticationFilter; import com.codiary.backend.global.jwt.JwtAuthenticationFilter; import com.codiary.backend.global.jwt.JwtTokenProvider; -import com.codiary.backend.domain.member.repository.TokenRepository; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -24,7 +23,6 @@ @RequiredArgsConstructor public class SecurityConfig { private final JwtTokenProvider jwtTokenProvider; - private final TokenRepository tokenRepository; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -53,7 +51,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti authorize -> authorize // Member 관련 접근 .requestMatchers("/api/v2/members/sign-up", "/api/v2/members/sign-up/check-email", "api/v2/members/sign-up/check-nickname").permitAll() - .requestMatchers("/api/v2/members/login", "api/v2/members/logout").permitAll() + .requestMatchers("/api/v2/members/login", "/api/v2/members/refresh", "api/v2/members/logout").permitAll() // Post 관련 접근 // Comment 관련 접근 // Team 관련 접근 @@ -65,7 +63,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/", "/api-docs/**", "/api-docs/swagger-config/*", "/swagger-ui/*", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .anyRequest().authenticated() ) - .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, tokenRepository), EmailPasswordAuthenticationFilter.class).build(); + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), EmailPasswordAuthenticationFilter.class).build(); } @Bean diff --git a/src/main/java/com/codiary/backend/global/jwt/JwtAuthenticationFilter.java b/src/main/java/com/codiary/backend/global/jwt/JwtAuthenticationFilter.java index cf2196d..22abb5c 100644 --- a/src/main/java/com/codiary/backend/global/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/codiary/backend/global/jwt/JwtAuthenticationFilter.java @@ -1,12 +1,10 @@ package com.codiary.backend.global.jwt; -import com.codiary.backend.domain.member.repository.TokenRepository; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; @@ -14,16 +12,14 @@ import org.springframework.web.filter.GenericFilterBean; import java.io.IOException; -import java.io.PrintWriter; @RequiredArgsConstructor public class JwtAuthenticationFilter extends GenericFilterBean { -// extends OncePerRequestFilter + // extends OncePerRequestFilter public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String BEARER_PREFIX = "Bearer"; private final JwtTokenProvider jwtTokenProvider; - private final TokenRepository tokenRepository; @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -32,28 +28,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha // 2. validateToken으로 토큰 유효성 검사 if (token != null && jwtTokenProvider.validateToken(token)) { - // 로그아웃 한 경우 - if (tokenRepository.existsByNotAvailableToken(token)) { - HttpServletResponse httpResponse = (HttpServletResponse) response; - httpResponse.setContentType("application/json"); - httpResponse.setHeader("Access-Control-Allow-Origin", "*"); - httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, DELETE, PUT"); - httpResponse.setHeader("Access-Control-Max-Age", "3600"); - httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, origin, content-type, accept"); - httpResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - - // 오류 메시지 JSON 생성 - String jsonResponse = "{" - + "\"isSuccess\": false," - + "\"code\": \"TOKEN_ERROR\"," - + "\"message\": \"invalid token\"" - + "}"; - - PrintWriter out = httpResponse.getWriter(); - out.print(jsonResponse); - out.flush(); - } - // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장 Authentication authentication = jwtTokenProvider.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); diff --git a/src/main/java/com/codiary/backend/global/jwt/JwtTokenProvider.java b/src/main/java/com/codiary/backend/global/jwt/JwtTokenProvider.java index 661e0d4..ae84f3f 100644 --- a/src/main/java/com/codiary/backend/global/jwt/JwtTokenProvider.java +++ b/src/main/java/com/codiary/backend/global/jwt/JwtTokenProvider.java @@ -59,6 +59,7 @@ public TokenInfo generateToken(Authentication authentication, Long memberId) { // Refresh Token 생성 String refreshToken = Jwts.builder() + .setSubject(authentication.getName()) .setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME)) .signWith(key, SignatureAlgorithm.HS256) .compact(); @@ -87,6 +88,7 @@ public TokenInfo generateToken(String email, Long memberId) { // Refresh Token 생성 String refreshToken = Jwts.builder() + .setSubject(email) .setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME)) .signWith(key, SignatureAlgorithm.HS256) .compact(); @@ -127,7 +129,7 @@ public boolean validateToken(String token) { log.info("Unsupported JWT Token", e); } catch (IllegalArgumentException e) { log.info("JWT claims string is empty.", e); - } catch (Exception e){ + } catch (Exception e) { System.out.println("잘못된 토큰 값입니다."); } return false; @@ -166,4 +168,27 @@ private String resolveToken(HttpServletRequest request) { } return null; } + + public String createAccessToken(String email) { + long now = (new Date()).getTime(); + + String accessToken = Jwts.builder() + .setSubject(email) + .claim(AUTHORITIES_KEY, "ROLE_USER") + .setExpiration(new Date(now + ACCESS_TOKEN_EXPIRE_TIME)) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + + return accessToken; + } + + public String getUserEmailFromToken(String token) { + Claims claims = parseClaims(token); + return claims.getSubject(); + } + + public Date getExpirationTimeFromToken(String token) { + Claims claims = parseClaims(token); + return claims.getExpiration(); + } }