Feat: 토큰 생성 - S11P21S002-16

This commit is contained in:
김용수 2024-08-28 22:00:34 +09:00
parent d788a111c4
commit c926b60d82
14 changed files with 212 additions and 117 deletions

View File

@ -46,7 +46,7 @@ dependencies {
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// GJson // GJson
implementation 'com.google.code.gson:gson:2.7' implementation 'com.google.code.gson:gson:2.8.9'
// OAuth // OAuth
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

View File

@ -1,27 +1,78 @@
package com.worlabel.domain.auth.controller; package com.worlabel.domain.auth.controller;
import com.worlabel.domain.auth.entity.dto.JwtToken;
import com.worlabel.domain.auth.service.AuthService;
import com.worlabel.domain.auth.service.JwtTokenService;
import com.worlabel.global.annotation.CurrentUser; import com.worlabel.global.annotation.CurrentUser;
import com.worlabel.global.exception.CustomException;
import com.worlabel.global.exception.ErrorCode;
import com.worlabel.global.response.SuccessResponse; import com.worlabel.global.response.SuccessResponse;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
@Slf4j @Slf4j
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/api/auth") @RequestMapping("/api/auth")
public class AuthController { public class AuthController {
// TODO: 리이슈 처리 private final AuthService authService;
@GetMapping("/")
public String login() { @Value("${auth.refreshTokenExpiry}")
return "로그인 성공"; long refreshExpiry;
// TODO: 리이슈 처리, 액세스 어떻게 받았는지 물어보기
@PostMapping("/reissue")
public SuccessResponse<Void> reissue(
HttpServletRequest request,
HttpServletResponse response
) {
log.info("reissue");
String refresh = parseRefreshCookie(request);
try {
JwtToken newToken = authService.reissue(refresh);
response.addCookie(createCookie(newToken.getRefreshToken()));
return SuccessResponse.empty();
} catch (CustomException e) {
throw e;
} catch (Exception e) {
throw new CustomException(ErrorCode.INVALID_TOKEN, e.getMessage());
}
}
private Cookie createCookie(String value) {
Cookie cookie = new Cookie("refreshToken", value);
cookie.setMaxAge((int) (refreshExpiry / 1000));
cookie.setPath("/");
cookie.setHttpOnly(true);
// cookie.setSecure(true); // 배포 HTTPS에서 사용
return cookie;
} }
@GetMapping("/user-info") @GetMapping("/user-info")
public SuccessResponse<Integer> getMemberInfo(@CurrentUser Integer currentMember){ public SuccessResponse<Integer> getMemberInfo(@CurrentUser Integer currentMember){
return SuccessResponse.of(currentMember); return SuccessResponse.of(currentMember);
} }
private static String parseRefreshCookie(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if(cookies != null) {
return Arrays.stream(cookies)
.filter(cookie -> "refresh".equals(cookie.getName()))
.findFirst()
.map(Cookie::getValue)
.orElse(null);
}
return null;
}
} }

View File

@ -0,0 +1,16 @@
package com.worlabel.domain.auth.entity.dto;
import lombok.Getter;
@Getter
public class LoginMember {
private int id;
private String email;
public static LoginMember of(int id, String email) {
LoginMember member = new LoginMember();
member.id = id;
member.email = email;
return member;
}
}

View File

@ -1,4 +1,4 @@
package com.worlabel.global.handler; package com.worlabel.domain.auth.handler;
import com.worlabel.global.exception.CustomException; import com.worlabel.global.exception.CustomException;
import com.worlabel.global.exception.ErrorCode; import com.worlabel.global.exception.ErrorCode;
@ -21,7 +21,7 @@ public class CustomAuthenticationDeniedHandler implements AccessDeniedHandler {
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
log.debug("오류 : {}", request.getAttribute("error-message")); log.debug("오류 : {}", request.getAttribute("error-message"));
response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setStatus(HttpServletResponse.SC_FORBIDDEN);
CustomException exception = new CustomException(ErrorCode.Access_DENIED); CustomException exception = new CustomException(ErrorCode.ACCESS_DENIED);
ErrorResponse errorResponse = new ErrorResponse(exception, request.getAttribute("error-message").toString()); ErrorResponse errorResponse = new ErrorResponse(exception, request.getAttribute("error-message").toString());
response.setContentType("application/json;charset=utf-8"); response.setContentType("application/json;charset=utf-8");
response.getWriter().write(errorResponse.toJson()); response.getWriter().write(errorResponse.toJson());

View File

@ -1,4 +1,4 @@
package com.worlabel.global.handler; package com.worlabel.domain.auth.handler;
import com.worlabel.global.exception.CustomException; import com.worlabel.global.exception.CustomException;
import com.worlabel.global.exception.ErrorCode; import com.worlabel.global.exception.ErrorCode;

View File

@ -1,9 +1,8 @@
package com.worlabel.global.handler; package com.worlabel.domain.auth.handler;
import com.worlabel.domain.auth.entity.CustomOAuth2User; import com.worlabel.domain.auth.entity.CustomOAuth2User;
import com.worlabel.domain.auth.entity.dto.JwtToken; import com.worlabel.domain.auth.entity.dto.JwtToken;
import com.worlabel.domain.auth.service.JwtTokenService; import com.worlabel.domain.auth.service.JwtTokenService;
import com.worlabel.global.util.JWTUtil;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@ -12,9 +11,9 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponentsBuilder;
import java.io.IOException; import java.io.IOException;
@ -25,6 +24,8 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler
@Value("${frontend.url}") @Value("${frontend.url}")
private String frontEnd; private String frontEnd;
@Value("${auth.refreshTokenExpiry}")
long refreshExpiry;
private final JwtTokenService jwtTokenService; private final JwtTokenService jwtTokenService;
@ -35,23 +36,22 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler
log.debug("로그인 성공 : {}", customOAuth2User); log.debug("로그인 성공 : {}", customOAuth2User);
// JWT 토큰 생성 // JWT 토큰 생성
JwtToken jwtToken = jwtTokenService.generateToken(customOAuth2User.getId()); JwtToken jwtToken = jwtTokenService.generateTokenByOAuth2User(customOAuth2User);
// 헤더에 액세스 토큰 추가 // 쿼리 파라미터로 액세스 토큰 전달
response.setHeader("Authorization", jwtToken.getAccessToken()); String redirectUrl = UriComponentsBuilder.fromUriString(frontEnd + "/redirect/oauth2")
.queryParam("accessToken", jwtToken.getAccessToken())
.toUriString();
// 쿠키에 리프레시 토큰 추가 // 쿠키에 리프레시 토큰 추가
response.addCookie(createCookie(jwtToken.getRefreshToken())); response.addCookie(createCookie(jwtToken.getRefreshToken()));
// 성공 리다이렉트할 URL 설정
String redirectUrl = frontEnd + "/"; // TODO: 적절한 리다이렉트 URL로 수정
getRedirectStrategy().sendRedirect(request, response, redirectUrl); getRedirectStrategy().sendRedirect(request, response, redirectUrl);
} }
private Cookie createCookie(String value) { private Cookie createCookie(String value) {
Cookie cookie = new Cookie("refreshToken", value); Cookie cookie = new Cookie("refreshToken", value);
int refreshTokenExpiryInSeconds = (int) ((System.currentTimeMillis() + jwtTokenService.getRefreshTokenExpiration()) / 1000); // 리프레시 토큰 만료 시간과 일치 cookie.setMaxAge((int) (refreshExpiry / 1000));
cookie.setMaxAge(refreshTokenExpiryInSeconds);
cookie.setPath("/"); cookie.setPath("/");
cookie.setHttpOnly(true); cookie.setHttpOnly(true);
// cookie.setSecure(true); // 배포 HTTPS에서 사용 // cookie.setSecure(true); // 배포 HTTPS에서 사용

View File

@ -0,0 +1,12 @@
package com.worlabel.domain.auth.repository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
@Slf4j
@Repository
@RequiredArgsConstructor
public class AuthCacheRepository {
}

View File

@ -0,0 +1,30 @@
package com.worlabel.domain.auth.service;
import com.worlabel.domain.auth.entity.dto.JwtToken;
import com.worlabel.global.exception.CustomException;
import com.worlabel.global.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.Objects;
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthService {
private final JwtTokenService jwtTokenService;
public JwtToken reissue(String refreshToken) throws Exception {
String username = jwtTokenService.parseUsername(refreshToken);
if(username == null) throw new CustomException(ErrorCode.INVALID_TOKEN);
// TODO: 레디스 토큰 검사
Object redisRefreshToken = null;
if(Objects.equals(refreshToken, redisRefreshToken)){
throw new CustomException(ErrorCode.USER_ALREADY_SIGN_OUT);
}
return jwtTokenService.generateTokenByRefreshToken(refreshToken);
}
}

View File

@ -6,14 +6,21 @@ import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts; import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.MalformedJwtException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.crypto.SecretKey; import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Slf4j
@Service @Service
public class JwtTokenService { public class JwtTokenService {
@ -31,40 +38,68 @@ public class JwtTokenService {
refreshTokenExpiration = refreshExpiry; refreshTokenExpiration = refreshExpiry;
} }
public JwtToken generateToken(int memberId){ public JwtToken generateTokenByOAuth2User(CustomOAuth2User user) {
List<String> authorities = user.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.toList();
return generateToken(user.getName(), user.getId(), authorities);
}
public JwtToken generateTokenByRefreshToken(String refreshToken) throws Exception{
if (!isTokenExpired(refreshToken) && isRefreshToken(refreshToken)) {
Claims claims = parseClaims(refreshToken);
String username = claims.getSubject();
int memberId = claims.get("id", Integer.class);
List<String> authorities = parseAuthorities(refreshToken).stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
return generateToken(username, memberId, authorities);
}
throw new Exception("유효하지 않은 토큰입니다.");
}
private JwtToken generateToken(String username, int memberId, List<String> authorities){
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
Date accessTokenExpire = new Date(now + tokenExpiration); Date accessTokenExpire = new Date(now + tokenExpiration);
Date refreshTokenExpire = new Date(now + refreshTokenExpiration); Date refreshTokenExpire = new Date(now + refreshTokenExpiration);
String accessToken = Jwts.builder() String accessToken = Jwts.builder()
.claim("id",memberId) .subject(username)
.claim("type","access") .claim("type", "access")
.claim("id", memberId)
.claim("authorities", authorities) // 권한 정보 추가
.expiration(accessTokenExpire) .expiration(accessTokenExpire)
.signWith(secretKey) .signWith(secretKey)
.compact(); .compact();
String refreshToken = Jwts.builder() String refreshToken = Jwts.builder()
.claim("id",memberId) .subject(username)
.claim("type","refresh") .claim("type","refresh")
.claim("authorities", memberId)
.claim("authorities", authorities) // 권한 정보 추가
.expiration(refreshTokenExpire) .expiration(refreshTokenExpire)
.signWith(secretKey) .signWith(secretKey)
.compact(); .compact();
log.debug("액세스 발급: {}",accessToken);
return new JwtToken(accessToken, refreshToken); return new JwtToken(accessToken, refreshToken);
} }
public JwtToken generateTokenByRefreshToken(String refreshToken) throws Exception{ public String parseUsername(String token) throws Exception {
if(isTokenExpired(refreshToken) && isRefreshToken(refreshToken)){ return parseClaims(token).getSubject();
return generateToken(Integer.parseInt(refreshToken));
}
throw new Exception("유효하지 않은 토큰입니다.");
} }
public int parseId(String token) throws Exception { public int parseId(String token) throws Exception {
return parseClaims(token).get("id", Integer.class); return parseClaims(token).get("id", Integer.class);
} }
public List<SimpleGrantedAuthority> parseAuthorities(String token) throws Exception {
Claims claims = parseClaims(token);
List<String> authorities = claims.get("authorities", List.class); // JWT에서 권한 정보 추출
return authorities.stream()
.map(SimpleGrantedAuthority::new)
.toList();
}
// 토큰 만료 여부 확인 // 토큰 만료 여부 확인
public boolean isTokenExpired(String token){ public boolean isTokenExpired(String token){
try{ try{
@ -88,17 +123,15 @@ public class JwtTokenService {
private boolean isTokenType(String token, String expectedType) { private boolean isTokenType(String token, String expectedType) {
try { try {
Claims claims = parseClaims(token); Claims claims = parseClaims(token);
log.debug("claims : {}",claims);
String tokenType = claims.get("type", String.class); String tokenType = claims.get("type", String.class);
return expectedType.equals(tokenType); return expectedType.equals(tokenType);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
return false; return false;
} }
} }
public long getRefreshTokenExpiration(){
return refreshTokenExpiration;
}
private Claims parseClaims(String token) throws Exception { private Claims parseClaims(String token) throws Exception {
String message; String message;
try{ try{
@ -118,5 +151,4 @@ public class JwtTokenService {
} }
throw new Exception(message); throw new Exception(message);
} }
} }

View File

@ -2,9 +2,9 @@ package com.worlabel.global.config;
import com.worlabel.domain.auth.service.CustomOAuth2UserService; import com.worlabel.domain.auth.service.CustomOAuth2UserService;
import com.worlabel.global.filter.JwtAuthenticationFilter; import com.worlabel.global.filter.JwtAuthenticationFilter;
import com.worlabel.global.handler.CustomAuthenticationDeniedHandler; import com.worlabel.domain.auth.handler.CustomAuthenticationDeniedHandler;
import com.worlabel.global.handler.CustomAuthenticationEntryPoint; import com.worlabel.domain.auth.handler.CustomAuthenticationEntryPoint;
import com.worlabel.global.handler.OAuth2SuccessHandler; import com.worlabel.domain.auth.handler.OAuth2SuccessHandler;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@ -71,7 +71,7 @@ public class SecurityConfig {
http http
.oauth2Login(oauth2 -> oauth2 .oauth2Login(oauth2 -> oauth2
.authorizationEndpoint(authorization -> authorization.baseUri("/api/login/oauth2/authorization")) .authorizationEndpoint(authorization -> authorization.baseUri("/api/login/oauth2/authorization"))
.redirectionEndpoint(redirection -> redirection .baseUri("/api/login/oauth2/code/*")) .redirectionEndpoint(redirection -> redirection.baseUri("/api/login/oauth2/code/*"))
.userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService))
.successHandler(oAuth2SuccessHandler) .successHandler(oAuth2SuccessHandler)
); );
@ -102,3 +102,8 @@ public class SecurityConfig {
return source; return source;
} }
} }
/*
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ5czAxMDcxODUxNjUxQGdtYWlsLmNvbSIsInR5cGUiOiJhY2Nlc3MiLCJhdXRob3JpdGllcyI6W3siYXV0aG9yaXR5IjoiVVNFUiJ9XSwiZXhwIjoxNzI0ODU1NzQ2fQ.tIo9e40nY1KjhBwYcw0BG18Q9qeTYAoXefezYM9YQiY
*/

View File

@ -22,9 +22,11 @@ public enum ErrorCode {
REFRESH_TOKEN_EXPIRED(HttpStatus.BAD_REQUEST, 2003, "리프레시 토큰이 만료되었습니다."), REFRESH_TOKEN_EXPIRED(HttpStatus.BAD_REQUEST, 2003, "리프레시 토큰이 만료되었습니다."),
INVALID_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, 2004, "유효하지 않은 리프레시 토큰입니다."), INVALID_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, 2004, "유효하지 않은 리프레시 토큰입니다."),
UNAUTHORIZED(HttpStatus.UNAUTHORIZED, 2005, "인증에 실패하였습니다."), UNAUTHORIZED(HttpStatus.UNAUTHORIZED, 2005, "인증에 실패하였습니다."),
Access_DENIED(HttpStatus.FORBIDDEN, 2006, "접근 권한이 없습니다."), ACCESS_DENIED(HttpStatus.FORBIDDEN, 2006, "접근 권한이 없습니다."),
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, 2000, "올바르지 않는 인증 토큰입니다. 다시 확인 해주세요"); INVALID_TOKEN(HttpStatus.UNAUTHORIZED, 2007, "올바르지 않는 인증 토큰입니다. 다시 확인 해주세요"),
USER_ALREADY_SIGN_OUT(HttpStatus.UNAUTHORIZED, 2008,"이미 로그아웃한 사용자입니다."),
;
private final HttpStatus status; private final HttpStatus status;
private final int code; private final int code;

View File

@ -1,17 +1,16 @@
package com.worlabel.global.filter; package com.worlabel.global.filter;
import com.worlabel.domain.auth.entity.CustomOAuth2User; import com.worlabel.domain.auth.entity.dto.LoginMember;
import com.worlabel.domain.member.repository.MemberRepository; import com.worlabel.domain.auth.service.JwtTokenService;
import com.worlabel.global.util.JWTUtil;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.security.oauth2.jwt.JwtException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -19,45 +18,44 @@ import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.List;
import java.util.Optional;
@Slf4j @Slf4j
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter { public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JWTUtil jwtUtil; public final String AUTHORIZATION_HEADER = "Authorization";
private final MemberRepository memberRepository; private final JwtTokenService jwtTokenService;
@Override @Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = getToken(request); String token = resolveToken(request);
log.debug("token {}", token);
try { try {
if (StringUtils.hasText(token) && !jwtUtil.isExpired(token)) { if (StringUtils.hasText(token) && !jwtTokenService.isTokenExpired(token) && jwtTokenService.isAccessToken(token)) {
String username = jwtUtil.getUsername(token); String name = jwtTokenService.parseUsername(token);
memberRepository.findByEmail(username).ifPresent(member -> { int id = jwtTokenService.parseId(token);
CustomOAuth2User customOAuth2User = new CustomOAuth2User(member);
Authentication authToken = new UsernamePasswordAuthenticationToken(customOAuth2User.getAuthMember(), null, customOAuth2User.getAuthorities()); List<SimpleGrantedAuthority> authorities = jwtTokenService.parseAuthorities(token);
SecurityContextHolder.getContext().setAuthentication(authToken); Authentication authToken = new UsernamePasswordAuthenticationToken(LoginMember.of(id,name), null, authorities);
}); SecurityContextHolder.getContext().setAuthentication(authToken);
} else { } else {
throw new JwtException("유효한 JWT 토큰이 없습니다."); throw new JwtException("유효한 JWT 토큰이 없습니다.");
} }
} catch (Exception e) { } catch (Exception e) {
log.debug("message: {}",e.getMessage()); log.debug("message: {}", e.getMessage());
SecurityContextHolder.clearContext(); SecurityContextHolder.clearContext();
request.setAttribute("error-message", e.getMessage()); request.setAttribute("error-message", e.getMessage());
} }
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }
private static String getToken(HttpServletRequest request) { private String resolveToken(HttpServletRequest request) {
Cookie[] cookies = request.getCookies(); String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
return Arrays.stream(Optional.ofNullable(cookies).orElse(new Cookie[0])) if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
.filter(cookie -> "Authorization".equals(cookie.getName())) return bearerToken.substring(7);
.map(Cookie::getValue) }
.findFirst() return null;
.orElse(null);
} }
} }

