Skip to content

Commit

Permalink
Merge pull request #41 from TogetherWithOcean-TWO/SCRUM-71-logout
Browse files Browse the repository at this point in the history
SCRUM-71 로그아웃 api 구현
  • Loading branch information
jin20fd authored Jul 23, 2024
2 parents 7a46394 + 559e04c commit 69a23aa
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.togetherwithocean.TWO.Jwt;

import java.io.IOException;

import io.jsonwebtoken.ExpiredJwtException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
Expand All @@ -25,12 +27,28 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
String refreshToken = jwtProvider.resolveRefreshToken((HttpServletRequest) request);
System.out.println("액세스 토큰: " + accessToken);
System.out.println("리프레쉬 토큰: " + refreshToken);

// 2. validateToken으로 토큰 유효성 검사
if (accessToken != null && jwtProvider.validateAccessToken(accessToken)) {
// 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장
Authentication authentication = jwtProvider.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
try {
// 2. validateToken으로 토큰 유효성 검사
if (accessToken != null && jwtProvider.validateAccessToken(accessToken)) {
// 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장
Authentication authentication = jwtProvider.getAuthentication(accessToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
catch (ExpiredJwtException e) {
if (refreshToken != null && jwtProvider.refreshTokenValidation(refreshToken, e.getClaims().getSubject())) {
String email = e.getClaims().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));
}
else {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT Token");
return;
}
}
else if (refreshToken != null && jwtProvider.refreshTokenValidation(refreshToken, jwtProvider.parseClaims(accessToken).getSubject())) {
System.out.println("액세스 토큰 만료");
Expand All @@ -45,9 +63,7 @@ else if (refreshToken != null && jwtProvider.refreshTokenValidation(refreshToken
System.out.println(newAccessToken);
System.out.println(newRefreshToken);
}
else
System.out.println("유효하지 않은 토큰");

chain.doFilter(request, response); // 다음 필터로 넘어가거나, 요청 처리 진행
}
}
}
31 changes: 29 additions & 2 deletions TWO/src/main/java/com/togetherwithocean/TWO/Jwt/JwtProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public class JwtProvider {

private static final String BEARER_TYPE = "Bearer";
private final String PREFIX_REFRESH = "REFRESH:";
private final String PREFIX_LOGOUT = "LOGOUT:";
private final String PREFIX_LOGOUT_REFRESH = "LOGOUT_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;
Expand Down Expand Up @@ -131,6 +133,17 @@ public Authentication getAuthentication(String accessToken) {
// 토큰 정보를 검증하는 메서드
public boolean validateAccessToken(String token) {
try {
String email = parseClaims(token).getSubject();
String logoutToken = redisTemplate.opsForValue().get(PREFIX_LOGOUT + email);

System.out.println("제공된 토큰: " + token);
System.out.println("로그아웃된 토큰: " + logoutToken);

if (token.equals(logoutToken)) {
System.out.println("로그아웃 처리된 액세스 토큰입니다.");
return false;
}

Jwts.parserBuilder()
.setSigningKey(key)
.build()
Expand All @@ -140,6 +153,7 @@ public boolean validateAccessToken(String token) {
log.info("Invalid JWT Token", e);
} catch (ExpiredJwtException e) {
log.info("Expired JWT Token", e);
throw e;
} catch (UnsupportedJwtException e) {
log.info("Unsupported JWT Token", e);
} catch (IllegalArgumentException e) {
Expand All @@ -153,13 +167,21 @@ public boolean validateAccessToken(String token) {
// db에 저장한다는 것이 jwt token을 사용한다는 강점을 상쇄시킨다.
// db 보다는 redis를 사용하는 것이 더욱 좋다. (in-memory db기 때문에 조회속도가 빠르고 주기적으로 삭제하는 기능이 기본적으로 존재합니다.)
public boolean refreshTokenValidation(String token, String email) {
String searchRefresh = redisTemplate.opsForValue().get(PREFIX_REFRESH + email);
String logoutRefresh = redisTemplate.opsForValue().get(PREFIX_LOGOUT_REFRESH + email);

System.out.println("제공된 토큰: " + token);
System.out.println("로그아웃된 토큰: " + logoutRefresh);

if (token.equals(logoutRefresh)) {
System.out.println("로그아웃 처리된 리프레쉬 토큰입니다.");
return false;
}

String searchRefresh = redisTemplate.opsForValue().get(PREFIX_REFRESH + email);
// DB에 저장한 토큰 비교
return token.equals(searchRefresh);
}


// accessToken
public Claims parseClaims(String accessToken) {
try {
Expand All @@ -172,4 +194,9 @@ public Claims parseClaims(String accessToken) {
return e.getClaims();
}
}

public boolean confirmLogout(String accessToken, String email) {
String logoutToken = redisTemplate.opsForValue().get(PREFIX_LOGOUT + email);
return accessToken.equals(logoutToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.togetherwithocean.TWO.Member.Repository.MemberRepository;
import com.togetherwithocean.TWO.Ranking.Service.RankingService;
import com.togetherwithocean.TWO.Stat.Service.StatService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import com.togetherwithocean.TWO.Jwt.TokenDto;
import com.togetherwithocean.TWO.Member.Domain.Member;
Expand Down Expand Up @@ -136,6 +137,14 @@ public ResponseEntity<PostSignInRes> sign_in(@RequestBody PostSignInReq postSign
return ResponseEntity.status(HttpStatus.OK).body(memberService.setSignInInfo(memberRes, token));
}

@PostMapping("/logout")
public ResponseEntity<String> logoutMember(Authentication principal, HttpServletRequest request) {
if (principal == null)
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
memberService.logoutMember(request, principal.getName());
return ResponseEntity.status(HttpStatus.OK).body("로그아웃 처리되었습니다.");
}

@GetMapping("/main-info")
public ResponseEntity<MainInfoRes> viewMainInfo(Authentication principal) {
if (principal == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.togetherwithocean.TWO.Member.Service;

import com.togetherwithocean.TWO.Badge.Domain.Badge;
import org.springframework.data.redis.core.StringRedisTemplate;
import com.togetherwithocean.TWO.Badge.Service.BadgeService;
import com.togetherwithocean.TWO.Item.Service.ItemSerivce;
import com.togetherwithocean.TWO.Jwt.JwtProvider;
Expand All @@ -16,6 +17,7 @@
import com.togetherwithocean.TWO.Stat.Domain.Stat;
import com.togetherwithocean.TWO.Stat.Repository.StatRepository;
import com.togetherwithocean.TWO.Stat.Service.StatService;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
Expand All @@ -24,7 +26,9 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;
import java.time.LocalDate;
import java.util.Date;

@Service
@RequiredArgsConstructor
Expand All @@ -35,7 +39,10 @@ public class MemberService {
private final BadgeService badgeService;
private final ItemSerivce itemSerivce;
private final JwtProvider jwtProvider;
private final StatService statService;
private final StringRedisTemplate redisTemplate;
private final String PREFIX_LOGOUT = "LOGOUT:";
private final String PREFIX_LOGOUT_REFRESH = "LOGOUT_REFRESH:";

private final Logger log = LoggerFactory.getLogger(getClass());


Expand Down Expand Up @@ -132,6 +139,18 @@ public PostSignInRes setSignInInfo(MemberRes memberRes, TokenDto token) {
return new PostSignInRes(memberRes, token);
}

public void logoutMember(HttpServletRequest request, String email) {
String accessToken = jwtProvider.resolveAccessToken(request);
String refreshToken = jwtProvider.resolveRefreshToken(request);
Date accessExpiration = jwtProvider.parseClaims(accessToken).getExpiration();
Date refreshExpiration = jwtProvider.parseClaims(refreshToken).getExpiration();

redisTemplate.opsForValue()
.set(PREFIX_LOGOUT + email, accessToken, Duration.ofSeconds(accessExpiration.getTime() - new Date().getTime()));
redisTemplate.opsForValue()
.set(PREFIX_LOGOUT_REFRESH + email, refreshToken, Duration.ofSeconds(refreshExpiration.getTime() - new Date().getTime()));
}

public MainInfoRes getMainInfo(String email) {
Member member = memberRepository.findMemberByEmail(email);

Expand Down

0 comments on commit 69a23aa

Please sign in to comment.