Skip to content

Commit

Permalink
Merge pull request #39 from FluffySnowTeam/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
GwanHwiJ authored Dec 27, 2023
2 parents 0131fdf + 6c37dc3 commit ab4fc3e
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 67 deletions.
93 changes: 85 additions & 8 deletions src/main/java/fluffysnow/idearly/config/jwt/JwtFilter.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package fluffysnow.idearly.config.jwt;

import fluffysnow.idearly.common.exception.UnauthorizedException;
import fluffysnow.idearly.member.dto.TokenDto;
import fluffysnow.idearly.member.dto.TokenRequestDto;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
Expand All @@ -17,42 +20,116 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

@RequiredArgsConstructor
@Slf4j
public class JwtFilter extends OncePerRequestFilter {

public static final String AUTHORIZATION_ACCESS = "accessToken";

private final JwtProvider jwtProvider;

private final RedisTemplate<String, String> redisTemplate;

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
String accessToken = resolveToken(request);
String cookieAccessToken = resolveToken(request, "accessToken");

log.info("jwt 토큰 = {} {}", accessToken, request.getRequestURI());
Authentication jwtProviderAuthentication = jwtProvider.getAuthentication(cookieAccessToken);

if (StringUtils.hasText(accessToken) && jwtProvider.validateToken(accessToken)) {
String refreshToken = redisTemplate.opsForValue().get("RT:" + jwtProviderAuthentication.getName());

String cookieRefreshToken = resolveToken(request, "refreshToken");

boolean accessTokenIsExpired = jwtProvider.validateAccessToken(cookieAccessToken);

if (StringUtils.hasText(refreshToken) && accessTokenIsExpired) {

if (cookieRefreshToken.equals(refreshToken)) {

log.info("토큰 재발급 중");

TokenRequestDto tokenRequestDto = new TokenRequestDto(cookieAccessToken, refreshToken);

TokenDto reissueToken = reissue(tokenRequestDto);

setReissueCookie(reissueToken, request, response);

cookieAccessToken = reissueToken.getAccessToken();
}
}

log.info("jwt 토큰 = {} {}", cookieAccessToken, request.getRequestURI());

if (StringUtils.hasText(cookieAccessToken) && jwtProvider.validateToken(cookieAccessToken)) {
/*1. Redis 에 해당 accessToken logout 여부 확인 */
String isLogout = redisTemplate.opsForValue().get(accessToken);
String isLogout = redisTemplate.opsForValue().get(cookieAccessToken);
if (ObjectUtils.isEmpty(isLogout)) {
/*2. 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext 에 저장 */
Authentication authentication = jwtProvider.getAuthentication(accessToken);
Authentication authentication = jwtProvider.getAuthentication(cookieAccessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}

filterChain.doFilter(request, response);
}

public String resolveToken(HttpServletRequest request) {
public String resolveToken(HttpServletRequest request, String cookieName) {
return Optional.ofNullable(request.getCookies())
.flatMap(cookies -> Arrays.stream(cookies)
.filter(cookie -> AUTHORIZATION_ACCESS.equals(cookie.getName()))
.filter(cookie -> cookieName.equals(cookie.getName()))
.findFirst()
.map(Cookie::getValue))
.orElse(null);
}

private void setReissueCookie(TokenDto tokenDto, HttpServletRequest request, HttpServletResponse response) {

Cookie accessTokenCookie = new Cookie("accessToken", tokenDto.getAccessToken());
accessTokenCookie.setPath("/");
accessTokenCookie.setMaxAge(60 * 60 * 3); // 액세스 토큰: 3시간
accessTokenCookie.setSecure(true);
accessTokenCookie.setHttpOnly(true);

Cookie refreshTokenCookie = new Cookie("refreshToken", tokenDto.getRefreshToken());
refreshTokenCookie.setPath("/");
refreshTokenCookie.setMaxAge(60 * 60 * 3); // 리프레쉬 토큰: 3시간
refreshTokenCookie.setSecure(true);
refreshTokenCookie.setHttpOnly(true);

response.addCookie(accessTokenCookie);
response.addCookie(refreshTokenCookie);
}

private TokenDto reissue(TokenRequestDto dto) {
if (!jwtProvider.validateToken(dto.getRefreshToken())) {
log.info("FAIL"); //에러 처리
}

Authentication authentication = jwtProvider.getAuthentication(dto.getAccessToken());

String refreshToken = redisTemplate.opsForValue().get("RT:" + authentication.getName());

log.info("RefreshToken: {}", refreshToken);

if (ObjectUtils.isEmpty(refreshToken)) {
log.info("잘못된 요청입니다."); //에러 처리
throw new UnauthorizedException("인증되지 않은 사용자의 요청입니다.");
}

if (!refreshToken.equals(dto.getRefreshToken())) {
log.info("Refresh Token 정보 불일치"); //에러 처리
throw new UnauthorizedException("인증되지 않은 사용자의 요청입니다.");
}

TokenDto tokenDto = jwtProvider.createTokenDto(authentication);

redisTemplate.opsForValue()
.set("RT:" + authentication.getName(), tokenDto.getRefreshToken(),
tokenDto.getRefreshTokenExpiresIn(), TimeUnit.MILLISECONDS);

log.info("토큰 재발급 완료");

return tokenDto;
}
}
28 changes: 23 additions & 5 deletions src/main/java/fluffysnow/idearly/config/jwt/JwtProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@
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.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.stream.Collectors;

Expand All @@ -31,9 +28,9 @@ public class JwtProvider {

private static final String BEARER_TYPE = "bearer"; // token type

private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 7; // 7일
private static final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 30; // 30분

private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 24 * 7; // 7일
private static final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60 * 2; // 2시간

private final Key private_key;

Expand Down Expand Up @@ -146,6 +143,27 @@ public boolean validateToken(String token) {
return false;
}

public boolean validateAccessToken(String token) {
try {
log.info("token: {}", token);
Jwts.parserBuilder()
.setSigningKey(private_key)
.build()
.parseClaimsJws(token);
return false;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("잘못된 JWT 서명입니다.");
} catch (ExpiredJwtException e) {
log.info("만료된 JWT 토큰입니다.");
return true;
} catch (UnsupportedJwtException e) {
log.info("지원되지 않는 JWT 토큰입니다.");
} catch (IllegalArgumentException e) {
log.info("JWT 토큰이 잘못되었습니다.");
}
return false;
}

public Long getExpiration(String accessToken) {
Date expiration = Jwts.parserBuilder().setSigningKey(private_key)
.build().parseClaimsJws(accessToken).getBody().getExpiration();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import fluffysnow.idearly.common.ApiResponse;
import fluffysnow.idearly.config.CustomUserDetails;
import fluffysnow.idearly.config.jwt.JwtProvider;
import fluffysnow.idearly.member.domain.Member;
import fluffysnow.idearly.member.dto.*;
import fluffysnow.idearly.member.service.MemberService;
Expand All @@ -20,6 +21,8 @@
@Slf4j
public class MemberController {

private final JwtProvider jwtProvider;

private final MemberService memberService;

@GetMapping
Expand All @@ -44,13 +47,13 @@ public ApiResponse<LoginResponseDto> login(@RequestBody LoginRequestDto loginReq
LoginResponseDto loginResponseDto = memberService.login(loginRequestDto);
Cookie accessTokenCookie = new Cookie("accessToken", loginResponseDto.getAccessToken());
accessTokenCookie.setPath("/");
accessTokenCookie.setMaxAge(60 * 60 * 24 * 7); // 액세스 토큰: 7일
accessTokenCookie.setMaxAge(60 * 60 * 3); // 액세스 토큰: 3시간
accessTokenCookie.setSecure(true);
accessTokenCookie.setHttpOnly(true);

Cookie refreshTokenCookie = new Cookie("refreshToken", loginResponseDto.getRefreshToken());
refreshTokenCookie.setPath("/");
refreshTokenCookie.setMaxAge(60 * 60 * 24 * 7); // 리프레쉬 토큰: 7일
refreshTokenCookie.setMaxAge(60 * 60 * 3); // 리프레쉬 토큰: 3시간
refreshTokenCookie.setSecure(true);
refreshTokenCookie.setHttpOnly(true);

Expand All @@ -60,27 +63,6 @@ public ApiResponse<LoginResponseDto> login(@RequestBody LoginRequestDto loginReq
return ApiResponse.ok(loginResponseDto);
}

@PostMapping("/reissue")
public ApiResponse<Void> reissue(@CookieValue("accessToken") String accessToken, @CookieValue("refreshToken") String refreshToken, HttpServletRequest request, HttpServletResponse response) {
TokenRequestDto tokenRequestDto = new TokenRequestDto(accessToken, refreshToken);
TokenDto tokenDto = memberService.reissue(tokenRequestDto);
Cookie accessTokenCookie = new Cookie("accessToken", tokenDto.getAccessToken());
accessTokenCookie.setPath("/");
accessTokenCookie.setMaxAge(60 * 60 * 24 * 7); // 액세스 토큰: 7일
accessTokenCookie.setSecure(true);
accessTokenCookie.setHttpOnly(true);

Cookie refreshTokenCookie = new Cookie("refreshToken", tokenDto.getRefreshToken());
refreshTokenCookie.setPath("/");
refreshTokenCookie.setMaxAge(60 * 60 * 24 * 7); // 리프레쉬 토큰: 7일
refreshTokenCookie.setSecure(true);
refreshTokenCookie.setHttpOnly(true);

response.addCookie(accessTokenCookie);
response.addCookie(refreshTokenCookie);
return ApiResponse.ok(null);
}

@PostMapping("/logout")
public ApiResponse<Void> logout(@CookieValue("accessToken") String accessToken, @CookieValue("refreshToken") String refreshToken, HttpServletRequest request, HttpServletResponse response) {
TokenRequestDto tokenRequestDto = new TokenRequestDto(accessToken, refreshToken);
Expand Down
31 changes: 0 additions & 31 deletions src/main/java/fluffysnow/idearly/member/service/MemberService.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.util.Optional;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -69,36 +68,6 @@ public LoginResponseDto login(LoginRequestDto loginRequestDto) {
return LoginResponseDto.of(principal, tokenDto);
}

public TokenDto reissue(TokenRequestDto dto) {
if (!jwtProvider.validateToken(dto.getRefreshToken())) {
log.info("FAIL"); //에러 처리
}

Authentication authentication = jwtProvider.getAuthentication(dto.getAccessToken());

String refreshToken = redisTemplate.opsForValue().get("RT:" + authentication.getName());

log.info("RefreshToken: {}", refreshToken);

if (ObjectUtils.isEmpty(refreshToken)) {
log.info("잘못된 요청입니다."); //에러 처리
throw new UnauthorizedException("인증되지 않은 사용자의 요청입니다.");
}

if (!refreshToken.equals(dto.getRefreshToken())) {
log.info("Refresh Token 정보 불일치"); //에러 처리
throw new UnauthorizedException("인증되지 않은 사용자의 요청입니다.");
}

TokenDto tokenDto = jwtProvider.createTokenDto(authentication);

redisTemplate.opsForValue()
.set("RT:" + authentication.getName(), tokenDto.getRefreshToken(),
tokenDto.getRefreshTokenExpiresIn(), TimeUnit.MILLISECONDS);

return tokenDto;
}

public void logout(TokenRequestDto dto) {
if (!jwtProvider.validateToken(dto.getAccessToken())) {
log.info("잘못된 요청입니다."); //에러 처리
Expand Down

0 comments on commit ab4fc3e

Please # to comment.