diff --git a/week02/seminar/demo/src/main/java/com/example/demo/auth/SecurityConfig.java b/week02/seminar/demo/src/main/java/com/example/demo/auth/SecurityConfig.java index 69db4ac..18ef89c 100644 --- a/week02/seminar/demo/src/main/java/com/example/demo/auth/SecurityConfig.java +++ b/week02/seminar/demo/src/main/java/com/example/demo/auth/SecurityConfig.java @@ -21,7 +21,7 @@ public class SecurityConfig { private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; private final CustomAccessDeniedHandler customAccessDeniedHandler; - private static final String[] AUTH_WHITE_LIST = {"/api/v1/members"}; + private static final String[] AUTH_WHITE_LIST = {"/api/v1/members", "/api/v1/members/regenerate-token"}; @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { diff --git a/week02/seminar/demo/src/main/java/com/example/demo/controller/MemberController.java b/week02/seminar/demo/src/main/java/com/example/demo/controller/MemberController.java index d512405..667cf81 100644 --- a/week02/seminar/demo/src/main/java/com/example/demo/controller/MemberController.java +++ b/week02/seminar/demo/src/main/java/com/example/demo/controller/MemberController.java @@ -1,6 +1,9 @@ package com.example.demo.controller; +import com.example.demo.common.dto.SuccessMessage; +import com.example.demo.common.dto.SuccessStatusResponse; import com.example.demo.service.MemberService; +import com.example.demo.service.dto.member.RegenerateAccessTokenDto; import com.example.demo.service.dto.member.UserJoinResponse; import com.example.demo.service.dto.member.MemberCreateDto; import com.example.demo.service.dto.member.MemberFindDto; @@ -12,6 +15,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -23,15 +27,23 @@ public class MemberController { private final MemberService memberService; @PostMapping - public ResponseEntity postMember( + public ResponseEntity postMember( @RequestBody MemberCreateDto memberCreate ) { UserJoinResponse userJoinResponse = memberService.createMember(memberCreate); return ResponseEntity.status(HttpStatus.CREATED) .header("Location", userJoinResponse.userId()) - .body( - userJoinResponse - ); + .body(SuccessStatusResponse.of (SuccessMessage.MEMBER_SING_UP_SUCCESS, userJoinResponse)); + } + + @PostMapping("/regenerate-token") + public ResponseEntity regenerateToken( + @RequestHeader(name = "Authorization-refreshToken") String refreshToken + ) { + return ResponseEntity.ok() + .body(SuccessStatusResponse.of( + SuccessMessage.ACCESS_TOKEN_REGENERATE_SUCCESS, + memberService.getNewAccessToken(refreshToken))); } @GetMapping("/{memberId}") diff --git a/week02/seminar/demo/src/main/java/com/example/demo/repository/RedisTokenRepository.java b/week02/seminar/demo/src/main/java/com/example/demo/repository/RedisTokenRepository.java new file mode 100644 index 0000000..eaf8348 --- /dev/null +++ b/week02/seminar/demo/src/main/java/com/example/demo/repository/RedisTokenRepository.java @@ -0,0 +1,11 @@ +package com.example.demo.repository; + +import com.example.demo.auth.redis.domain.Token; +import java.util.Optional; +import org.springframework.data.repository.CrudRepository; + +public interface RedisTokenRepository extends CrudRepository { + + Optional findByRefreshToken(final String refreshToken); + Optional findById(final String id); +} diff --git a/week02/seminar/demo/src/main/java/com/example/demo/service/MemberService.java b/week02/seminar/demo/src/main/java/com/example/demo/service/MemberService.java index 1d23281..f6d0325 100644 --- a/week02/seminar/demo/src/main/java/com/example/demo/service/MemberService.java +++ b/week02/seminar/demo/src/main/java/com/example/demo/service/MemberService.java @@ -1,17 +1,20 @@ package com.example.demo.service; import com.example.demo.auth.UserAuthentication; +import com.example.demo.auth.redis.domain.Token; import com.example.demo.common.dto.ErrorMessage; import com.example.demo.common.jwt.JwtTokenProvider; import com.example.demo.domain.Member; import com.example.demo.exception.NotFoundException; +import com.example.demo.exception.UnauthorizedException; import com.example.demo.repository.MemberRepository; +import com.example.demo.repository.RedisTokenRepository; +import com.example.demo.service.dto.member.RegenerateAccessTokenDto; import com.example.demo.service.dto.member.UserJoinResponse; import com.example.demo.service.dto.member.MemberCreateDto; import com.example.demo.service.dto.member.MemberFindDto; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; -import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Lettuce.Cluster.Refresh; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -21,6 +24,7 @@ public class MemberService { private final MemberRepository memberRepository; private final JwtTokenProvider jwtTokenProvider; + private final RedisTokenRepository redisTokenRepository; @Transactional public UserJoinResponse createMember( @@ -36,15 +40,39 @@ public UserJoinResponse createMember( String refreshToekn = jwtTokenProvider.issueRefreshToken( UserAuthentication.createUserAuthentication(memberId) ); + + //리프레시 토큰 레디스에 저장(idx는 memberId로..) + redisTokenRepository.save(Token.of(memberId, refreshToekn)); + return UserJoinResponse.of( memberId.toString(), accessToken, refreshToekn); } + @Transactional + public RegenerateAccessTokenDto getNewAccessToken(String refreshToken) { + + // 리프레시 토큰이 redis에 존재하는지 확인하기. 존재하면 만료되지 않은 것임.(일정 시간 후 사라지므로) + // 없으면 다시 로그인하라는 에러메시지 보내기 + Token token = getRefreshToken(refreshToken); + + // 존재하는 토큰이 유효한 토큰인지(제대로 된 토큰인지) 다시 검증 + jwtTokenProvider.validateToken(refreshToken); + + // 유효하다면 액세스 토큰 재발급 + String accessToken = jwtTokenProvider.issueAccessToken( + UserAuthentication.createUserAuthentication(token.getId()) + ); + + return RegenerateAccessTokenDto.of(accessToken); + } + + @Transactional(readOnly = true) public Member findById(Long memberId) { return memberRepository.findById(memberId).orElseThrow( () -> new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND_BY_ID_EXCEPTION)); } + @Transactional(readOnly = true) public Member findMemberById( Long memberId ) { @@ -61,9 +89,13 @@ public MemberFindDto findMemberById2(Long memberId) { @Transactional public void deleteMemberById(Long memberId) { - Member member = memberRepository.findById(memberId).orElseThrow(() -> new EntityNotFoundException("ID에 해당하는 사용자가 존재하지 않습니다.")); memberRepository.delete(member); } + + public Token getRefreshToken(String refreshToken) { + return redisTokenRepository.findByRefreshToken(refreshToken).orElseThrow( + () -> new UnauthorizedException(ErrorMessage.JWT_UNAUTHORIZED_REFRESH_EXPIRED_EXCEPTION)); + } }