Skip to content

Commit

Permalink
Merge pull request #39 from TogetherWithOcean-TWO/SCRUM-42-refreshToken
Browse files Browse the repository at this point in the history
SCRUM-42 refreshToken -> accessToken 재발급
  • Loading branch information
jin20fd authored Jul 21, 2024
2 parents 01420c2 + a280cf4 commit 7a46394
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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;
Expand All @@ -20,18 +21,33 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
throws IOException, ServletException {

// 1. Request Header에서 JWT 토큰 추출
String token = jwtProvider.resolveToken((HttpServletRequest) request);
System.out.println(token);
String accessToken = jwtProvider.resolveAccessToken((HttpServletRequest) request);
String refreshToken = jwtProvider.resolveRefreshToken((HttpServletRequest) request);
System.out.println("액세스 토큰: " + accessToken);
System.out.println("리프레쉬 토큰: " + refreshToken);

// 2. validateToken으로 토큰 유효성 검사
if (token != null && jwtProvider.validateToken(token)) {
if (accessToken != null && jwtProvider.validateAccessToken(accessToken)) {
// 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장
Authentication authentication = jwtProvider.getAuthentication(token);
Authentication authentication = jwtProvider.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
else {
System.out.println("유효하지 않은 토큰");
else if (refreshToken != null && jwtProvider.refreshTokenValidation(refreshToken, jwtProvider.parseClaims(accessToken).getSubject())) {
System.out.println("액세스 토큰 만료");
String email = jwtProvider.parseClaims(accessToken).getSubject();
String newAccessToken = jwtProvider.createAccessToken(email);
String newRefreshToken = jwtProvider.createRefreshToken(email);
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("AccessToken", newAccessToken);
httpResponse.setHeader("RefreshToken", newRefreshToken);
SecurityContextHolder.getContext().setAuthentication(jwtProvider.getAuthentication(newAccessToken));
System.out.println("액세스 토큰 재발급");
System.out.println(newAccessToken);
System.out.println(newRefreshToken);
}
else
System.out.println("유효하지 않은 토큰");

chain.doFilter(request, response); // 다음 필터로 넘어가거나, 요청 처리 진행
}
}
85 changes: 66 additions & 19 deletions TWO/src/main/java/com/togetherwithocean/TWO/Jwt/JwtProvider.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.togetherwithocean.TWO.Jwt;

import com.togetherwithocean.TWO.Member.Domain.Member;
import com.togetherwithocean.TWO.Member.Repository.MemberRepository;
import org.springframework.data.redis.core.StringRedisTemplate;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
Expand All @@ -16,7 +18,9 @@
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;


import java.security.Key;
import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
Expand All @@ -29,34 +33,28 @@
public class JwtProvider {

private static final String BEARER_TYPE = "Bearer";
private final String PREFIX_REFRESH = "REFRESH:";
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 final StringRedisTemplate redisTemplate;
private final MemberRepository memberRepository;
private final Key key;

public JwtProvider(@Value("${jwt.secret}") String secretKey) {
public JwtProvider(@Value("${jwt.secret}") String secretKey, StringRedisTemplate redisTemplate,
MemberRepository memberRepository) {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
this.redisTemplate = redisTemplate;
this.memberRepository = memberRepository;
}

public TokenDto generateToken(Member loginMember) {

long now = (new Date()).getTime();

// Access Token 생성
Date accessTokenExpiresIn = new Date(now + ACCESS_TOKEN_EXPIRE_TIME);

String accessToken = Jwts.builder()
.setSubject(loginMember.getEmail()) // payload "sub": "name"
.claim("auth", loginMember.getAuthority())
.setExpiration(accessTokenExpiresIn) // payload "exp": 151621022 (ex)
.signWith(key, SignatureAlgorithm.HS256) // header "alg": "HS256"
.compact();
String accessToken = createAccessToken(loginMember.getEmail());

// Refresh Token 생성
String refreshToken = Jwts.builder()
.setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
String refreshToken = createRefreshToken(loginMember.getEmail());

return TokenDto.builder()
.grantType(BEARER_TYPE)
Expand All @@ -66,15 +64,52 @@ public TokenDto generateToken(Member loginMember) {
.build();
}

// Request Header에서 토큰 정보 추출
public String resolveToken(HttpServletRequest request) {
public String createAccessToken(String email) {
long now = (new Date()).getTime();
Member member = memberRepository.findMemberByEmail(email);

return Jwts.builder()
.setSubject(member.getEmail()) // payload "sub": "name"
.claim("auth", member.getAuthority())
.setExpiration(new Date(now + ACCESS_TOKEN_EXPIRE_TIME)) // payload "exp": 151621022 (ex)
.signWith(key, SignatureAlgorithm.HS256) // header "alg": "HS256"
.compact();
}

public String createRefreshToken(String email) {
long now = (new Date()).getTime();
Member loginMember = memberRepository.findMemberByEmail(email);

String refreshToken = Jwts.builder()
.setExpiration(new Date(now + REFRESH_TOKEN_EXPIRE_TIME))
.signWith(key, SignatureAlgorithm.HS256)
.compact();

// redis에 Refresh Token 저장
redisTemplate.opsForValue()
.set(PREFIX_REFRESH + loginMember.getEmail(), refreshToken, Duration.ofSeconds(REFRESH_TOKEN_EXPIRE_TIME));

return refreshToken;
}

// Request Header에서 액세스 토큰 정보 추출
public String resolveAccessToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer")) {
return bearerToken.substring(7);
}
return null;
}

// Request Header에서 리프레쉬 토큰 정보 추출
public String resolveRefreshToken(HttpServletRequest request) {
String refreshToken = request.getHeader("RefreshToken");
if (StringUtils.hasText(refreshToken)) {
return refreshToken;
}
return null;
}

// Jwt 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 메서드
public Authentication getAuthentication(String accessToken) {
Claims claims = parseClaims(accessToken);
Expand All @@ -94,7 +129,7 @@ public Authentication getAuthentication(String accessToken) {
}

// 토큰 정보를 검증하는 메서드
public boolean validateToken(String token) {
public boolean validateAccessToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(key)
Expand All @@ -113,8 +148,20 @@ public boolean validateToken(String token) {
return false;
}

// refreshToken 토큰 검증
// db에 저장되어 있는 token과 비교
// db에 저장한다는 것이 jwt token을 사용한다는 강점을 상쇄시킨다.
// db 보다는 redis를 사용하는 것이 더욱 좋다. (in-memory db기 때문에 조회속도가 빠르고 주기적으로 삭제하는 기능이 기본적으로 존재합니다.)
public boolean refreshTokenValidation(String token, String email) {
String searchRefresh = redisTemplate.opsForValue().get(PREFIX_REFRESH + email);

// DB에 저장한 토큰 비교
return token.equals(searchRefresh);
}


// accessToken
private Claims parseClaims(String accessToken) {
public Claims parseClaims(String accessToken) {
try {
return Jwts.parserBuilder()
.setSigningKey(key)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// REST API이므로 basic auth 및 csrf 보안을 사용하지 않음
http.httpBasic(AbstractHttpConfigurer::disable);
http.csrf(AbstractHttpConfigurer::disable);

// JWT를 사용하기 때문에 세션을 사용하지 않음
http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.togetherwithocean.TWO.Member.Service;

import com.togetherwithocean.TWO.Badge.Domain.Badge;
import com.togetherwithocean.TWO.Badge.Repository.BadgeRepository;
import com.togetherwithocean.TWO.Badge.Service.BadgeService;
import com.togetherwithocean.TWO.Item.Service.ItemSerivce;
import com.togetherwithocean.TWO.Jwt.JwtProvider;
Expand Down

0 comments on commit 7a46394

Please sign in to comment.