Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
210ae52
refactor: 토큰 기능 제공 클래스 이름 변경
nayonsoso Jan 14, 2025
ee49cfe
refactor: 토큰 접두사 추가 함수 이름 변경, static import 적용
nayonsoso Jan 14, 2025
7227179
feat: subject 추출 함수 추가
nayonsoso Jan 14, 2025
e8632fc
test: TokenProvider 테스트 작성
nayonsoso Jan 14, 2025
feb828f
refactor: 예외 응답 함수 추출
nayonsoso Jan 14, 2025
b795eb8
feat: 로그아웃 체크 필터 생성
nayonsoso Jan 14, 2025
54141e1
test: 로그아웃 체크 필터 테스트 작성
nayonsoso Jan 14, 2025
e89dc76
refactor: 중복 코드 함수로 추출
nayonsoso Jan 14, 2025
ff902d2
refactor: JWT 인증 필터 수정
nayonsoso Jan 14, 2025
d0e0929
refactor: 사용하지 않는 코드,클래스 제거
nayonsoso Jan 14, 2025
53299c9
test: JWT 인증 필터 테스트 작성
nayonsoso Jan 14, 2025
c33466b
refactor: 스프링 시큐리티 설정 클래스 수정
nayonsoso Jan 14, 2025
aaca84f
refactor: 중복 선언된 cors 설정 제거
nayonsoso Jan 20, 2025
1d37ff4
refactor: cors 관련 설정 ConfigurationProperties로 변경
nayonsoso Jan 20, 2025
b607988
refactor: TokenType 패키지 이동
nayonsoso Jan 20, 2025
995ab18
refactor: TokenProvider, TokenValidator 패키지 이동
nayonsoso Jan 20, 2025
505b746
refactor: JwtProperties 분리
nayonsoso Jan 20, 2025
a4b0db4
refactor: JwtUtils 분리
nayonsoso Jan 20, 2025
2019370
refactor: ConfigurationPropertiesScan 적용
nayonsoso Jan 20, 2025
d9c965b
refactor: 인스턴스화 방지
nayonsoso Jan 22, 2025
605f24a
test: 테스트 메서드 이름에 컨벤션 적용
nayonsoso Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;

@ConfigurationPropertiesScan
@EnableScheduling
@EnableJpaAuditing
@EnableCaching
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.example.solidconnection.config.token;
package com.example.solidconnection.auth.domain;

import lombok.Getter;

