Skip to content

Commit

Permalink
Merge pull request #28 from Team-J9/feature/token
Browse files Browse the repository at this point in the history
토큰 로직 수정
  • Loading branch information
SJ70 authored Jul 25, 2024
2 parents 8240b71 + c9246fc commit 9a1dfe1
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 94 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
implementation 'com.google.auth:google-auth-library-oauth2-http:1.4.0'
implementation 'com.google.cloud:google-cloud-storage:2.1.5'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/j9/bestmoments/config/RedisConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.j9.bestmoments.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;

@Configuration
public class RedisConfig {

@Value("${redis.host}")
private String host;

@Value("${redis.port}")
private int port;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}

}
11 changes: 11 additions & 0 deletions src/main/java/com/j9/bestmoments/constants/TokenExpiration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.j9.bestmoments.constants;

public final class TokenExpiration {

// 10분
public static final int ACCESS_TOKEN = 60 * 10;

// 60분
public static final int REFRESH_TOKEN = 60 * 60;

}
43 changes: 19 additions & 24 deletions src/main/java/com/j9/bestmoments/domain/Token.java
Original file line number Diff line number Diff line change
@@ -1,40 +1,35 @@
package com.j9.bestmoments.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import java.util.UUID;
import lombok.Getter;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.TimeToLive;

