Skip to content

Commit

Permalink
#15 Merge: Refresh token Redis 적용 (#29)
Browse files Browse the repository at this point in the history
* #2 Fix: jwt token 만료 시간 임의 조정

* #15 Feat: refresh token redis 적용
  • Loading branch information
woogieon8on authored Aug 11, 2024
1 parent 3c9b387 commit c5d9546
Show file tree
Hide file tree
Showing 15 changed files with 237 additions and 205 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ dependencies {
//swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'

// redis
//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

implementation 'org.springframework.session:spring-session-data-redis'
}

jar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ public enum ErrorStatus implements BaseCode {

// WineNote Error
WINE_NOTE_NOT_FOUND(HttpStatus.BAD_REQUEST, "WINE_NOTE4001", "와인 노트가 없습니다."),
NOT_INVALID_SCENT(HttpStatus.BAD_REQUEST, "WINE_NOTE4001", "해당 이름의 향이 없습니다.");
NOT_INVALID_SCENT(HttpStatus.BAD_REQUEST, "WINE_NOTE4001", "해당 이름의 향이 없습니다."),

// Member Error
REFRESH_TOKEN_NOT_FOUND(HttpStatus.BAD_REQUEST, "REFRESH_TOKEN4001", "리프레쉬 토큰이 없습니다."),
REFRESH_TOKEN_EXPIRED(HttpStatus.BAD_REQUEST, "REFRESH_TOKEN4001", "리프레쉬 토큰이 만료되었습니다."),
INVALID_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "REFRESH_TOKEN4001", "유효하지 않은 리프레쉬 토큰입니다."),

// Redis Error
REDIS_NOT_FOUND(HttpStatus.BAD_REQUEST, "REDIS4001", "Redis 설정에 오류가 발생했습니다.");


private final HttpStatus httpStatus;
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/com/drinkeg/drinkeg/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.drinkeg.drinkeg.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.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;

@Configuration
@EnableRedisRepositories
public class RedisConfig {

@Value("${spring.data.redis.host}")
private String host;

@Value("${spring.data.redis.port}")
private Integer port;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}

@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());

return redisTemplate;
}
}
9 changes: 5 additions & 4 deletions src/main/java/com/drinkeg/drinkeg/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import com.drinkeg.drinkeg.jwt.JWTUtil;
import com.drinkeg.drinkeg.jwt.LoginFilter;
import com.drinkeg.drinkeg.oauth2.CustomSuccessHandler;
import com.drinkeg.drinkeg.repository.RefreshRepository;
import com.drinkeg.drinkeg.redis.RedisClient;
import com.drinkeg.drinkeg.service.loginService.CustomOAuth2UserService;
import com.drinkeg.drinkeg.service.loginService.TokenService;
import lombok.RequiredArgsConstructor;
Expand Down Expand Up @@ -34,7 +34,7 @@ public class SecurityConfig {
private final CustomSuccessHandler customSuccessHandler;
private final JWTUtil jwtUtil;
private final TokenService tokenService;
private final RefreshRepository refreshRepository;
private final RedisClient redisClient;

// AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입
private final AuthenticationConfiguration authenticationConfiguration;
Expand Down Expand Up @@ -79,10 +79,11 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.addFilterBefore(new JWTFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);

http
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, tokenService), UsernamePasswordAuthenticationFilter.class);
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil, tokenService, redisClient),
UsernamePasswordAuthenticationFilter.class);

http
.addFilterBefore(new CustomLogoutFilter(jwtUtil, refreshRepository), LogoutFilter.class);
.addFilterBefore(new CustomLogoutFilter(jwtUtil, redisClient), LogoutFilter.class);

//oauth2
http
Expand Down
24 changes: 0 additions & 24 deletions src/main/java/com/drinkeg/drinkeg/controller/JoinController.java

This file was deleted.

34 changes: 34 additions & 0 deletions src/main/java/com/drinkeg/drinkeg/controller/MemberController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.drinkeg.drinkeg.controller;

import com.drinkeg.drinkeg.apipayLoad.ApiResponse;
import com.drinkeg.drinkeg.dto.securityDTO.jwtDTO.JoinDTO;
import com.drinkeg.drinkeg.service.loginService.MemberService;
import com.drinkeg.drinkeg.service.loginService.TokenService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class MemberController {

private final MemberService memberService;
private final TokenService tokenService;

@PostMapping("/join")
public ApiResponse<?> joinProcess(@RequestBody JoinDTO joinDTO) {

memberService.join(joinDTO);
return ApiResponse.onSuccess("회원가입 성공");
}

@PostMapping("/reissue")
public ApiResponse<?> reissue(HttpServletRequest request, HttpServletResponse response) {

tokenService.reissueRefreshToken(request, response);
return ApiResponse.onSuccess("리프레쉬 토큰 재발급 성공");
}
}

