Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -7,8 +7,8 @@ public enum TokenType {

ACCESS("ACCESS:", 1000 * 60 * 60), // 1hour
REFRESH("REFRESH:", 1000 * 60 * 60 * 24 * 7), // 7days
KAKAO_OAUTH("KAKAO:", 1000 * 60 * 60), // 1hour
BLACKLIST("BLACKLIST:", ACCESS.expireTime)
BLACKLIST("BLACKLIST:", ACCESS.expireTime),
SIGN_UP("SIGN_UP:", 1000 * 60 * 10), // 10min
;

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

public String addPrefixToSubject(String subject) {
return prefix + subject;
public String addPrefix(String string) {
return prefix + string;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,37 +5,26 @@
import com.example.solidconnection.custom.exception.CustomException;
import com.example.solidconnection.siteuser.domain.SiteUser;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.time.LocalDate;
import java.util.concurrent.TimeUnit;
import java.util.Optional;

import static com.example.solidconnection.auth.domain.TokenType.ACCESS;
import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST;
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 {

private final RedisTemplate<String, String> redisTemplate;
private final TokenProvider tokenProvider;
private final AuthTokenProvider authTokenProvider;

/*
* 로그아웃 한다.
* - 엑세스 토큰을 블랙리스트에 추가한다.
* */
public void signOut(String accessToken) {
redisTemplate.opsForValue().set(
BLACKLIST.addPrefixToSubject(accessToken),
accessToken,
BLACKLIST.getExpireTime(),
TimeUnit.MILLISECONDS
);
authTokenProvider.generateAndSaveBlackListToken(accessToken);
}

/*
Expand All @@ -56,14 +45,12 @@ public void quit(SiteUser siteUser) {
* */
public ReissueResponse reissue(String subject) {
// 리프레시 토큰 만료 확인
String refreshTokenKey = REFRESH.addPrefixToSubject(subject);
String refreshToken = redisTemplate.opsForValue().get(refreshTokenKey);
if (ObjectUtils.isEmpty(refreshToken)) {
Optional<String> optionalRefreshToken = authTokenProvider.findRefreshToken(subject);
if (optionalRefreshToken.isEmpty()) {
throw new CustomException(REFRESH_TOKEN_EXPIRED);
}
// 액세스 토큰 재발급
String newAccessToken = tokenProvider.generateToken(subject, ACCESS);
tokenProvider.saveToken(newAccessToken, ACCESS);
String newAccessToken = authTokenProvider.generateAccessToken(subject);
return new ReissueResponse(newAccessToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.example.solidconnection.auth.service;

import com.example.solidconnection.auth.domain.TokenType;
import com.example.solidconnection.config.security.JwtProperties;
import com.example.solidconnection.siteuser.domain.SiteUser;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Optional;

import static com.example.solidconnection.util.JwtUtils.parseSubjectIgnoringExpiration;

@Component
public class AuthTokenProvider extends TokenProvider {

public AuthTokenProvider(JwtProperties jwtProperties, RedisTemplate<String, String> redisTemplate) {
super(jwtProperties, redisTemplate);
}

public String generateAccessToken(SiteUser siteUser) {
String subject = siteUser.getId().toString();
return generateToken(subject, TokenType.ACCESS);
}

public String generateAccessToken(String subject) {
return generateToken(subject, TokenType.ACCESS);
}

public String generateAndSaveRefreshToken(SiteUser siteUser) {
String subject = siteUser.getId().toString();
String refreshToken = generateToken(subject, TokenType.REFRESH);
return saveToken(refreshToken, TokenType.REFRESH);
}

public String generateAndSaveBlackListToken(String accessToken) {
String blackListToken = generateToken(accessToken, TokenType.BLACKLIST);
return saveToken(blackListToken, TokenType.BLACKLIST);
}

public Optional<String> findRefreshToken(String subject) {
String refreshTokenKey = TokenType.REFRESH.addPrefix(subject);
return Optional.ofNullable(redisTemplate.opsForValue().get(refreshTokenKey));
}

public Optional<String> findBlackListToken(String subject) {
String blackListTokenKey = TokenType.BLACKLIST.addPrefix(subject);
return Optional.ofNullable(redisTemplate.opsForValue().get(blackListTokenKey));
}

public String getEmail(String token) {
return parseSubjectIgnoringExpiration(token, jwtProperties.secret());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
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.auth.domain.TokenType;
import com.example.solidconnection.siteuser.domain.AuthType;
import com.example.solidconnection.siteuser.domain.SiteUser;
import com.example.solidconnection.siteuser.repository.SiteUserRepository;
Expand All @@ -20,7 +19,8 @@
@Service
public class SignInService {

private final TokenProvider tokenProvider;
private final AuthTokenProvider authTokenProvider;
private final SignUpTokenProvider signUpTokenProvider;
private final SiteUserRepository siteUserRepository;
private final KakaoOAuthClient kakaoOAuthClient;

Expand Down Expand Up @@ -60,15 +60,13 @@ private void resetQuitedAt(SiteUser siteUser) {
}

private SignInResponse getSignInInfo(SiteUser siteUser) {
String accessToken = tokenProvider.generateToken(siteUser, TokenType.ACCESS);
String refreshToken = tokenProvider.generateToken(siteUser, TokenType.REFRESH);
tokenProvider.saveToken(refreshToken, TokenType.REFRESH);
String accessToken = authTokenProvider.generateAccessToken(siteUser);
String refreshToken = authTokenProvider.generateAndSaveRefreshToken(siteUser);
return new SignInResponse(true, accessToken, refreshToken);
}

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

import com.example.solidconnection.auth.dto.SignUpRequest;
import com.example.solidconnection.auth.dto.SignUpResponse;
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 @@ -28,7 +27,7 @@
public class SignUpService {

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

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

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

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

import com.example.solidconnection.auth.domain.TokenType;
import com.example.solidconnection.config.security.JwtProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
public class SignUpTokenProvider extends TokenProvider {

public SignUpTokenProvider(JwtProperties jwtProperties, RedisTemplate<String, String> redisTemplate) {
super(jwtProperties, redisTemplate);
}

public String generateAndSaveSignUpToken(String email) {
String signUpToken = generateToken(email, TokenType.SIGN_UP);
return saveToken(signUpToken, TokenType.SIGN_UP);
}

public Optional<String> findSignUpToken(String email) {
String signUpKey = TokenType.SIGN_UP.addPrefix(email);
return Optional.ofNullable(redisTemplate.opsForValue().get(signUpKey));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,27 @@

import com.example.solidconnection.auth.domain.TokenType;
import com.example.solidconnection.config.security.JwtProperties;
import com.example.solidconnection.siteuser.domain.SiteUser;
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.parseSubjectIgnoringExpiration;

@RequiredArgsConstructor
@Component
public class TokenProvider {
public abstract class TokenProvider {

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

public String generateToken(SiteUser siteUser, TokenType tokenType) {
String subject = siteUser.getId().toString();
return generateToken(subject, tokenType);
public TokenProvider(JwtProperties jwtProperties, RedisTemplate<String, String> redisTemplate) {
this.jwtProperties = jwtProperties;
this.redisTemplate = redisTemplate;
}

public String generateToken(String string, TokenType tokenType) {
protected final String generateToken(String string, TokenType tokenType) {
Claims claims = Jwts.claims().setSubject(string);
Date now = new Date();
Date expiredDate = new Date(now.getTime() + tokenType.getExpireTime());
Expand All @@ -40,18 +34,14 @@ public String generateToken(String string, TokenType tokenType) {
.compact();
}

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

public String getEmail(String token) {
return parseSubjectIgnoringExpiration(token, jwtProperties.secret());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
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.auth.domain.TokenType.SIGN_UP;
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;
Expand All @@ -38,7 +38,7 @@ public void validateAccessToken(String token) {

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

Expand All @@ -55,22 +55,22 @@ private void validateTokenNotExpired(String token, TokenType tokenType) {
if (tokenType.equals(ACCESS)) {
throw new CustomException(ACCESS_TOKEN_EXPIRED);
}
if (token.equals(KAKAO_OAUTH)) {
if (token.equals(SIGN_UP)) {
throw new CustomException(INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN);
}
}
}

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

private void validateKakaoTokenNotUsed(String token) {
String email = getClaim(token).getSubject();
if (!Objects.equals(redisTemplate.opsForValue().get(KAKAO_OAUTH.addPrefixToSubject(email)), token)) {
if (!Objects.equals(redisTemplate.opsForValue().get(SIGN_UP.addPrefix(email)), token)) {
throw new CustomException(INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
package com.example.solidconnection.custom.security.filter;

import com.example.solidconnection.config.security.JwtProperties;
import com.example.solidconnection.auth.service.AuthTokenProvider;
import com.example.solidconnection.custom.exception.CustomException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST;
import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_SIGN_OUT;
import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest;

@Component
@RequiredArgsConstructor
public class SignOutCheckFilter extends OncePerRequestFilter {

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

@Override
protected void doFilterInternal(@NonNull HttpServletRequest request,
Expand All @@ -37,7 +34,6 @@ protected void doFilterInternal(@NonNull HttpServletRequest request,
}

private boolean hasSignedOut(String accessToken) {
String blacklistKey = BLACKLIST.addPrefixToSubject(accessToken);
return redisTemplate.opsForValue().get(blacklistKey) != null;
return authTokenProvider.findBlackListToken(accessToken).isPresent();
}
}
Loading