diff --git a/build.gradle b/build.gradle index e04cf8d..11dfce3 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,7 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + implementation 'org.springframework.boot:spring-boot-starter-data-redis' //FCM implementation 'com.google.firebase:firebase-admin:9.2.0' diff --git a/docker-compose.yml b/docker-compose.yml index 5309a0a..5910b71 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,13 @@ version: '3' services: + redis: + image: redis + container_name: redis + ports: + - 6379:6379 + restart: always + web: container_name: web image: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} diff --git a/src/main/java/ewha/lux/once/domain/user/controller/UserController.java b/src/main/java/ewha/lux/once/domain/user/controller/UserController.java index e232cb7..2c26d1a 100644 --- a/src/main/java/ewha/lux/once/domain/user/controller/UserController.java +++ b/src/main/java/ewha/lux/once/domain/user/controller/UserController.java @@ -3,13 +3,9 @@ import ewha.lux.once.domain.home.dto.FCMTokenDto; import ewha.lux.once.domain.home.service.FirebaseCloudMessageService; import ewha.lux.once.domain.user.dto.*; -import ewha.lux.once.domain.user.entity.Users; import ewha.lux.once.domain.user.service.UserService; -import ewha.lux.once.global.common.CommonResponse; -import ewha.lux.once.global.common.CustomException; -import ewha.lux.once.global.common.ResponseCode; -import ewha.lux.once.global.common.UserAccount; -import ewha.lux.once.global.security.JwtProvider; +import ewha.lux.once.global.common.*; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.data.repository.query.Param; import org.springframework.http.MediaType; @@ -18,6 +14,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import ewha.lux.once.global.security.JwtProvider; import java.io.IOException; import java.text.ParseException; @@ -35,13 +32,7 @@ public class UserController { @PostMapping("/signup") public CommonResponse signup(@RequestBody SignupRequestDto request) throws ParseException { try { - Users users = userService.signup(request); - - String accessToken = jwtProvider.generateAccessToken(users.getLoginId()); - String refreshToken = jwtProvider.generateRefreshToken(users.getLoginId()); - - LoginResponseDto loginResponseDto = new LoginResponseDto(users.getId(), accessToken, refreshToken); - return new CommonResponse<>(ResponseCode.SUCCESS, loginResponseDto); + return new CommonResponse<>(ResponseCode.SUCCESS, userService.signup(request)); } catch (CustomException e) { return new CommonResponse<>(e.getStatus()); } @@ -51,19 +42,31 @@ public CommonResponse signup(@RequestBody SignupRequestDto request) throws Pa @PostMapping("/login") public CommonResponse signin(@RequestBody SignInRequestDto request) { try { - Users user = userService.authenticate(request); - - String accessToken = jwtProvider.generateAccessToken(user.getLoginId()); - String refreshToken = jwtProvider.generateRefreshToken(user.getLoginId()); - - LoginResponseDto loginResponseDto = new LoginResponseDto(user.getId(), accessToken, refreshToken); + return new CommonResponse<>(ResponseCode.SUCCESS, userService.authenticate(request)); + } catch (CustomException e) { + return new CommonResponse<>(e.getStatus()); + } + } + // [Post] 자동로그인 + @PostMapping("/auto") + public CommonResponse autologinPage() { + return new CommonResponse<>(ResponseCode.VALID_ACCESS_TOKEN); + } - return new CommonResponse<>(ResponseCode.SUCCESS, loginResponseDto); + // [Post] 로그아웃 + @PostMapping("/logout") + @ResponseBody + public CommonResponse logoutPage(HttpServletRequest request,@AuthenticationPrincipal UserAccount userAccount) { + try { + userService.postLogout(request, userAccount.getUsers()); + return new CommonResponse<>(ResponseCode.SUCCESS); } catch (CustomException e) { return new CommonResponse<>(e.getStatus()); } } + + // [Delete] 회원 탈퇴 @DeleteMapping("/quit") @ResponseBody diff --git a/src/main/java/ewha/lux/once/domain/user/service/RedisService.java b/src/main/java/ewha/lux/once/domain/user/service/RedisService.java new file mode 100644 index 0000000..5649047 --- /dev/null +++ b/src/main/java/ewha/lux/once/domain/user/service/RedisService.java @@ -0,0 +1,38 @@ +package ewha.lux.once.domain.user.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +@Service +@RequiredArgsConstructor +public class RedisService { + private final RedisTemplate redisTemplate; + private static final String REFRESH_TOKEN_PREFIX = "refreshToken:"; + private static final String ACCESS_TOKEN_BLACKLIST_PREFIX = "blacklistAccessToken:"; + + + public void setRefreshValueWithTTL(String user_id, String refreshToken, long timeout, TimeUnit unit) { + String key = REFRESH_TOKEN_PREFIX + user_id; + ValueOperations valueOperations = redisTemplate.opsForValue(); + valueOperations.set(key, refreshToken, timeout, unit); + } + public void setAccessBlackValueWithTTL(String accessToken, String value, long timeout, TimeUnit unit) { + String key = ACCESS_TOKEN_BLACKLIST_PREFIX + accessToken; + ValueOperations valueOperations = redisTemplate.opsForValue(); + valueOperations.set(key, value, timeout, unit); + } + + public Object getValue(String key) { + ValueOperations valueOperations = redisTemplate.opsForValue(); + Object object = valueOperations.get(key); + return object; + } + + public void deleteValue(String key) { + redisTemplate.delete(key); + } +} diff --git a/src/main/java/ewha/lux/once/domain/user/service/UserService.java b/src/main/java/ewha/lux/once/domain/user/service/UserService.java index 0aca133..7eda671 100644 --- a/src/main/java/ewha/lux/once/domain/user/service/UserService.java +++ b/src/main/java/ewha/lux/once/domain/user/service/UserService.java @@ -9,8 +9,12 @@ import ewha.lux.once.domain.user.entity.Users; import ewha.lux.once.global.common.CustomException; import ewha.lux.once.global.common.ResponseCode; +import ewha.lux.once.global.security.JwtAuthFilter; +import ewha.lux.once.global.security.JwtProvider; +import jakarta.servlet.http.HttpServletRequest; import ewha.lux.once.global.repository.*; import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; @@ -24,7 +28,9 @@ import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; + import java.util.stream.Collectors; +import java.util.concurrent.TimeUnit; @Service @RequiredArgsConstructor @@ -34,14 +40,22 @@ public class UserService implements UserDetailsService { private final CardRepository cardRepository; private final CardCompanyRepository cardCompanyRepository; private final OwnedCardRepository ownedCardRepository; + + private final JwtAuthFilter jwtAuthFilter; + private final JwtProvider jwtProvider; + private final AnnouncementRepository announcementRepository; private final ChatHistoryRepository chatHistoryRepository; private final ConnectedCardCompanyRepository connectedCardCompanyRepository; private final FavoriteRepository favoriteRepository; private final FCMTokenRepository fcmTokenRepository; + private final S3Uploader s3Uploader; + private final RedisTemplate redisTemplate; + private final RedisService redisService; + private static final String REFRESH_TOKEN_PREFIX = "refreshToken:"; - public Users signup(SignupRequestDto request) throws CustomException, ParseException { + public LoginResponseDto signup(SignupRequestDto request) throws CustomException, ParseException { String loginId = request.getLoginId(); String username = request.getUsername(); String password = request.getPassword(); @@ -77,10 +91,18 @@ public Users signup(SignupRequestDto request) throws CustomException, ParseExcep usersBuilder.birthday(birthday); } - return usersRepository.save(usersBuilder.benefitGoal(100000).build()); + Users newUser = usersRepository.save(usersBuilder.benefitGoal(100000).build()); + + String accessToken = jwtProvider.createAccessToken(newUser.getLoginId()); + String refreshToken = jwtProvider.createRefreshToken(newUser.getLoginId()); + + LoginResponseDto loginResponseDto = new LoginResponseDto(newUser.getId(), accessToken, refreshToken); + redisService.setRefreshValueWithTTL(newUser.getId().toString(), refreshToken, 14L, TimeUnit.DAYS); + + return loginResponseDto; } - public Users authenticate(SignInRequestDto request) throws CustomException { + public LoginResponseDto authenticate(SignInRequestDto request) throws CustomException { String loginId = request.getLoginId(); String password = request.getPassword(); @@ -93,7 +115,22 @@ public Users authenticate(SignInRequestDto request) throws CustomException { users.setLastLogin(); usersRepository.save(users); - return users; + + String accessToken = jwtProvider.createAccessToken(users.getLoginId()); + String refreshToken = jwtProvider.createRefreshToken(users.getLoginId()); + LoginResponseDto loginResponseDto = new LoginResponseDto(users.getId(), accessToken, refreshToken); + redisService.setRefreshValueWithTTL(users.getId().toString(), refreshToken, 14L, TimeUnit.DAYS); + + return loginResponseDto; + } + + public void postLogout(HttpServletRequest request, Users nowuser) throws CustomException { + String accessToken = jwtAuthFilter.resolveToken(request,jwtAuthFilter.HEADER_KEY); + Long expiration = jwtProvider.getExpiration(accessToken); + redisService.setAccessBlackValueWithTTL(accessToken,"logout",expiration, TimeUnit.MILLISECONDS); + // 리프레시 토큰 삭제 + redisService.deleteValue(REFRESH_TOKEN_PREFIX+nowuser.getId().toString()); + System.out.println("왕왕왕왕왕"); } public void deleteUsers(Users nowUser) throws CustomException { diff --git a/src/main/java/ewha/lux/once/global/common/ResponseCode.java b/src/main/java/ewha/lux/once/global/common/ResponseCode.java index 75b4c14..cbfbb5f 100644 --- a/src/main/java/ewha/lux/once/global/common/ResponseCode.java +++ b/src/main/java/ewha/lux/once/global/common/ResponseCode.java @@ -12,11 +12,13 @@ public enum ResponseCode { CHANGE_MYPAGE_SUCCESS(1002, true, "내 정보 수정을 성공했습니다."), RELEASE_MAINCARD_SUCCESS(1003, true, "주카드 해제를 성공했습니다."), DELETE_CARD_SUCCESS(1004, true, "등록 카드 삭제에 성공했습니다."), + VALID_ACCESS_TOKEN(1005, true, "유효한 access token입니다."), /* 2000~ : Request 오류 */ + // ===================================== /* 3000~ : Response 오류 @@ -26,6 +28,10 @@ public enum ResponseCode { INVALID_USER_ID(3001, false, "아이디가 존재하지 않습니다."), FAILED_TO_LOGIN(3002, false, "비밀번호가 일치하지 않습니다."), DUPLICATED_USER_NAME(3003, false,"이미 존재하는 아이디입니다."), + UNAUTHORIZED_REFRESH(3004, false, "refresh token이 만료되었습니다. 다시 로그인해주세요"), + UNAUTHORIZED(3005, false, "access token이 유효하지 않습니다."), + INVALID_REFRESH(3006, false, "refresh token이 유효하지 않습니다."), + BLACKLISTED_TOKEN(3007, false, "블랙리스트에 등록된 토큰입니다."), // 3100~ : card 관련 오류 CARD_NOT_FOUND(3100, false,"존재하지 않는 카드입니다."), diff --git a/src/main/java/ewha/lux/once/global/config/JwtSecurityConfig.java b/src/main/java/ewha/lux/once/global/config/JwtSecurityConfig.java new file mode 100644 index 0000000..6d8b823 --- /dev/null +++ b/src/main/java/ewha/lux/once/global/config/JwtSecurityConfig.java @@ -0,0 +1,21 @@ +package ewha.lux.once.global.config; + +import ewha.lux.once.domain.user.service.RedisService; +import ewha.lux.once.global.security.JwtAuthFilter; +import ewha.lux.once.global.security.JwtProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.security.config.annotation.SecurityConfigurerAdapter; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@RequiredArgsConstructor +public class JwtSecurityConfig extends SecurityConfigurerAdapter { + private final JwtProvider jwtProvider; + private final RedisService redisService; + + @Override + public void configure(HttpSecurity http) throws Exception{ + http.addFilterBefore(new JwtAuthFilter(jwtProvider, redisService), UsernamePasswordAuthenticationFilter.class); + } +} diff --git a/src/main/java/ewha/lux/once/global/config/RedisConfig.java b/src/main/java/ewha/lux/once/global/config/RedisConfig.java new file mode 100644 index 0000000..c76c82e --- /dev/null +++ b/src/main/java/ewha/lux/once/global/config/RedisConfig.java @@ -0,0 +1,34 @@ +package ewha.lux.once.global.config; + +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.data.redis.RedisProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@RequiredArgsConstructor +@Configuration +@EnableRedisRepositories +public class RedisConfig { + private final RedisProperties redisProperties; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort()); + // return new LettuceConnectionFactory("localhost", redisProperties.getPort()); // 로컬 + } + + @Bean + public RedisTemplate redisTemplate() { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory()); + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setValueSerializer(new StringRedisSerializer()); + return redisTemplate; + } + +} diff --git a/src/main/java/ewha/lux/once/global/config/SecurityConfig.java b/src/main/java/ewha/lux/once/global/config/SecurityConfig.java index fbffa45..94d3b6f 100644 --- a/src/main/java/ewha/lux/once/global/config/SecurityConfig.java +++ b/src/main/java/ewha/lux/once/global/config/SecurityConfig.java @@ -1,5 +1,6 @@ package ewha.lux.once.global.config; +import ewha.lux.once.domain.user.service.RedisService; import ewha.lux.once.global.security.JwtAuthFilter; import ewha.lux.once.global.security.JwtProvider; import lombok.RequiredArgsConstructor; @@ -25,7 +26,7 @@ @Configuration public class SecurityConfig { private final JwtProvider jwtProvider; - + private final RedisService redisService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -57,10 +58,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .requestMatchers("/user/duplicate").permitAll() .requestMatchers("/user/login").permitAll() .requestMatchers("/user/auto").permitAll() + .requestMatchers("/user/card/search").permitAll() + .requestMatchers("/user/logout").permitAll() .requestMatchers("/user/card/**").permitAll() .anyRequest().authenticated() ) - .addFilterBefore(new JwtAuthFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(new JwtAuthFilter(jwtProvider, redisService), UsernamePasswordAuthenticationFilter.class); return http.build(); diff --git a/src/main/java/ewha/lux/once/global/security/JwtAuthFilter.java b/src/main/java/ewha/lux/once/global/security/JwtAuthFilter.java index 7b27331..79fc6db 100644 --- a/src/main/java/ewha/lux/once/global/security/JwtAuthFilter.java +++ b/src/main/java/ewha/lux/once/global/security/JwtAuthFilter.java @@ -2,6 +2,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import ewha.lux.once.domain.user.dto.LoginResponseDto; +import ewha.lux.once.domain.user.service.RedisService; +import ewha.lux.once.global.common.ResponseCode; import ewha.lux.once.global.common.UserAccount; import ewha.lux.once.domain.user.entity.Users; import ewha.lux.once.global.common.ResponseDto; @@ -11,80 +13,115 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.util.ObjectUtils; -import org.springframework.util.StringUtils; +import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.io.PrintWriter; @Slf4j @RequiredArgsConstructor +@Component public class JwtAuthFilter extends OncePerRequestFilter { private final JwtProvider jwtProvider; + private final RedisService redisService; public static final String HEADER_KEY = "Authorization"; public static final String REFRESH_HEADER_KEY = "Authorization-refresh"; public static final String PREFIX = "Bearer "; + private static final String REFRESH_TOKEN_PREFIX = "refreshToken:"; private final GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - // 자동 로그인 요청인 경우 - if(request.getRequestURI().equals("/user/auto")) { - - String accessToken = resolveToken(request, HEADER_KEY); - String refreshToken = resolveToken(request, REFRESH_HEADER_KEY); - - if (!jwtProvider.validateAccessTokenExpiration(accessToken)) { // accesstoken이 유효한 경우 - Users users = jwtProvider.validateTokenAndGetUsers(accessToken); - LoginResponseDto loginResponseDto = new LoginResponseDto(users.getId(), accessToken, refreshToken); + String accessToken = resolveToken(request, HEADER_KEY); + String refreshToken = resolveToken(request, REFRESH_HEADER_KEY); - response.setContentType("application/json"); - response.setCharacterEncoding("utf-8"); - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().write(new ObjectMapper().writeValueAsString(ResponseEntity.ok(ResponseDto.response(1000,true, "access token이 검증되었습니다.", loginResponseDto)))); - return; - } else if (!jwtProvider.validateAccessTokenExpiration(refreshToken)) { // accesstoken 만료, refreshtoken이 유효한 경우 - Users users = jwtProvider.validateTokenAndGetUsers(refreshToken); - String newAccessToken = jwtProvider.generateAccessToken(users.getLoginId()); - LoginResponseDto loginResponseDto = new LoginResponseDto(users.getId(), newAccessToken, refreshToken); - - response.setContentType("application/json"); - response.setCharacterEncoding("utf-8"); - response.setStatus(HttpServletResponse.SC_OK); - response.getWriter().write(new ObjectMapper().writeValueAsString(ResponseEntity.ok(ResponseDto.response(1000,true, "access token이 재발급되었습니다.", loginResponseDto)))); - return; - } else { // refreshtoken 만료 - response.setContentType("application/json"); - response.setCharacterEncoding("utf-8"); - response.setStatus(HttpServletResponse.SC_BAD_REQUEST); - response.getWriter().write(new ObjectMapper().writeValueAsString(ResponseEntity.ok(ResponseDto.response(1000, true, "refresh token이 만료되었습니다. 다시 로그인해주세요")))); + if (accessToken != null && jwtProvider.validateToken(accessToken)) { + // access token있고 유효할 때 + if(jwtProvider.isAccessTokenLoggedOut(accessToken)){ + // 블랙리스트 + sendErrorResponse(response,403,ResponseCode.BLACKLISTED_TOKEN); return; } - } - String accessToken = resolveToken(request, HEADER_KEY); + // users 찾아 인증정보 저장 + Users users = jwtProvider.extractUsersFromToken(accessToken); + saveAuthentication(users); - if(StringUtils.hasText(accessToken) && jwtProvider.validateToken(accessToken)){ - Users users = jwtProvider.validateTokenAndGetUsers(accessToken); + } else if (refreshToken != null) { + if (jwtProvider.validateToken(refreshToken)) { + // refresh token있고 유효할 때 - saveAuthentication(users); - filterChain.doFilter(request, response); + Users users = jwtProvider.extractUsersFromToken(refreshToken); + String redisRefreshToken = (String) redisService.getValue(REFRESH_TOKEN_PREFIX+users.getId().toString()); + + if (redisRefreshToken != null && redisRefreshToken.equals(refreshToken)) { + // access token 재발급 + String newAccessToken = jwtProvider.createAccessToken(users.getLoginId()); + + response.setHeader(HEADER_KEY, PREFIX + newAccessToken); + + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + LoginResponseDto loginResponseDto = new LoginResponseDto(users.getId(), newAccessToken, refreshToken); + + ResponseDto responseDto1 = ResponseDto.response(1000, true, "access token이 재발급되었습니다.", loginResponseDto); + + PrintWriter writer = response.getWriter(); + writer.write(new ObjectMapper().writeValueAsString(responseDto1)); + writer.flush(); + + saveAuthentication(users); + return; + + } else { + // refresh token이 유효하지 않을 때 + sendErrorResponse(response,403,ResponseCode.INVALID_REFRESH); + return; + } + } else { + // refresh token이 만료되었을 때 + sendErrorResponse(response,403,ResponseCode.UNAUTHORIZED_REFRESH); + return; + } + } else if (accessToken != null) { + // access token이 유효하지 않을 때 + sendErrorResponse(response,403,ResponseCode.UNAUTHORIZED); return; } + filterChain.doFilter(request, response); + + } + private void sendErrorResponse(HttpServletResponse response, int statusCode, ResponseCode code) throws IOException { + response.setStatus(statusCode); + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + PrintWriter writer = response.getWriter(); + ResponseDto responseDto = ResponseDto.response(code.getCode(), code.isInSuccess(), code.getMessage()); + writer.write(new ObjectMapper().writeValueAsString(responseDto)); + writer.flush(); + } - filterChain.doFilter(request, response); + // Token만 분리해 반환 (Bearer 제거) + public String resolveToken(HttpServletRequest request, String headerKey) { + String bearerToken = request.getHeader(headerKey); + if (bearerToken != null && bearerToken.startsWith(PREFIX)) { + return bearerToken.substring(PREFIX.length()); + } + return null; } + public void saveAuthentication(Users users) { UserAccount userAccount = new UserAccount(users); @@ -99,16 +136,4 @@ public void saveAuthentication(Users users) { SecurityContextHolder.setContext(context); } - // Token만 분리해 반환 - private String resolveToken(HttpServletRequest request, String key){ - String token = request.getHeader(key); - - // key의 헤더가 존재하고, Bearer로 시작한다면 - if(!ObjectUtils.isEmpty(token) && token.startsWith(PREFIX)) { - return token.substring(PREFIX.length()); - } - - return null; - } - } diff --git a/src/main/java/ewha/lux/once/global/security/JwtProvider.java b/src/main/java/ewha/lux/once/global/security/JwtProvider.java index 65e522d..632226f 100644 --- a/src/main/java/ewha/lux/once/global/security/JwtProvider.java +++ b/src/main/java/ewha/lux/once/global/security/JwtProvider.java @@ -2,7 +2,7 @@ import ewha.lux.once.domain.user.entity.Users; import ewha.lux.once.global.repository.UsersRepository; -import ewha.lux.once.domain.user.service.UserService; +import org.springframework.data.redis.core.RedisTemplate; import io.jsonwebtoken.*; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -15,85 +15,70 @@ @Component @RequiredArgsConstructor public class JwtProvider { + private static final long ACCESS_EXPIRE_TIME = 1000l * 60 * 60 * 2; // 2시간 private static final long REFRESH_EXPIRE_TIME = 1000l * 60 * 60 * 24 * 14; // 2주 - private final UserService userService; + + private final UsersRepository usersRepository; + private final RedisTemplate redisTemplate; + private static final String ACCESS_TOKEN_BLACKLIST_PREFIX = "blacklistAccessToken:"; + @Getter @Value("${spring.jwt.secret}") private String secretKey; - // 토큰 생성 - public String generateToken(String loginId,Long accessTokenValidTime,boolean isAccessToken) { - Claims claims = Jwts.claims().setSubject(loginId); - String subject = isAccessToken ? "access" : "refresh"; - - claims.put("authority", subject); // 권한 - + public String createAccessToken(String userId) { Date now = new Date(); - Date expiration = new Date(now.getTime() + accessTokenValidTime); // 만료 시간 + Date validity = new Date(now.getTime() + ACCESS_EXPIRE_TIME); return Jwts.builder() - .setClaims(claims) + .setSubject(userId) .setIssuedAt(now) - .setExpiration(expiration) - .signWith(SignatureAlgorithm.HS512, secretKey) // (비밀키, 해싱 알고리즘) + .setExpiration(validity) + .signWith(SignatureAlgorithm.HS256, secretKey) .compact(); } - // 액세스 토큰 생성 - public String generateAccessToken(String loginId) { - return generateToken(loginId, ACCESS_EXPIRE_TIME,true); - } - - // 리프레쉬 토큰 생성 - public String generateRefreshToken(String loginId) { - return generateToken(loginId, REFRESH_EXPIRE_TIME,false); - } - - // 토큰 유효성 확인, t/f 반환 - public boolean validateToken(String token) throws ExpiredJwtException { - Claims claims = Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token) - .getBody(); - - Date expiration = claims.getExpiration(); + public String createRefreshToken(String userId) { Date now = new Date(); + Date validity = new Date(now.getTime() + REFRESH_EXPIRE_TIME); - if (expiration.before(now)) { - return false; - } - - return true; + return Jwts.builder() + .setSubject(userId) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact(); } - // 토큰 검증 및 payload 추출 (토큰으로 현재 Users 찾아 반환) - public Users validateTokenAndGetUsers(String token){ - Claims claims = Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token) - .getBody(); - String loginId = claims.getSubject(); + // 토큰으로 User찾아 반환 + public Users extractUsersFromToken(String token) { + + String loginId = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); return usersRepository.findByLoginId(loginId).get(); } + // accessToken 만료 시간 반환 + public Long getExpiration(String token) { + Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody(); + return claims.getExpiration().getTime(); + } - // 토큰 만료 여부 확인 - public boolean validateAccessTokenExpiration(String token) { - try{ - Jws claims = Jwts.parser() - .setSigningKey(secretKey) - .parseClaimsJws(token); - return claims.getBody().getExpiration().before(new Date()); // 현재보다 만료가 이전인지 확인 - } - catch (ExpiredJwtException ignored){ - return true; + public boolean validateToken(String token) { + try { + Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); + return true; + } catch (Exception e) { + return false; } } - + public boolean isAccessTokenLoggedOut(String accessToken) { + // Redis에서 액세스 토큰의 상태를 가져옴 + String key = ACCESS_TOKEN_BLACKLIST_PREFIX + accessToken; + // 로그아웃 상태인지 확인 + return redisTemplate.hasKey(key); + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d2da036..6502a92 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,4 +1,8 @@ spring: + data: + redis: + host: redis + port: 6379 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: ${SPRING_DATABASE_URL}