Refactor: reissue API 오류 해결
This commit is contained in:
parent
8d8de6e396
commit
331950ec70
@ -1,13 +1,17 @@
|
|||||||
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.entity.dto.JwtToken;
|
||||||
import com.worlabel.domain.auth.repository.AuthCacheRepository;
|
import com.worlabel.domain.auth.entity.dto.AccessTokenResponse;
|
||||||
import com.worlabel.domain.auth.service.AuthService;
|
import com.worlabel.domain.auth.service.AuthService;
|
||||||
import com.worlabel.domain.auth.service.JwtTokenService;
|
import com.worlabel.domain.auth.service.JwtTokenService;
|
||||||
import com.worlabel.global.annotation.CurrentUser;
|
import com.worlabel.global.annotation.CurrentUser;
|
||||||
|
import com.worlabel.global.config.swagger.SwaggerApiError;
|
||||||
|
import com.worlabel.global.config.swagger.SwaggerApiSuccess;
|
||||||
import com.worlabel.global.exception.CustomException;
|
import com.worlabel.global.exception.CustomException;
|
||||||
import com.worlabel.global.exception.ErrorCode;
|
import com.worlabel.global.exception.ErrorCode;
|
||||||
import com.worlabel.global.response.SuccessResponse;
|
import com.worlabel.global.response.SuccessResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.servlet.http.Cookie;
|
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;
|
||||||
@ -21,35 +25,38 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Tag(name = "인증/인가 관련 API")
|
||||||
@RequestMapping("/api/auth")
|
@RequestMapping("/api/auth")
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
private final AuthService authService;
|
|
||||||
private final AuthCacheRepository authCacheRepository;
|
|
||||||
private final JwtTokenService jwtTokenService;
|
|
||||||
|
|
||||||
@Value("${auth.refreshTokenExpiry}")
|
@Value("${auth.refreshTokenExpiry}")
|
||||||
long refreshExpiry;
|
long refreshExpiry;
|
||||||
|
|
||||||
// TODO: 리이슈 처리, 액세스 어떻게 받았는지 물어보기
|
private final AuthService authService;
|
||||||
|
private final JwtTokenService jwtTokenService;
|
||||||
|
|
||||||
|
@Operation(summary = "JWT 토큰 재발급", description = "Refresh Token을 확인하여 JWT 토큰 재발급")
|
||||||
|
@SwaggerApiSuccess(description = "Return Access Token")
|
||||||
|
@SwaggerApiError({ErrorCode.INVALID_TOKEN, ErrorCode.USER_ALREADY_SIGN_OUT, ErrorCode.REFRESH_TOKEN_EXPIRED, ErrorCode.INVALID_REFRESH_TOKEN})
|
||||||
@PostMapping("/reissue")
|
@PostMapping("/reissue")
|
||||||
public SuccessResponse<String> reissue(
|
public SuccessResponse<AccessTokenResponse> reissue(
|
||||||
HttpServletRequest request,
|
HttpServletRequest request,
|
||||||
HttpServletResponse response
|
HttpServletResponse response
|
||||||
) {
|
) {
|
||||||
|
log.debug("reissue request");
|
||||||
String refresh = parseRefreshCookie(request);
|
String refresh = parseRefreshCookie(request);
|
||||||
log.info("reissue :{}", refresh);
|
|
||||||
try {
|
try {
|
||||||
JwtToken newToken = authService.reissue(refresh);
|
JwtToken newToken = authService.reissue(refresh);
|
||||||
log.debug("새로운 토큰 발급 성공");
|
|
||||||
int id = jwtTokenService.parseId(newToken.getAccessToken());
|
int id = jwtTokenService.parseId(newToken.getAccessToken());
|
||||||
log.debug("{}",id);
|
|
||||||
response.addCookie(createCookie(newToken.getRefreshToken()));
|
response.addCookie(createCookie(newToken.getRefreshToken()));
|
||||||
authCacheRepository.save(id, newToken.getRefreshToken(), refreshExpiry);
|
authService.saveRefreshToken(id, newToken.getRefreshToken(),refreshExpiry);
|
||||||
return SuccessResponse.of(newToken.getAccessToken());
|
|
||||||
|
return SuccessResponse.of(AccessTokenResponse.from(newToken.getAccessToken()));
|
||||||
} catch (CustomException e) {
|
} catch (CustomException e) {
|
||||||
throw e;
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -57,15 +64,10 @@ public class AuthController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cookie createCookie(String value) {
|
// TODO: Member 완성 후 구현
|
||||||
Cookie cookie = new Cookie("refreshToken", value);
|
@Operation(summary = "로그인 중인 사용자 정보를 반환", description = "현재 로그인중인 사용자의 정보를 반환합니다.")
|
||||||
cookie.setMaxAge((int) (refreshExpiry / 1000));
|
@SwaggerApiSuccess(description = "Return Member Info")
|
||||||
cookie.setPath("/");
|
@SwaggerApiError({ErrorCode.INVALID_TOKEN, ErrorCode.USER_ALREADY_SIGN_OUT, ErrorCode.REFRESH_TOKEN_EXPIRED, ErrorCode.INVALID_REFRESH_TOKEN})
|
||||||
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);
|
||||||
@ -78,8 +80,18 @@ public class AuthController {
|
|||||||
.filter(cookie -> "refreshToken".equals(cookie.getName()))
|
.filter(cookie -> "refreshToken".equals(cookie.getName()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(Cookie::getValue)
|
.map(Cookie::getValue)
|
||||||
|
.map(String::trim)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ public class CustomOAuth2User implements OAuth2User {
|
|||||||
private transient final AuthMemberDto authMember;
|
private transient final AuthMemberDto authMember;
|
||||||
|
|
||||||
public CustomOAuth2User(Member member) {
|
public CustomOAuth2User(Member member) {
|
||||||
authMember = AuthMemberDto.of(member);
|
authMember = AuthMemberDto.from(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.worlabel.domain.auth.entity.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Schema(name = "리프레시 토큰 응답 dto", description = "리프레시 토큰 응답 dto")
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class AccessTokenResponse {
|
||||||
|
|
||||||
|
@Schema(description = "액세스 토큰", example = "")
|
||||||
|
private String accessToken;
|
||||||
|
|
||||||
|
public static AccessTokenResponse from(String accessToken) {
|
||||||
|
return new AccessTokenResponse(accessToken);
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,24 @@
|
|||||||
package com.worlabel.domain.auth.entity.dto;
|
package com.worlabel.domain.auth.entity.dto;
|
||||||
|
|
||||||
import com.worlabel.domain.member.entity.Member;
|
import com.worlabel.domain.member.entity.Member;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@ToString
|
@ToString
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public class AuthMemberDto {
|
public class AuthMemberDto {
|
||||||
private int id;
|
private int id;
|
||||||
private String email;
|
private String email;
|
||||||
private String role;
|
private String role;
|
||||||
|
|
||||||
public static AuthMemberDto of(Member member) {
|
public static AuthMemberDto from(Member member) {
|
||||||
AuthMemberDto authMemberDto = new AuthMemberDto();
|
return new AuthMemberDto(member.getId(), member.getEmail(), member.getRole().toString());
|
||||||
authMemberDto.id = member.getId();
|
}
|
||||||
authMemberDto.email = member.getEmail();
|
|
||||||
authMemberDto.role = member.getRole().toString();
|
|
||||||
|
|
||||||
return authMemberDto;
|
public static AuthMemberDto of(int id, String email, String role) {
|
||||||
|
return new AuthMemberDto(id, email, role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,16 @@
|
|||||||
package com.worlabel.domain.auth.entity.dto;
|
package com.worlabel.domain.auth.entity.dto;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public class JwtToken {
|
public class JwtToken {
|
||||||
private String accessToken;
|
private String accessToken;
|
||||||
private String refreshToken;
|
private String refreshToken;
|
||||||
|
|
||||||
|
public static JwtToken of(String accessToken, String refreshToken){
|
||||||
|
return new JwtToken(accessToken, refreshToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,14 +15,23 @@ public class AuthCacheRepository {
|
|||||||
|
|
||||||
private final RedisTemplate<String, Object> redisTemplate;
|
private final RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
public void save(int memberId, String token, Long expiredTime) {
|
/**
|
||||||
redisTemplate.opsForValue().set(CacheKey.authenticationKey(memberId), String.valueOf(token), expiredTime, TimeUnit.MILLISECONDS);
|
* 리프레시 토큰 저장
|
||||||
|
*/
|
||||||
|
public void save(int memberId, String refreshToken, Long expiredTime) {
|
||||||
|
redisTemplate.opsForValue().set(CacheKey.authenticationKey(memberId), String.valueOf(refreshToken), expiredTime, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 리프레시 토큰 반환
|
||||||
|
*/
|
||||||
public String find(int memberId) {
|
public String find(int memberId) {
|
||||||
return (String) redisTemplate.opsForValue().get(CacheKey.authenticationKey(memberId));
|
return (String) redisTemplate.opsForValue().get(CacheKey.authenticationKey(memberId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 리프레시 토큰 삭제
|
||||||
|
*/
|
||||||
public void delete(int memberId){
|
public void delete(int memberId){
|
||||||
redisTemplate.delete(CacheKey.authenticationKey(memberId));
|
redisTemplate.delete(CacheKey.authenticationKey(memberId));
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import com.worlabel.global.exception.ErrorCode;
|
|||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@ -14,17 +15,26 @@ import java.util.Objects;
|
|||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public class AuthService {
|
public class AuthService {
|
||||||
|
|
||||||
private final JwtTokenService jwtTokenService;
|
private final JwtTokenService jwtTokenService;
|
||||||
private final AuthCacheRepository authCacheRepository;
|
private final AuthCacheRepository authCacheRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT 토큰 재발급
|
||||||
|
*/
|
||||||
public JwtToken reissue(String refreshToken) throws Exception {
|
public JwtToken reissue(String refreshToken) throws Exception {
|
||||||
int id = jwtTokenService.parseId(refreshToken);
|
int id = jwtTokenService.parseId(refreshToken);
|
||||||
Object redisRefreshToken = authCacheRepository.find(id);
|
String redisRefreshToken = authCacheRepository.find(id);
|
||||||
log.debug("{} == {} ",redisRefreshToken,refreshToken);
|
if(!refreshToken.equals(redisRefreshToken)){
|
||||||
if(!Objects.equals(refreshToken, redisRefreshToken)){
|
|
||||||
throw new CustomException(ErrorCode.USER_ALREADY_SIGN_OUT);
|
throw new CustomException(ErrorCode.USER_ALREADY_SIGN_OUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
return jwtTokenService.generateTokenByRefreshToken(refreshToken);
|
return jwtTokenService.generateTokenByRefreshToken(refreshToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 레디에 리프레시 토큰 저장
|
||||||
|
*/
|
||||||
|
public void saveRefreshToken(int id, String refreshToken,Long expiredTime) {
|
||||||
|
authCacheRepository.save(id, refreshToken, expiredTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,13 @@ package com.worlabel.domain.auth.service;
|
|||||||
|
|
||||||
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.global.exception.CustomException;
|
||||||
|
import com.worlabel.global.exception.ErrorCode;
|
||||||
import io.jsonwebtoken.Claims;
|
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 io.jsonwebtoken.security.SignatureException;
|
||||||
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.GrantedAuthority;
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
@ -15,8 +18,11 @@ 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.security.Key;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@ -27,6 +33,7 @@ public class JwtTokenService {
|
|||||||
private final SecretKey secretKey;
|
private final SecretKey secretKey;
|
||||||
private final Long tokenExpiration;
|
private final Long tokenExpiration;
|
||||||
private final Long refreshTokenExpiration;
|
private final Long refreshTokenExpiration;
|
||||||
|
private Key key ;
|
||||||
|
|
||||||
public JwtTokenService(
|
public JwtTokenService(
|
||||||
@Value("${spring.jwt.secret}") String key,
|
@Value("${spring.jwt.secret}") String key,
|
||||||
@ -45,43 +52,58 @@ public class JwtTokenService {
|
|||||||
return generateToken(user.getName(), user.getId(), authorities);
|
return generateToken(user.getName(), user.getId(), authorities);
|
||||||
}
|
}
|
||||||
|
|
||||||
public JwtToken generateTokenByRefreshToken(String refreshToken) throws Exception{
|
public JwtToken generateTokenByRefreshToken(String refreshToken) throws Exception {
|
||||||
if (!isTokenExpired(refreshToken) && isRefreshToken(refreshToken)) {
|
log.debug("생성");
|
||||||
Claims claims = parseClaims(refreshToken);
|
|
||||||
String username = claims.getSubject();
|
if (isTokenExpired(refreshToken)) {
|
||||||
int memberId = claims.get("id", Integer.class);
|
throw new CustomException(ErrorCode.REFRESH_TOKEN_EXPIRED);
|
||||||
List<String> authorities = parseAuthorities(refreshToken).stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
|
|
||||||
return generateToken(username, memberId, authorities);
|
|
||||||
}
|
}
|
||||||
throw new Exception("유효하지 않은 토큰입니다.");
|
|
||||||
|
if (!isRefreshToken(refreshToken)) {
|
||||||
|
throw new CustomException(ErrorCode.INVALID_REFRESH_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("유효성 통과");
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private JwtToken generateToken(String username, int memberId, List<String> authorities){
|
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);
|
||||||
|
|
||||||
|
log.debug("액세스 만료 시간 : {}", accessTokenExpire.getTime());
|
||||||
|
log.debug("리프레시 만료 시간 : {}", refreshTokenExpire.getTime());
|
||||||
|
|
||||||
String accessToken = Jwts.builder()
|
String accessToken = Jwts.builder()
|
||||||
.subject(username)
|
.subject(username)
|
||||||
.claim("type", "access")
|
.claim("type", "access")
|
||||||
.claim("id", memberId)
|
.claim("id", memberId)
|
||||||
.claim("authorities", authorities) // 권한 정보 추가
|
.claim("authorities", authorities) // 권한 정보 추가
|
||||||
|
.issuedAt(new Date(now))
|
||||||
.expiration(accessTokenExpire)
|
.expiration(accessTokenExpire)
|
||||||
|
.claim("jti", UUID.randomUUID().toString())
|
||||||
.signWith(secretKey)
|
.signWith(secretKey)
|
||||||
.compact();
|
.compact();
|
||||||
|
|
||||||
String refreshToken = Jwts.builder()
|
String refreshToken = Jwts.builder()
|
||||||
.subject(username)
|
.subject(username)
|
||||||
.claim("type","refresh")
|
.claim("type", "refresh")
|
||||||
.claim("id", memberId)
|
.claim("id", memberId)
|
||||||
.claim("authorities", authorities) // 권한 정보 추가
|
.claim("authorities", authorities) // 권한 정보 추가
|
||||||
|
.issuedAt(new Date(now))
|
||||||
.expiration(refreshTokenExpire)
|
.expiration(refreshTokenExpire)
|
||||||
|
.claim("jti", UUID.randomUUID().toString())
|
||||||
.signWith(secretKey)
|
.signWith(secretKey)
|
||||||
.compact();
|
.compact();
|
||||||
|
|
||||||
log.debug("액세스 발급: {}",accessToken);
|
return JwtToken.of(accessToken, refreshToken);
|
||||||
return new JwtToken(accessToken, refreshToken);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String parseUsername(String token) throws Exception {
|
public String parseUsername(String token) throws Exception {
|
||||||
@ -101,15 +123,8 @@ public class JwtTokenService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 토큰 만료 여부 확인
|
// 토큰 만료 여부 확인
|
||||||
public boolean isTokenExpired(String token){
|
public boolean isTokenExpired(String token) throws Exception {
|
||||||
try{
|
return parseClaims(token) == null;
|
||||||
Claims claims = parseClaims(token);
|
|
||||||
return claims.getExpiration().before(new Date());
|
|
||||||
}catch (ExpiredJwtException e) {
|
|
||||||
return true; // 만료된 토큰
|
|
||||||
} catch (Exception e) {
|
|
||||||
return false; // 다른 오류일 경우
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isRefreshToken(String token) {
|
public boolean isRefreshToken(String token) {
|
||||||
@ -123,7 +138,7 @@ 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);
|
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) {
|
||||||
@ -134,19 +149,20 @@ public class JwtTokenService {
|
|||||||
|
|
||||||
private Claims parseClaims(String token) throws Exception {
|
private Claims parseClaims(String token) throws Exception {
|
||||||
String message;
|
String message;
|
||||||
try{
|
try {
|
||||||
return Jwts.parser()
|
return Jwts.parser()
|
||||||
.verifyWith(secretKey)
|
.verifyWith(secretKey)
|
||||||
.build()
|
.build()
|
||||||
.parseSignedClaims(token)
|
.parseSignedClaims(token)
|
||||||
.getPayload();
|
.getPayload();
|
||||||
}catch (ExpiredJwtException e){
|
} catch (ExpiredJwtException e) {
|
||||||
message = "유효기간이 만료된 토큰입니다.";
|
message = "유효기간이 만료된 토큰입니다.";
|
||||||
}catch (MalformedJwtException e){
|
} catch (MalformedJwtException | SignatureException e) {
|
||||||
message = "잘못된 형식의 토큰입니다.";
|
message = "잘못된 형식의 토큰입니다.";
|
||||||
}catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
message = "잘못된 인자입니다.";
|
message = "잘못된 인자입니다.";
|
||||||
}catch (Exception e){
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
message = "토큰 파싱 중 에러가 발생했습니다.";
|
message = "토큰 파싱 중 에러가 발생했습니다.";
|
||||||
}
|
}
|
||||||
throw new Exception(message);
|
throw new Exception(message);
|
||||||
|
@ -7,9 +7,9 @@ import lombok.Getter;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Schema(name = "워크스페이스 목록 응답 dto", description = "워크스페이스 목록 응답 DTO")
|
|
||||||
@Getter
|
@Getter
|
||||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@Schema(name = "워크스페이스 목록 응답 dto", description = "워크스페이스 목록 응답 DTO")
|
||||||
public class WorkspaceResponses {
|
public class WorkspaceResponses {
|
||||||
|
|
||||||
@Schema(description = "워크스페이스 목록", example = "")
|
@Schema(description = "워크스페이스 목록", example = "")
|
||||||
|
@ -35,13 +35,14 @@ public class SecurityConfig {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
// HTTP Basic 인증 방식 비활성화
|
// HTTP Basic 인증 방식 비활성화
|
||||||
http.
|
http.
|
||||||
httpBasic((auth) -> auth.disable());
|
httpBasic((auth) -> auth.disable());
|
||||||
|
|
||||||
// CSRF 비활성화
|
// CSRF 비활성화
|
||||||
http
|
http
|
||||||
.csrf((auth) -> auth.disable());
|
.csrf((auth) -> auth.disable());
|
||||||
|
|
||||||
// Form 로그인 방식 비활성화
|
// Form 로그인 방식 비활성화
|
||||||
http
|
http
|
||||||
.formLogin((auth) -> auth.disable());
|
.formLogin((auth) -> auth.disable());
|
||||||
@ -60,11 +61,10 @@ public class SecurityConfig {
|
|||||||
.accessDeniedHandler(authenticationDeniedHandler)
|
.accessDeniedHandler(authenticationDeniedHandler)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// 경로별 인가 작업
|
// 경로별 인가 작업
|
||||||
http
|
http
|
||||||
.authorizeHttpRequests(auth->auth
|
.authorizeHttpRequests(auth->auth
|
||||||
.requestMatchers("/api/**").authenticated()
|
.requestMatchers("/api/auth/reissue").permitAll()
|
||||||
.anyRequest().authenticated());
|
.anyRequest().authenticated());
|
||||||
|
|
||||||
// OAuth2
|
// OAuth2
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package com.worlabel.global.filter;
|
package com.worlabel.global.filter;
|
||||||
|
|
||||||
import com.worlabel.domain.auth.entity.dto.LoginMember;
|
import com.worlabel.domain.auth.entity.dto.AuthMemberDto;
|
||||||
import com.worlabel.domain.auth.service.JwtTokenService;
|
import com.worlabel.domain.auth.service.JwtTokenService;
|
||||||
import jakarta.servlet.FilterChain;
|
import jakarta.servlet.FilterChain;
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
@ -36,9 +36,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
if (StringUtils.hasText(token) && !jwtTokenService.isTokenExpired(token) && jwtTokenService.isAccessToken(token)) {
|
if (StringUtils.hasText(token) && !jwtTokenService.isTokenExpired(token) && jwtTokenService.isAccessToken(token)) {
|
||||||
String name = jwtTokenService.parseUsername(token);
|
String name = jwtTokenService.parseUsername(token);
|
||||||
int id = jwtTokenService.parseId(token);
|
int id = jwtTokenService.parseId(token);
|
||||||
|
|
||||||
List<SimpleGrantedAuthority> authorities = jwtTokenService.parseAuthorities(token);
|
List<SimpleGrantedAuthority> authorities = jwtTokenService.parseAuthorities(token);
|
||||||
Authentication authToken = new UsernamePasswordAuthenticationToken(LoginMember.of(id,name), null, authorities);
|
|
||||||
|
Authentication authToken = new UsernamePasswordAuthenticationToken(
|
||||||
|
AuthMemberDto.of(id,name,authorities.getFirst().toString()),
|
||||||
|
null,
|
||||||
|
authorities
|
||||||
|
);
|
||||||
SecurityContextHolder.getContext().setAuthentication(authToken);
|
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||||
} else {
|
} else {
|
||||||
throw new JwtException("유효한 JWT 토큰이 없습니다.");
|
throw new JwtException("유효한 JWT 토큰이 없습니다.");
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package com.worlabel.global.resolver;
|
package com.worlabel.global.resolver;
|
||||||
|
|
||||||
import com.worlabel.domain.auth.entity.dto.LoginMember;
|
import com.worlabel.domain.auth.entity.dto.AuthMemberDto;
|
||||||
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;
|
||||||
@ -14,6 +14,7 @@ import org.springframework.web.method.support.ModelAndViewContainer;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
|
public class CurrentUserArgumentResolver implements HandlerMethodArgumentResolver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsParameter(MethodParameter parameter) {
|
public boolean supportsParameter(MethodParameter parameter) {
|
||||||
return parameter.hasParameterAnnotation(CurrentUser.class);
|
return parameter.hasParameterAnnotation(CurrentUser.class);
|
||||||
@ -22,8 +23,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 LoginMember){
|
if(principal instanceof AuthMemberDto){
|
||||||
return ((LoginMember) principal).getId();
|
return ((AuthMemberDto) principal).getId();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user