This file was deleted.

27 changes: 0 additions & 27 deletions src/main/java/com/drinkeg/drinkeg/domain/RefreshToken.java

This file was deleted.

22 changes: 13 additions & 9 deletions src/main/java/com/drinkeg/drinkeg/jwt/CustomLogoutFilter.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.drinkeg.drinkeg.jwt;

import com.drinkeg.drinkeg.repository.RefreshRepository;
import com.drinkeg.drinkeg.apipayLoad.code.status.ErrorStatus;
import com.drinkeg.drinkeg.exception.GeneralException;
import com.drinkeg.drinkeg.redis.RedisClient;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
Expand All @@ -10,6 +12,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;

import java.io.IOException;
Expand All @@ -18,7 +21,7 @@
public class CustomLogoutFilter extends GenericFilterBean {

private final JWTUtil jwtUtil;
private final RefreshRepository refreshRepository;
private final RedisClient redisClient;

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Expand Down Expand Up @@ -81,18 +84,19 @@ private void doFilter(HttpServletRequest request, HttpServletResponse response,
return;
}

// 토큰이 DB에 저장되어 있는지 확인
Boolean isExist = refreshRepository.existsByRefresh(refresh);
if (!isExist) {
String username = jwtUtil.getUsername(refresh);

// response status code
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
//DB에 저장되어 있는지 확인
String redisRefresh = redisClient.getValue(username);
if (StringUtils.isEmpty(redisRefresh) || !refresh.equals(redisRefresh)) {

//response body
throw new GeneralException(ErrorStatus.INVALID_REFRESH_TOKEN);
}

// 로그아웃 진행
// Refresh 토큰 DB에서 제거
refreshRepository.deleteByRefresh(refresh);
redisClient.deleteValue(username);

// 쿠키에 저장되어 있는 Refresh 토큰 null값 처리
Cookie cookie = new Cookie("refresh", null);
Expand Down
7 changes: 4 additions & 3 deletions src/main/java/com/drinkeg/drinkeg/jwt/LoginFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.drinkeg.drinkeg.dto.securityDTO.oauth2DTO.LoginResponse;
import com.drinkeg.drinkeg.dto.securityDTO.jwtDTO.PrincipalDetail;
import com.drinkeg.drinkeg.redis.RedisClient;
import com.drinkeg.drinkeg.service.loginService.TokenService;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
Expand All @@ -28,6 +29,7 @@ public class LoginFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final JWTUtil jwtUtil;
private final TokenService tokenService;
private final RedisClient redisClient;

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
Expand Down Expand Up @@ -82,9 +84,8 @@ protected void successfulAuthentication(HttpServletRequest request, HttpServletR
response.addCookie(tokenService.createCookie("refreshToken", refreshToken));
response.setStatus(HttpStatus.OK.value());

// refresh 토큰 저장
tokenService.addRefreshToken(username, refreshToken, 864000000L);

// redis에 refresh 토큰 저장
redisClient.setValue(username, refreshToken, 864000000L);

response.sendRedirect("http://localhost:8080/maindy");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import com.drinkeg.drinkeg.dto.securityDTO.jwtDTO.PrincipalDetail;
import com.drinkeg.drinkeg.jwt.JWTUtil;
import com.drinkeg.drinkeg.redis.RedisClient;
import com.drinkeg.drinkeg.service.loginService.TokenService;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -24,6 +25,7 @@ public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler

private final JWTUtil jwtUtil;
private final TokenService tokenService;
private final RedisClient redisClient;

// CustomSuccessHandler(JWTUtil jwtUtil) {

Expand Down Expand Up @@ -59,9 +61,8 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo
response.addCookie(tokenService.createCookie("refreshToken", refreshToken));
response.setStatus(HttpStatus.OK.value());

// refresh 토큰 저장
tokenService.addRefreshToken(username, refreshToken, 864000000L);

// redis에 refresh 토큰 저장
redisClient.setValue(username, refreshToken, 864000000L);

response.sendRedirect("http://localhost:8080/maindy");

Expand Down
Loading

0 comments on commit c5d9546

Please sign in to comment.