View File

@ -1,6 +1,6 @@
package com.worlabel.global.resolver; package com.worlabel.global.resolver;
import com.worlabel.domain.auth.entity.dto.AuthMemberDto; import com.worlabel.domain.auth.entity.dto.LoginMember;
import com.worlabel.global.annotation.CurrentUser; import com.worlabel.global.annotation.CurrentUser;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
@ -22,8 +22,8 @@ public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolve
@Override @Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if(principal instanceof AuthMemberDto){ if(principal instanceof LoginMember){
return ((AuthMemberDto) principal).getId(); return ((LoginMember) principal).getId();
} }
return null; return null;
} }

View File

@ -1,51 +0,0 @@
package com.worlabel.global.util;
import io.jsonwebtoken.Jwts;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Date;
@Component
public class JWTUtil {
private final SecretKey secretKey;
public JWTUtil(@Value("${spring.jwt.secret}") String key) {
secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
}
public String getUsername(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class);
}
public String getRole(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("role", String.class);
}
public boolean isExpired(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
}
/**
* AccessToken 생성
* @param username 사용자 아이디(이메일)
* @param role 사용자 권한
* @param expiredMs 토큰 만료시간
* @return AccessToken
*/
public String createJwtToken(String username, String role, Long expiredMs, String type) {
return Jwts.builder()
.claim("username", username)
.claim("role", role)
.claim("type", type)
.expiration(new Date(System.currentTimeMillis() + expiredMs))
.signWith(secretKey)
.compact();
}
}