@Getter
public enum TokenType {

ACCESS("", 1000 * 60 * 60),
REFRESH("refresh:", 1000 * 60 * 60 * 24 * 7),
KAKAO_OAUTH("kakao:", 1000 * 60 * 60);
ACCESS("ACCESS:", 1000 * 60 * 60), // 1hour
REFRESH("REFRESH:", 1000 * 60 * 60 * 24 * 7), // 7days
KAKAO_OAUTH("KAKAO:", 1000 * 60 * 60); // 1hour

private final String prefix;
private final int expireTime;
Expand All @@ -17,7 +17,7 @@ public enum TokenType {
this.expireTime = expireTime;
}

public String addTokenPrefixToSubject(String subject) {
public String addPrefixToSubject(String subject) {
return prefix + subject;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@


import com.example.solidconnection.auth.dto.ReissueResponse;
import com.example.solidconnection.config.token.TokenService;
import com.example.solidconnection.config.token.TokenType;
import com.example.solidconnection.custom.exception.CustomException;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
Expand All @@ -16,15 +14,18 @@
import java.time.LocalDate;
import java.util.concurrent.TimeUnit;

import static com.example.solidconnection.config.token.TokenValidator.SIGN_OUT_VALUE;
import static com.example.solidconnection.auth.domain.TokenType.ACCESS;
import static com.example.solidconnection.auth.domain.TokenType.REFRESH;
import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED;

@RequiredArgsConstructor
@Service
public class AuthService {

public static final String SIGN_OUT_VALUE = "signOut";

private final RedisTemplate<String, String> redisTemplate;
private final TokenService tokenService;
private final TokenProvider tokenProvider;
private final SiteUserRepository siteUserRepository;

/*
Expand All @@ -36,9 +37,9 @@ public class AuthService {
* */
public void signOut(String email) {
redisTemplate.opsForValue().set(
TokenType.REFRESH.addTokenPrefixToSubject(email),
REFRESH.addPrefixToSubject(email),
SIGN_OUT_VALUE,
TokenType.REFRESH.getExpireTime(),
REFRESH.getExpireTime(),
TimeUnit.MILLISECONDS
);
}
Expand All @@ -62,14 +63,14 @@ public void quit(String email) {
* */
public ReissueResponse reissue(String email) {
// 리프레시 토큰 만료 확인
String refreshTokenKey = TokenType.REFRESH.addTokenPrefixToSubject(email);
String refreshTokenKey = REFRESH.addPrefixToSubject(email);
String refreshToken = redisTemplate.opsForValue().get(refreshTokenKey);
if (ObjectUtils.isEmpty(refreshToken)) {
throw new CustomException(REFRESH_TOKEN_EXPIRED);
}
// 액세스 토큰 재발급
String newAccessToken = tokenService.generateToken(email, TokenType.ACCESS);
tokenService.saveToken(newAccessToken, TokenType.ACCESS);
String newAccessToken = tokenProvider.generateToken(email, ACCESS);
tokenProvider.saveToken(newAccessToken, ACCESS);
return new ReissueResponse(newAccessToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import com.example.solidconnection.auth.dto.kakao.KakaoCodeRequest;
import com.example.solidconnection.auth.dto.kakao.KakaoOauthResponse;
import com.example.solidconnection.auth.dto.kakao.KakaoUserInfoDto;
import com.example.solidconnection.config.token.TokenService;
import com.example.solidconnection.config.token.TokenType;
import com.example.solidconnection.auth.domain.TokenType;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
import lombok.RequiredArgsConstructor;
Expand All @@ -18,7 +17,7 @@
@Service
public class SignInService {

private final TokenService tokenService;
private final TokenProvider tokenProvider;
private final SiteUserRepository siteUserRepository;
private final KakaoOAuthClient kakaoOAuthClient;

Expand Down Expand Up @@ -58,15 +57,15 @@ private void resetQuitedAt(String email) {
}

private SignInResponse getSignInInfo(String email) {
String accessToken = tokenService.generateToken(email, TokenType.ACCESS);
String refreshToken = tokenService.generateToken(email, TokenType.REFRESH);
tokenService.saveToken(refreshToken, TokenType.REFRESH);
String accessToken = tokenProvider.generateToken(email, TokenType.ACCESS);
String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH);
tokenProvider.saveToken(refreshToken, TokenType.REFRESH);
return new SignInResponse(true, accessToken, refreshToken);
}

private FirstAccessResponse getFirstAccessInfo(KakaoUserInfoDto kakaoUserInfoDto) {
String kakaoOauthToken = tokenService.generateToken(kakaoUserInfoDto.kakaoAccountDto().email(), TokenType.KAKAO_OAUTH);
tokenService.saveToken(kakaoOauthToken, TokenType.KAKAO_OAUTH);
String kakaoOauthToken = tokenProvider.generateToken(kakaoUserInfoDto.kakaoAccountDto().email(), TokenType.KAKAO_OAUTH);
tokenProvider.saveToken(kakaoOauthToken, TokenType.KAKAO_OAUTH);
return FirstAccessResponse.of(kakaoUserInfoDto, kakaoOauthToken);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import com.example.solidconnection.auth.dto.SignUpRequest;
import com.example.solidconnection.auth.dto.SignUpResponse;
import com.example.solidconnection.config.token.TokenService;
import com.example.solidconnection.config.token.TokenType;
import com.example.solidconnection.config.token.TokenValidator;
import com.example.solidconnection.auth.domain.TokenType;
import com.example.solidconnection.custom.exception.CustomException;
import com.example.solidconnection.entity.InterestedCountry;
import com.example.solidconnection.entity.InterestedRegion;
Expand All @@ -29,7 +27,7 @@
public class SignUpService {

private final TokenValidator tokenValidator;
private final TokenService tokenService;
private final TokenProvider tokenProvider;
private final SiteUserRepository siteUserRepository;
private final RegionRepository regionRepository;
private final InterestedRegionRepository interestedRegionRepository;
Expand All @@ -51,7 +49,7 @@ public class SignUpService {
public SignUpResponse signUp(SignUpRequest signUpRequest) {
// 검증
tokenValidator.validateKakaoToken(signUpRequest.kakaoOauthToken());
String email = tokenService.getEmail(signUpRequest.kakaoOauthToken());
String email = tokenProvider.getEmail(signUpRequest.kakaoOauthToken());
validateNicknameDuplicated(signUpRequest.nickname());
validateUserNotDuplicated(email);

Expand All @@ -64,9 +62,9 @@ public SignUpResponse signUp(SignUpRequest signUpRequest) {
saveInterestedCountry(signUpRequest, savedSiteUser);

// 토큰 발급
String accessToken = tokenService.generateToken(email, TokenType.ACCESS);
String refreshToken = tokenService.generateToken(email, TokenType.REFRESH);
tokenService.saveToken(refreshToken, TokenType.REFRESH);
String accessToken = tokenProvider.generateToken(email, TokenType.ACCESS);
String refreshToken = tokenProvider.generateToken(email, TokenType.REFRESH);
tokenProvider.saveToken(refreshToken, TokenType.REFRESH);
return new SignUpResponse(accessToken, refreshToken);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.example.solidconnection.auth.service;

Comment on lines +1 to +2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TokenProvider는 Auth의 책임이니 적절하게 이동한 거 같습니다! 다만 앞으로 애플 등의 인증 방식이 추가될 수 있으므로, 확장성을 위해 인터페이스 도입을 고려하면 좋을 것 같습니다. 현재는 구조를 유지하면서, 새로운 인증 방식 추가 시점에 인터페이스화를 논의해도 좋을 거 같습니다!

import com.example.solidconnection.auth.domain.TokenType;
import com.example.solidconnection.config.security.JwtProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.concurrent.TimeUnit;

import static com.example.solidconnection.util.JwtUtils.parseSubject;
import static com.example.solidconnection.util.JwtUtils.parseSubjectOrElseThrow;

@RequiredArgsConstructor
@Component
public class TokenProvider {

private final RedisTemplate<String, String> redisTemplate;
private final JwtProperties jwtProperties;

public String generateToken(String email, TokenType tokenType) {
Claims claims = Jwts.claims().setSubject(email);
Date now = new Date();
Date expiredDate = new Date(now.getTime() + tokenType.getExpireTime());
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiredDate)
.signWith(SignatureAlgorithm.HS512, jwtProperties.secret())
.compact();
}

public String saveToken(String token, TokenType tokenType) {
String subject = parseSubjectOrElseThrow(token, jwtProperties.secret());
redisTemplate.opsForValue().set(
tokenType.addPrefixToSubject(subject),
token,
tokenType.getExpireTime(),
TimeUnit.MILLISECONDS
);
return token;
}

public String getEmail(String token) {
return parseSubject(token, jwtProperties.secret());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.example.solidconnection.config.token;
package com.example.solidconnection.auth.service;

import com.example.solidconnection.auth.domain.TokenType;
import com.example.solidconnection.custom.exception.CustomException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
Expand All @@ -12,72 +13,64 @@
import java.util.Date;
import java.util.Objects;

import static com.example.solidconnection.auth.domain.TokenType.ACCESS;
import static com.example.solidconnection.auth.domain.TokenType.KAKAO_OAUTH;
import static com.example.solidconnection.auth.domain.TokenType.REFRESH;
import static com.example.solidconnection.custom.exception.ErrorCode.ACCESS_TOKEN_EXPIRED;
import static com.example.solidconnection.custom.exception.ErrorCode.EMPTY_TOKEN;
import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN;
import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_TOKEN;
import static com.example.solidconnection.custom.exception.ErrorCode.REFRESH_TOKEN_EXPIRED;
import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT;

@Component
@RequiredArgsConstructor
public class TokenValidator {

public static final String SIGN_OUT_VALUE = "signOut";

private final RedisTemplate<String, String> redisTemplate;

@Value("${jwt.secret}")
private String secretKey;

public void validateAccessToken(String token) {
validateTokenNotEmpty(token);
validateTokenNotExpired(token, TokenType.ACCESS);
validateNotSignOut(token);
validateTokenNotExpired(token, ACCESS);
validateRefreshToken(token);
}

public void validateKakaoToken(String token) {
validateTokenNotEmpty(token);
validateTokenNotExpired(token, TokenType.KAKAO_OAUTH);
validateTokenNotExpired(token, KAKAO_OAUTH);
validateKakaoTokenNotUsed(token);
}

private void validateTokenNotEmpty(String token) {
if (!StringUtils.hasText(token)) {
throw new CustomException(INVALID_TOKEN);
throw new CustomException(EMPTY_TOKEN);
}
}

private void validateTokenNotExpired(String token, TokenType tokenType) {
Date expiration = getClaim(token).getExpiration();
long now = new Date().getTime();
if ((expiration.getTime() - now) < 0) {
if (tokenType.equals(TokenType.ACCESS)) {
if (tokenType.equals(ACCESS)) {
throw new CustomException(ACCESS_TOKEN_EXPIRED);
}
if (token.equals(TokenType.KAKAO_OAUTH)) {
if (token.equals(KAKAO_OAUTH)) {
throw new CustomException(INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN);
}
}
}

private void validateNotSignOut(String token) {
String email = getClaim(token).getSubject();
if (SIGN_OUT_VALUE.equals(redisTemplate.opsForValue().get(TokenType.REFRESH.addTokenPrefixToSubject(email)))) {
throw new CustomException(USER_ALREADY_SIGN_OUT);
}
}

private void validateRefreshToken(String token) {
String email = getClaim(token).getSubject();
if (redisTemplate.opsForValue().get(TokenType.REFRESH.addTokenPrefixToSubject(email)) == null) {
if (redisTemplate.opsForValue().get(REFRESH.addPrefixToSubject(email)) == null) {
throw new CustomException(REFRESH_TOKEN_EXPIRED);
}
}

private void validateKakaoTokenNotUsed(String token) {
String email = getClaim(token).getSubject();
if (!Objects.equals(redisTemplate.opsForValue().get(TokenType.KAKAO_OAUTH.addTokenPrefixToSubject(email)), token)) {
if (!Objects.equals(redisTemplate.opsForValue().get(KAKAO_OAUTH.addPrefixToSubject(email)), token)) {
throw new CustomException(INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN);
}
}
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example.solidconnection.config.security;

import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.List;

@ConfigurationProperties(prefix = "cors")
public record CorsProperties(List<String> allowedOrigins) {
}
Loading