@Entity
@Getter
@NoArgsConstructor
public class Token {
@RedisHash(value = "Token")
public
class Token {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String token;

@ManyToOne
@JoinColumn(name = "member_id", referencedColumnName = "id")
private Member member;
private TokenType tokenType;

private String refreshToken;
private String accessToken;
@TimeToLive
private Long expiration;

@Builder
public Token(Member member, String refreshToken, String accessToken) {
this.member = member;
this.refreshToken = refreshToken;
this.accessToken = accessToken;
}
private UUID memberId;

public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
@Builder
public Token(Member member, TokenType tokenType, String token) {
this.memberId = member.getId();
this.token = token;
this.tokenType = tokenType;
this.expiration = tokenType.getExpiration();
}

}
18 changes: 18 additions & 0 deletions src/main/java/com/j9/bestmoments/domain/TokenType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.j9.bestmoments.domain;

import com.j9.bestmoments.constants.TokenExpiration;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum TokenType {

ACCESS_TOKEN("accessToken", TokenExpiration.ACCESS_TOKEN),
REFRESH_TOKEN("refreshToken", TokenExpiration.REFRESH_TOKEN),
;

private final String name;
private final long expiration;

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.time.LocalDateTime;

public record LoginDto(
String grantType,
String accessToken,
String refreshToken,
LocalDateTime deletedAt
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
if (token != null && jwtTokenProvider.validateToken(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
tokenService.checkExpired(token);
tokenService.findByToken(token);
}

chain.doFilter(request, response);
Expand Down
25 changes: 4 additions & 21 deletions src/main/java/com/j9/bestmoments/jwt/JwtTokenProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Collections;
import java.util.Date;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
Expand All @@ -26,40 +27,22 @@ public class JwtTokenProvider {

private final Key key;

@Value("${jwt.accessTokenExpirationMs}")
private long accessTokenExpirationMs;

@Value("${jwt.refreshTokenExpirationMs}")
private long refreshTokenExpirationMs;

// secret 값을 암호화 (SHA 키 생성)
public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}

public String generateAccessToken(Member member) {
Date now = new Date();
Date accessTokenExpiresIn = new Date(now.getTime() + accessTokenExpirationMs);
return Jwts.builder()
.claim("id", member.getId())
.claim("role", member.getRole().getValue())
.setIssuedAt(now)
.setExpiration(accessTokenExpiresIn)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
}

public String generateRefreshToken(Member member) {
Date now = new Date();
Date refreshTokenExpiresIn = new Date(now.getTime() + refreshTokenExpirationMs);
return Jwts.builder()
.claim("id", member.getId())
.claim("role", member.getRole().getValue())
.setIssuedAt(now)
.setExpiration(refreshTokenExpiresIn)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
return UUID.randomUUID().toString();
}

// 토큰을 복호화하여 인증 정보 추출
Expand All @@ -71,7 +54,7 @@ public Authentication getAuthentication(String accessToken) {
.getBody();

if (claims.get("id") == null || claims.get("role") == null) {
throw new RuntimeException("권한 정보가 없는 토큰입니다.");
throw new AccessDeniedException("권한 정보가 없는 토큰입니다.");
}

String id = claims.get("id").toString();
Expand Down
10 changes: 4 additions & 6 deletions src/main/java/com/j9/bestmoments/repository/TokenRepository.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package com.j9.bestmoments.repository;

import com.j9.bestmoments.domain.Token;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

public interface TokenRepository extends JpaRepository<Token, Long> {

Optional<Token> findByAccessToken(String accessToken);
Optional<Token> findByRefreshToken(String refreshToken);
@Repository
public interface TokenRepository extends CrudRepository<Token, String> {

}
79 changes: 38 additions & 41 deletions src/main/java/com/j9/bestmoments/service/TokenService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.j9.bestmoments.domain.Member;
import com.j9.bestmoments.domain.Token;
import com.j9.bestmoments.domain.TokenType;
import com.j9.bestmoments.dto.response.LoginDto;
import com.j9.bestmoments.jwt.JwtTokenProvider;
import com.j9.bestmoments.repository.TokenRepository;
Expand All @@ -12,69 +13,65 @@
import org.springframework.transaction.annotation.Transactional;

@Service
@Slf4j
@RequiredArgsConstructor
@Slf4j
public class TokenService {

private final TokenRepository tokenRepository;
private final JwtTokenProvider jwtTokenProvider;
private final MemberService memberService;

@Transactional
public LoginDto create(Member member) {
String accessToken = jwtTokenProvider.generateAccessToken(member);
String refreshToken = jwtTokenProvider.generateRefreshToken(member);
Token token = Token.builder()
.member(member)
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
tokenRepository.save(token);
log.error(token.getAccessToken());
log.error(token.getRefreshToken());
log.error(tokenRepository.findAll().get(0).getAccessToken());
return new LoginDto("Bearer", accessToken, refreshToken, member.getDeletedAt());
}

public Token findByAnyToken(String token) {
return tokenRepository.findByAccessToken(resolveToken(token))
.or(() -> tokenRepository.findByRefreshToken(resolveToken(token)))
.orElseThrow(() -> new AccessDeniedException("존재하지 않거나 만료된 토큰입니다."));
}

public Token findByAccessToken(String accessToken) {
return tokenRepository.findByAccessToken(resolveToken(accessToken))
.orElseThrow(() -> new AccessDeniedException("존재하지 않거나 만료된 액세스 토큰입니다."));
String accessToken = createAccessToken(member);
String refreshToken = createRefreshToken(member);
log.info("토큰 발급됨\n - accessToken: {}\n - refreshToken: {}", accessToken, refreshToken);
this.findByToken(accessToken);
return new LoginDto(accessToken, refreshToken, member.getDeletedAt());
}

public Token findByRefreshToken(String refreshToken) {
return tokenRepository.findByRefreshToken(resolveToken(refreshToken))
.orElseThrow(() -> new AccessDeniedException("존재하지 않거나 만료된 리프래시 토큰입니다."));
private String createAccessToken(Member member) {
String token = jwtTokenProvider.generateAccessToken(member);
Token accessToken = Token.builder()
.member(member)
.tokenType(TokenType.ACCESS_TOKEN)
.token(token)
.build();
tokenRepository.save(accessToken);
return token;
}

public void checkExpired(String token) {
findByAnyToken(token);
private String createRefreshToken(Member member) {
String token = jwtTokenProvider.generateRefreshToken(member);
Token refreshToken = Token.builder()
.member(member)
.tokenType(TokenType.REFRESH_TOKEN)
.token(token)
.build();
tokenRepository.save(refreshToken);
return token;
}

@Transactional
public void expire(String token) {
Token foundToken = findByAnyToken(token);
Token foundToken = this.findByToken(token);
tokenRepository.delete(foundToken);
}

@Transactional
public String refresh(String refreshToken) {
Token foundToken = findByRefreshToken(refreshToken);
String accessToken = jwtTokenProvider.generateAccessToken(foundToken.getMember());
foundToken.setAccessToken(accessToken);
tokenRepository.save(foundToken);
return accessToken;
public Token findByToken(String token) {
return tokenRepository.findById(token)
.orElseThrow(() -> new AccessDeniedException("만료되거나 발급되지 않은 토큰입니다."));
}

private String resolveToken(String token) {
if (token.startsWith("Bearer")) {
return token.substring(7);
@Transactional
public String refresh(String refreshToken) {
Token foundToken = this.findByToken(refreshToken);
if (!foundToken.getTokenType().equals(TokenType.REFRESH_TOKEN)) {
throw new AccessDeniedException("만료되거나 발급되지 않은 토큰입니다.");
}
return token;
Member member = memberService.findById(foundToken.getMemberId());
String newAccessToken = createAccessToken(member);
return newAccessToken;
}

}

0 comments on commit 9a1dfe1

Please sign in to comment.