Skip to content

Commit

Permalink
Merge pull request #166 from KUIT-Space/refactor/#161/인증-인가-처리-리펙토링
Browse files Browse the repository at this point in the history
Refactor/#161/인증 인가 처리 리펙토링
  • Loading branch information
seongjunnoh authored Nov 2, 2024
2 parents e90ee3e + 8eaab2e commit 8822b5f
Show file tree
Hide file tree
Showing 19 changed files with 605 additions and 84 deletions.
Binary file added .DS_Store
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
public class SpaceSpringApplication {
Expand Down
Binary file modified src/main/java/space/space_spring/domain/.DS_Store
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
import space.space_spring.domain.authorization.jwt.model.TokenType;
import space.space_spring.domain.authorization.jwt.repository.JwtRepository;
import space.space_spring.domain.user.model.PostLoginDto;
import space.space_spring.entity.TokenStorage;
import space.space_spring.entity.RefreshTokenStorage;
import space.space_spring.entity.User;
import space.space_spring.exception.CustomException;
import space.space_spring.jwt.JwtLoginProvider;
import space.space_spring.domain.authorization.jwt.model.JwtLoginProvider;
import space.space_spring.util.user.UserUtils;

import static space.space_spring.entity.enumStatus.UserSignupType.LOCAL;
Expand Down Expand Up @@ -40,11 +40,11 @@ public PostLoginDto login(PostLoginDto.Request request) {
validatePassword(userByEmail, request.getPassword());

// TODO 3. JWT 발급 -> access token, refresh token 2개 발급
String accessToken = jwtLoginProvider.generateToken(userByEmail, TokenType.ACCESS);
String refreshToken = jwtLoginProvider.generateToken(userByEmail, TokenType.REFRESH);
String accessToken = jwtLoginProvider.generateToken(userByEmail.getUserId(), TokenType.ACCESS);
String refreshToken = jwtLoginProvider.generateToken(userByEmail.getUserId(), TokenType.REFRESH);

// TODO 4. refresh token db에 저장
TokenStorage tokenStorage = TokenStorage.builder()
RefreshTokenStorage tokenStorage = RefreshTokenStorage.builder()
.user(userByEmail)
.tokenValue(refreshToken)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,9 @@ public class JwtController {
*/
@PostMapping("/new-token")
public BaseResponse<String> updateAccessToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
// access token, refresh token 파싱
TokenPairDTO tokenPairDTO = jwtService.resolveTokenPair(request);

// access token 로부터 user find
User userByAccessToken = jwtService.getUserByAccessToken(tokenPairDTO.getAccessToken());
TokenPairDTO newTokenPairDTO = jwtService.updateTokenPair(request);

// refresh token 유효성 검사
jwtService.validateRefreshToken(userByAccessToken, tokenPairDTO.getRefreshToken());

// access token, refresh token 새로 발급
TokenPairDTO newTokenPairDTO = jwtService.updateTokenPair(userByAccessToken);

// response header에 새로 발급한 token pair set
response.setHeader("Authorization-refresh", "Bearer " + newTokenPairDTO.getRefreshToken());
response.setHeader("Authorization", "Bearer " + newTokenPairDTO.getAccessToken());

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package space.space_spring.jwt;
package space.space_spring.domain.authorization.jwt.model;

import io.jsonwebtoken.*;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import space.space_spring.domain.authorization.jwt.model.TokenType;
import space.space_spring.entity.User;
import space.space_spring.exception.CustomException;
import space.space_spring.exception.jwt.bad_request.JwtUnsupportedTokenException;
import space.space_spring.exception.jwt.unauthorized.JwtInvalidTokenException;
Expand All @@ -17,6 +19,8 @@

@Slf4j
@Component
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class JwtLoginProvider {
@Value("${secret.jwt.access-secret-key}")
private String ACCESS_SECRET_KEY;
Expand All @@ -30,14 +34,12 @@ public class JwtLoginProvider {
@Value("${secret.jwt.refresh-expired-in}")
private Long REFRESH_EXPIRED_IN;

public String generateToken(User user, TokenType tokenType) {
public String generateToken(Long userId, TokenType tokenType) {
// Claims claims = Jwts.claims().setSubject(jwtPayloadDto.getUserId().toString());

Date now = new Date();
Date expiration = setExpiration(now, tokenType);

Long userId = user.getUserId();

return makeToken(tokenType, userId, now, expiration);
}

Expand Down Expand Up @@ -116,4 +118,5 @@ public Long getUserIdFromAccessToken(String token) {
throw e;
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package space.space_spring.domain.authorization.jwt.model;

import jakarta.servlet.http.HttpServletRequest;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import space.space_spring.exception.jwt.bad_request.JwtNoTokenException;
import space.space_spring.exception.jwt.bad_request.JwtUnsupportedTokenException;

import static space.space_spring.response.status.BaseExceptionResponseStatus.TOKEN_NOT_FOUND;
import static space.space_spring.response.status.BaseExceptionResponseStatus.UNSUPPORTED_TOKEN_TYPE;

@Component
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class JwtLoginTokenResolver {

private static final String JWT_TOKEN_PREFIX = "Bearer ";

public TokenPairDTO resolveTokenPair(HttpServletRequest request) {
// TODO 1. access token 파싱
String accessToken = request.getHeader(HttpHeaders.AUTHORIZATION);
validateToken(accessToken);

// TODO 2. refresh token 파싱
String refreshToken = request.getHeader("Authorization-refresh");
validateToken(refreshToken);

// TODO 3. return
return TokenPairDTO.builder()
.accessToken(accessToken.substring(JWT_TOKEN_PREFIX.length()))
.refreshToken(refreshToken.substring(JWT_TOKEN_PREFIX.length()))
.build();
}

private void validateToken(String token) {
if (token == null) {
throw new JwtNoTokenException(TOKEN_NOT_FOUND);
}
if (!token.startsWith(JWT_TOKEN_PREFIX)) {
throw new JwtUnsupportedTokenException(UNSUPPORTED_TOKEN_TYPE);
}
}

}
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
package space.space_spring.domain.authorization.jwt.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import space.space_spring.entity.TokenStorage;
import org.springframework.transaction.annotation.Transactional;
import space.space_spring.entity.RefreshTokenStorage;
import space.space_spring.entity.User;

import java.util.Optional;

@Repository
public interface JwtRepository extends JpaRepository<TokenStorage, Long> {
public interface JwtRepository extends JpaRepository<RefreshTokenStorage, Long> {

Optional<RefreshTokenStorage> findByUser(User user);

@Modifying
@Transactional
@Query("DELETE FROM RefreshTokenStorage t WHERE t.user = :user")
void deleteByUser(@Param("user") User user);

Optional<TokenStorage> findByUser(User user);
void deleteByUser(User user);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,54 @@

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import space.space_spring.domain.authorization.jwt.model.*;
import space.space_spring.domain.authorization.jwt.repository.JwtRepository;
import space.space_spring.domain.user.repository.UserRepository;
import space.space_spring.domain.authorization.jwt.model.TokenPairDTO;
import space.space_spring.domain.authorization.jwt.model.TokenType;
import space.space_spring.entity.TokenStorage;
import space.space_spring.entity.RefreshTokenStorage;
import space.space_spring.entity.User;
import space.space_spring.exception.CustomException;
import space.space_spring.exception.jwt.bad_request.JwtNoTokenException;
import space.space_spring.exception.jwt.bad_request.JwtUnsupportedTokenException;
import space.space_spring.exception.jwt.unauthorized.JwtExpiredTokenException;
import space.space_spring.exception.jwt.unauthorized.JwtUnauthorizedTokenException;
import space.space_spring.jwt.JwtLoginProvider;

import static space.space_spring.response.status.BaseExceptionResponseStatus.*;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class JwtService {

private final JwtRepository jwtRepository;
private final JwtLoginProvider jwtLoginProvider;
private final UserRepository userRepository;
private final JwtLoginProvider jwtLoginProvider;
private final JwtLoginTokenResolver tokenResolver;

private static final String JWT_TOKEN_PREFIX = "Bearer ";

@Transactional
public TokenPairDTO updateTokenPair(HttpServletRequest request) {
// request에서 기존의 TokenPair를 찾아와서
TokenPairDTO oldTokenPair = tokenResolver.resolveTokenPair(request);

public TokenPairDTO resolveTokenPair(HttpServletRequest request) {
// TODO 1. access token 파싱
String accessToken = request.getHeader(HttpHeaders.AUTHORIZATION);
validateToken(accessToken);
// 여기서 User 찾고
User user = getUserByAccessToken(oldTokenPair.getAccessToken());

// TODO 2. refresh token 파싱
String refreshToken = request.getHeader("Authorization-refresh");
validateToken(refreshToken);
// 이 User로 refresh token의 유효성 검사 진행하고
validateRefreshToken(user, oldTokenPair.getRefreshToken());

// TODO 3. return
return TokenPairDTO.builder()
.accessToken(accessToken.substring(JWT_TOKEN_PREFIX.length()))
.refreshToken(refreshToken.substring(JWT_TOKEN_PREFIX.length()))
.build();
}

private void validateToken(String token) {
if (token == null) {
throw new JwtNoTokenException(TOKEN_NOT_FOUND);
}
if (!token.startsWith(JWT_TOKEN_PREFIX)) {
throw new JwtUnsupportedTokenException(UNSUPPORTED_TOKEN_TYPE);
}
// access, refresh 새로 발급
return updateTokenPair(user);
}

public User getUserByAccessToken(String accessToken) {
private User getUserByAccessToken(String accessToken) {
Long userIdFromToken = jwtLoginProvider.getUserIdFromAccessToken(accessToken);

return userRepository.findByUserId(userIdFromToken)
.orElseThrow(() -> new CustomException(USER_NOT_FOUND));
}

@Transactional
public void validateRefreshToken(User user, String refreshToken) {
TokenStorage tokenStorage = jwtRepository.findByUser(user)
private void validateRefreshToken(User user, String refreshToken) {
RefreshTokenStorage tokenStorage = jwtRepository.findByUser(user)
.orElseThrow(() ->
{
// db에서 row delete 하는 코드 추가
Expand All @@ -77,41 +61,47 @@ public void validateRefreshToken(User user, String refreshToken) {
if (jwtLoginProvider.isExpiredToken(refreshToken, TokenType.REFRESH)) {
// refresh token이 만료된 경우 -> 예외 발생 -> 유저의 재 로그인 유도
// db에서 row delete 하는 코드 추가
jwtRepository.deleteByUser(user);
deleteRefreshTokenStorage(user);

throw new JwtExpiredTokenException(EXPIRED_REFRESH_TOKEN);
}

// TODO 2. refresh token이 db에 실제로 존재하는지 체크
// TODO 2. refresh token이 db에 존재하는 token 값과 일치하는지 확인
if (!tokenStorage.checkTokenValue(refreshToken)) {
// refresh token이 db에 존재하지 않느 경우 -> 유효하지 않은 refresh token이므로 예외 발생
// refresh token이 db에 존재하는 token 값과 일치하지 않는 경우 -> 유효하지 않은 refresh token이므로 예외 발생
// db에서 row delete 하는 코드 추가
jwtRepository.deleteByUser(user);
deleteRefreshTokenStorage(user);

throw new JwtUnauthorizedTokenException(TOKEN_MISMATCH);
}
}

@Transactional
public TokenPairDTO updateTokenPair(User user) {
// TODO 1. new access token, refresh token 발급
String newAccessToken = jwtLoginProvider.generateToken(user, TokenType.ACCESS);
String newRefreshToken = jwtLoginProvider.generateToken(user, TokenType.REFRESH);
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deleteRefreshTokenStorage(User user) {
jwtRepository.deleteByUser(user);
}

// TODO 2. db의 refresh token update
TokenStorage tokenStorage = jwtRepository.findByUser(user)
private TokenPairDTO updateTokenPair(User user) {
RefreshTokenStorage tokenStorage = jwtRepository.findByUser(user)
.orElseThrow(() -> new JwtUnauthorizedTokenException(TOKEN_MISMATCH));

// new access token, new refresh token 발급 받아서
String newAccessToken = jwtLoginProvider.generateToken(user.getUserId(), TokenType.ACCESS);
String newRefreshToken = jwtLoginProvider.generateToken(user.getUserId(), TokenType.REFRESH);

// tokenStorage update 하고
tokenStorage.updateTokenValue(newRefreshToken);

// TODO 3. return
return TokenPairDTO.builder()
.accessToken(newAccessToken)
.refreshToken(newRefreshToken)
.build();

}

public TokenPairDTO provideJwtToOAuthUser(User userByOAuthInfo) {
String accessToken = jwtLoginProvider.generateToken(userByOAuthInfo, TokenType.ACCESS);
String refreshToken = jwtLoginProvider.generateToken(userByOAuthInfo, TokenType.REFRESH);
String accessToken = jwtLoginProvider.generateToken(userByOAuthInfo.getUserId(), TokenType.ACCESS);
String refreshToken = jwtLoginProvider.generateToken(userByOAuthInfo.getUserId(), TokenType.REFRESH);

return TokenPairDTO.builder()
.accessToken(accessToken)
Expand All @@ -121,10 +111,11 @@ public TokenPairDTO provideJwtToOAuthUser(User userByOAuthInfo) {

@Transactional
public void updateRefreshToken(User user, String refreshToken) {
TokenStorage tokenStorage = jwtRepository.findByUser(user)
RefreshTokenStorage tokenStorage = jwtRepository.findByUser(user)
.orElseThrow(() -> new JwtUnauthorizedTokenException(TOKEN_MISMATCH));

tokenStorage.updateTokenValue(refreshToken);
}


}
Original file line number Diff line number Diff line change
@@ -1,24 +1,21 @@
package space.space_spring.entity;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "Token_Storage")
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TokenStorage {
public class RefreshTokenStorage {

@Id @GeneratedValue
@Column(name = "token_storage_id")
private Long tokenStorageId;

@OneToOne
@OneToOne(orphanRemoval = true)
// @Column(name = "user_id")
private User user;

Expand All @@ -33,4 +30,17 @@ public boolean checkTokenValue(String tokenValue) {
return this.tokenValue.equals(tokenValue);
}

@Builder
private RefreshTokenStorage(User user, String tokenValue) {
this.user = user;
this.tokenValue = tokenValue;
}

public static RefreshTokenStorage create(User user, String tokenValue) {
return RefreshTokenStorage.builder()
.user(user)
.tokenValue(tokenValue)
.build();
}

}
Loading

0 comments on commit 8822b5f

Please sign in to comment.