Skip to content
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주차 실습 과제 구현 #16

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
4 changes: 3 additions & 1 deletion week3/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'

runtimeOnly 'com.h2database:h2'
// implementation group: 'org.postgresql',name:'postgresql',version:'42.7.3'

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
testImplementation 'io.rest-assured:rest-assured'
Expand All @@ -46,6 +45,9 @@ dependencies {
//Multipart file
implementation("software.amazon.awssdk:bom:2.21.0")
implementation("software.amazon.awssdk:s3:2.21.0")

//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.3.1.RELEASE'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ public enum ErrorMessage {

//401
JWT_UNAUTHORIZED_EXCEPTION(HttpStatus.UNAUTHORIZED.value(), "사용자의 로그인 검증을 실패했습니다."),

//404
REFRESH_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND.value(),"refresh 토큰이 존재하지 않습니다."),
;
private final int status;
private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.sopt.seminar.config.auth.redis.Service;

import com.sopt.seminar.config.auth.UserAuthentication;
import com.sopt.seminar.config.auth.redis.domain.Token;
import com.sopt.seminar.config.auth.redis.repository.TokenRepository;
import com.sopt.seminar.config.jwt.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
public class TokenService {

private final TokenRepository tokenRepository;
private final JwtTokenProvider jwtTokenProvider;

@Transactional
public void saveRefreshToken(
final Long userId,
final String refreshToken
) {
tokenRepository.save(
Token.of(
userId,
refreshToken
)
);
}

public String reissueAccessTokenByRefreshToken(
Long userId
){
String newAccessToken = jwtTokenProvider.issueAccessToken(UserAuthentication.createUserAuthentication(userId));
return newAccessToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.sopt.seminar.config.auth.redis.controller;

import com.sopt.seminar.common.dto.ApiResponse;
import com.sopt.seminar.common.dto.SuccessMessage;
import com.sopt.seminar.config.auth.PrincipalHandler;
import com.sopt.seminar.config.auth.redis.Service.TokenService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class TokenController {

private final TokenService tokenService;
private final PrincipalHandler principalHandler;

@PostMapping("api/v1/refresh-token")
public ApiResponse reissueAccessToken(){
return ApiResponse.success(
SuccessMessage.ACCESS_TOKEN_REFRESH_SUCCESS,
tokenService.reissueAccessTokenByRefreshToken(principalHandler.getUserIdFromPrincipal()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.sopt.seminar.config.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)
@AllArgsConstructor
@Getter
@Builder
public class Token {
@Id
private Long id;

@Indexed
private String refreshToken;

public static Token of(
final Long id,
final String refreshToken
){
return Token.builder()
.id(id)
.refreshToken(refreshToken)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.sopt.seminar.config.auth.redis.repository;

import com.sopt.seminar.config.auth.redis.domain.Token;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;

public interface TokenRepository extends CrudRepository<Token, String> {
Optional<Token> findByRefreshToken(final String refreshToken);
Optional<Token> findById(final Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.sopt.seminar.config.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;
import java.util.Base64;
import java.util.Date;
import javax.crypto.SecretKey;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

private static final String USER_ID = "userId";

private static final Long ACCESS_TOKEN_EXPIRATION_TIME = 30 * 60L;
private static final Long REFRESH_TOKEN_EXPIRATION_TIME = 24 * 60 * 60 * 1000L * 14;

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

public String issueAccessToken(final Authentication authentication) {
return generateToken(authentication, ACCESS_TOKEN_EXPIRATION_TIME);
}

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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.sopt.seminar.config.jwt;

public enum JwtValidationType {
VALID_JWT, // 유효한 JWT
INVALID_JWT_SIGNATURE, // 유효하지 않은 서명
INVALID_JWT_TOKEN, // 유효하지 않은 토큰
EXPIRED_JWT_TOKEN, // 만료된 토큰
UNSUPPORTED_JWT_TOKEN, // 지원하지 않는 형식의 토큰
EMPTY_JWT // 빈 JWT
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,6 @@ public ResponseEntity createBlog(
principalHandler.getUserIdFromPrincipal(), blogCreateRequest))).build();
}

// @PostMapping("/blog")
// public ResponseEntity createBlog(
// BlogCreateRequest blogCreateRequest
// ) {
// return ResponseEntity.created(URI.create(blogService.create(
// principalHandler.getUserIdFromPrincipal(), blogCreateRequest))).build();
// }

// @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));
// }

@PatchMapping("/blog/{blogId}/title")
public ResponseEntity updateBlogTitle(
@PathVariable Long blogId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ public class MemberController {

private final MemberService memberService;

// @PostMapping
// public ResponseEntity createMember(@RequestBody MemberCreateDto memberCreate) {
// return ResponseEntity.created(URI.create(memberService.createMember(memberCreate))).build();
// }

@GetMapping("/{memberId}")
public ResponseEntity<MemberFindDto> findMemberById(@PathVariable Long memberId){
return ResponseEntity.ok(memberService.findMemberById(memberId));
Expand Down
19 changes: 8 additions & 11 deletions week3/src/main/java/com/sopt/seminar/service/MemberService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.sopt.seminar.service;

import com.sopt.seminar.config.auth.UserAuthentication;
import com.sopt.seminar.common.dto.ErrorMessage;
import com.sopt.seminar.config.auth.UserAuthentication;
import com.sopt.seminar.config.auth.redis.Service.TokenService;
import com.sopt.seminar.config.jwt.JwtTokenProvider;
import com.sopt.seminar.domain.Member;
import com.sopt.seminar.exception.NotFoundException;
Expand All @@ -21,15 +22,7 @@ public class MemberService {

private final MemberRepository memberRepository;
private final JwtTokenProvider jwtTokenProvider;

// @Transactional
// public String createMember(
// MemberCreateDto memberCreateDto
// ) {
// Member member = Member.create(memberCreateDto.name(), memberCreateDto.part(), memberCreateDto.age());
// memberRepository.save(member);
// return member.getId().toString();
// }
private final TokenService tokenService;

@Transactional
public UserJoinResponse createMember(
Expand All @@ -42,7 +35,11 @@ public UserJoinResponse createMember(
String accessToken = jwtTokenProvider.issueAccessToken(
UserAuthentication.createUserAuthentication(memberId)
);
return UserJoinResponse.of(accessToken, memberId.toString());
String refreshToken = jwtTokenProvider.issueRefreshToken(
UserAuthentication.createUserAuthentication(memberId)
);
tokenService.saveRefreshToken(memberId, refreshToken);
return UserJoinResponse.of(accessToken,refreshToken, memberId.toString());
}

public MemberFindDto findMemberById(Long memberId) {
Expand Down