-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[feat] 6차 세미나 과제 코드입니다. #8
Open
choyeongju
wants to merge
2
commits into
main
Choose a base branch
from
#seminar/6
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+382
−7
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
33 changes: 33 additions & 0 deletions
33
practice/src/main/java/org/sopt/practice/auth/redis/config/RedisRepositoryConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package org.sopt.practice.auth.redis.config; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.boot.autoconfigure.data.redis.RedisProperties; | ||
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; | ||
import org.springframework.data.redis.core.RedisTemplate; | ||
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories; | ||
import org.springframework.data.redis.serializer.StringRedisSerializer; | ||
|
||
@Configuration | ||
@RequiredArgsConstructor | ||
@EnableRedisRepositories | ||
public class RedisRepositoryConfig { | ||
|
||
private final RedisProperties redisProperties; | ||
|
||
@Bean | ||
public RedisConnectionFactory redisConnectionFactory() { | ||
return new LettuceConnectionFactory(redisProperties.getHost(), redisProperties.getPort()); | ||
} | ||
|
||
@Bean | ||
public RedisTemplate<String, Object> redisTemplate() { | ||
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); | ||
redisTemplate.setConnectionFactory(redisConnectionFactory()); | ||
redisTemplate.setKeySerializer(new StringRedisSerializer()); | ||
redisTemplate.setValueSerializer(new StringRedisSerializer()); | ||
return redisTemplate; | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
practice/src/main/java/org/sopt/practice/auth/redis/controller/TokenController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package org.sopt.practice.auth.redis.controller; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.sopt.practice.auth.PrincipalHandler; | ||
import org.sopt.practice.auth.redis.service.RedisTokenService; | ||
import org.sopt.practice.service.dto.AccessTokenDto; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.transaction.annotation.Transactional; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
@Transactional | ||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/v1/member") | ||
public class TokenController { | ||
|
||
private final RedisTokenService redisTokenService; | ||
private final PrincipalHandler principalHandler; | ||
|
||
@PostMapping("/refresh-token") | ||
public ResponseEntity<AccessTokenDto> refreshToken(){ | ||
Long userId = principalHandler.getUserIdFromPrincipal(); | ||
AccessTokenDto newAccessTokenResponse = redisTokenService.refreshToken(userId); | ||
return ResponseEntity.status(HttpStatus.CREATED) | ||
.body(newAccessTokenResponse); | ||
} | ||
} | ||
33 changes: 33 additions & 0 deletions
33
practice/src/main/java/org/sopt/practice/auth/redis/domain/Token.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package org.sopt.practice.auth.redis.domain; | ||
|
||
import jakarta.persistence.Id; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import org.springframework.data.redis.core.RedisHash; | ||
import org.springframework.data.redis.core.index.Indexed; | ||
|
||
@RedisHash(value = "", timeToLive = 60 * 60 * 24 * 1000L * 14) //TTL 설정 | ||
//value = ""이렇게 하면, 객체가 Redis에 저장될 때 클래스의 전체 이름이 해시 키로 사용 | ||
|
||
@AllArgsConstructor | ||
@Getter | ||
@Builder | ||
public class Token { | ||
|
||
@Id | ||
private Long id; | ||
|
||
@Indexed //Redis에서 Indexed 어노테이션 사용 시 이 값으로 객체 값 찾을 수 있다. 주로 검색 조건으로 사용되는 필드에 적용! | ||
private String refreshToken; | ||
|
||
public static Token of( | ||
final Long id, | ||
final String refreshToken | ||
){ | ||
return Token.builder() | ||
.id(id) | ||
.refreshToken(refreshToken) | ||
.build(); | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
practice/src/main/java/org/sopt/practice/auth/redis/repository/RedisTokenRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package org.sopt.practice.auth.redis.repository; | ||
|
||
import org.sopt.practice.auth.redis.domain.Token; | ||
import org.springframework.data.repository.CrudRepository; | ||
|
||
import java.util.Optional; | ||
|
||
public interface RedisTokenRepository extends CrudRepository<Token, Long> { | ||
/* | ||
* Optional은 메소드의 결과가 null이 될 수 있으며, null에 의해 오류가 발생할 가능성이 매우 높을 때 반환값으로만 사용 | ||
* */ | ||
|
||
Optional<Token> findByRefreshToken(final String refreshToken); | ||
Optional<Token> findById(final Long id); | ||
} |
42 changes: 42 additions & 0 deletions
42
practice/src/main/java/org/sopt/practice/auth/redis/service/RedisTokenService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package org.sopt.practice.auth.redis.service; | ||
|
||
import jakarta.transaction.Transactional; | ||
import lombok.RequiredArgsConstructor; | ||
import org.sopt.practice.auth.redis.domain.Token; | ||
import org.sopt.practice.auth.redis.repository.RedisTokenRepository; | ||
import org.sopt.practice.common.dto.ErrorMessage; | ||
import org.sopt.practice.common.jwt.JwtTokenProvider; | ||
import org.sopt.practice.common.jwt.JwtValidationType; | ||
import org.sopt.practice.exception.UnauthorizedException; | ||
import org.sopt.practice.service.dto.AccessTokenDto; | ||
import org.sopt.practice.auth.UserAuthentication; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class RedisTokenService { | ||
|
||
private final RedisTokenRepository redisTokenRepository; | ||
private final JwtTokenProvider jwtTokenProvider; | ||
|
||
@Transactional | ||
public AccessTokenDto refreshToken(Long userId) { | ||
// Redis에서 Refresh Token을 조회 | ||
Token token = redisTokenRepository.findById(userId) | ||
.orElseThrow(() -> new UnauthorizedException(ErrorMessage.REFRESH_TOKEN_NOT_FOUND)); | ||
|
||
// Refresh Token 검증 | ||
JwtValidationType validationType = jwtTokenProvider.validateToken(token.getRefreshToken()); | ||
if (validationType == JwtValidationType.EXPIRED_JWT_TOKEN) { | ||
throw new UnauthorizedException(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION); | ||
} else if (validationType != JwtValidationType.VALID_JWT) { | ||
throw new UnauthorizedException(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION); | ||
} | ||
|
||
// 새로운 Access Token 발급 | ||
String newAccessToken = jwtTokenProvider.newAccessToken(token.getRefreshToken()); | ||
|
||
// 새로운 Access Token을 포함한 응답 객체 반환 | ||
return AccessTokenDto.of(newAccessToken); | ||
} | ||
} |
97 changes: 97 additions & 0 deletions
97
practice/src/main/java/org/sopt/practice/common/jwt/JwtTokenProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package org.sopt.practice.common.jwt; | ||
|
||
import io.jsonwebtoken.*; | ||
import io.jsonwebtoken.security.Keys; | ||
import lombok.RequiredArgsConstructor; | ||
import org.sopt.practice.auth.UserAuthentication; | ||
import org.sopt.practice.service.dto.UserJoinResponse; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.beans.factory.annotation.Value; | ||
|
||
import javax.crypto.SecretKey; | ||
import java.util.Base64; | ||
import java.util.Date; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtTokenProvider { | ||
|
||
private static final String USER_ID = "userId"; | ||
|
||
//AccessToken 관련 로직 | ||
private static final Long ACCESS_TOKEN_EXPIRATION_TIME = 24 * 60 * 60 * 1000L * 2; | ||
|
||
@Value("${jwt.secret}") //application.yml 파일에 설정한 암호화 키를 가져옴 | ||
private String JWT_SECRET; | ||
|
||
|
||
public String issueAccessToken(final Authentication authentication) { | ||
return generateToken(authentication, ACCESS_TOKEN_EXPIRATION_TIME); | ||
} | ||
|
||
// RefreshToken 관련 로직 | ||
private static final Long REFRESH_TOKEN_EXPIRATION_TIME = 60 * 60 * 24 * 1000L * 14; | ||
|
||
public String issueRefreshToken(final Authentication authentication) { | ||
return generateToken(authentication, REFRESH_TOKEN_EXPIRATION_TIME); | ||
} | ||
|
||
public String generateToken(Authentication authentication, Long tokenExpirationTime) { | ||
final Date now = new Date(); | ||
final Claims claims = Jwts.claims() | ||
.setIssuedAt(now) | ||
.setExpiration(new Date(now.getTime() + tokenExpirationTime)); // 만료 시간 | ||
|
||
claims.put(USER_ID, authentication.getPrincipal()); | ||
|
||
return Jwts.builder() | ||
.setHeaderParam(Header.TYPE, Header.JWT_TYPE) // Header | ||
.setClaims(claims) // Claim | ||
.signWith(getSigningKey()) // Signature | ||
.compact(); | ||
} | ||
|
||
private SecretKey getSigningKey() { | ||
String encodedKey = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes()); //SecretKey 통해 서명 생성 | ||
return Keys.hmacShaKeyFor(encodedKey.getBytes()); //일반적으로 HMAC (Hash-based Message Authentication Code) 알고리즘 사용 | ||
} | ||
|
||
public JwtValidationType validateToken(String token) { | ||
try { | ||
final Claims claims = getBody(token); | ||
return JwtValidationType.VALID_JWT; | ||
} catch (MalformedJwtException ex) { | ||
return JwtValidationType.INVALID_JWT_TOKEN; | ||
} catch (ExpiredJwtException ex) { | ||
return JwtValidationType.EXPIRED_JWT_TOKEN; | ||
} catch (UnsupportedJwtException ex) { | ||
return JwtValidationType.UNSUPPORTED_JWT_TOKEN; | ||
} catch (IllegalArgumentException ex) { | ||
return JwtValidationType.EMPTY_JWT; | ||
} | ||
} | ||
|
||
private Claims getBody(final String token) { | ||
return Jwts.parserBuilder() | ||
.setSigningKey(getSigningKey()) | ||
.build() | ||
.parseClaimsJws(token) | ||
.getBody(); | ||
} | ||
|
||
public Long getUserFromJwt(String token) { | ||
Claims claims = getBody(token); | ||
return Long.valueOf(claims.get(USER_ID).toString()); | ||
} | ||
|
||
// 리프레시 토큰이 유효하면 새로운 access token 발급 | ||
public String newAccessToken(String refreshToken){ | ||
Claims claims = getBody(refreshToken); | ||
Long userId = Long.valueOf(claims.get(USER_ID).toString()); | ||
|
||
Authentication authentication = UserAuthentication.createUserAuthentication(userId); | ||
return issueAccessToken(authentication); | ||
} | ||
|
||
} |
63 changes: 63 additions & 0 deletions
63
practice/src/main/java/org/sopt/practice/controller/BlogController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package org.sopt.practice.controller; | ||
|
||
import jakarta.validation.Valid; | ||
import lombok.RequiredArgsConstructor; | ||
import org.sopt.practice.auth.PrincipalHandler; | ||
import org.sopt.practice.common.dto.SuccessMessage; | ||
import org.sopt.practice.common.dto.SuccessStatusResponse; | ||
import org.sopt.practice.service.BlogService; | ||
import org.sopt.practice.service.dto.BlogCreateRequest; | ||
import org.sopt.practice.service.dto.BlogTitleUpdateRequest; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.net.URI; | ||
|
||
@RestController | ||
@RequestMapping("/api/v1") | ||
@RequiredArgsConstructor | ||
public class BlogController { | ||
|
||
private final BlogService blogService; | ||
|
||
// @PostMapping("/blog") | ||
// public ResponseEntity<SuccessStatusResponse> createBlog( | ||
// @RequestHeader(name = "memberId") Long memberId, | ||
// @RequestBody BlogCreateRequest blogCreateRequest | ||
// ) { | ||
// return ResponseEntity.status(HttpStatus.CREATED).header( | ||
// "Location", | ||
// blogService.create(memberId, blogCreateRequest)) | ||
// .body(SuccessStatusResponse.of(SuccessMessage.BLOG_CREATE_SUCCESS)); | ||
// } | ||
|
||
//5.18 | ||
private final PrincipalHandler principalHandler; | ||
// | ||
// @PostMapping("/blog") | ||
// public ResponseEntity createBlog( | ||
// BlogCreateRequest blogCreateRequest | ||
// ) { | ||
// return ResponseEntity.created(URI.create(blogService.create( | ||
// principalHandler.getUserIdFromPrincipal(), blogCreateRequest))).build(); | ||
// } | ||
|
||
//5.18 | ||
@PostMapping("/blog") | ||
public ResponseEntity createBlog( | ||
BlogCreateRequest blogCreateRequest | ||
) { | ||
return ResponseEntity.created(URI.create(blogService.create( | ||
principalHandler.getUserIdFromPrincipal(), blogCreateRequest))).build(); | ||
} | ||
|
||
@PatchMapping("/blog/{blogId}/title") | ||
public ResponseEntity updateBlogTitle( | ||
@PathVariable Long blogId, | ||
@Valid @RequestBody BlogTitleUpdateRequest blogTitleUpdateRequest | ||
) { | ||
blogService.updateTitle(blogId, blogTitleUpdateRequest); | ||
return ResponseEntity.noContent().build(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위의 메서드에서 @RequestBody로 RefreshToken을 받도록 수정하면 좀 더 안전하게 토큰을 갱신할 수 있을 것 같다는 생각이 드네요..!!