Skip to content

Commit f285310

Browse files
authored
feat: 토큰 재발급 구현
- 관련 엔드포인트 추가 - 토큰 재발급 경로에 대해 인증 상태 요구 -> JwtFilter를 거쳐 재발급 처리 (토큰 검증 로직 간소화)
1 parent c7a82b7 commit f285310

File tree

5 files changed

+49
-11
lines changed

5 files changed

+49
-11
lines changed

src/main/java/dmu/dasom/api/domain/member/controller/MemberController.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import dmu.dasom.api.domain.common.exception.ErrorResponse;
44
import dmu.dasom.api.domain.member.dto.SignupRequestDto;
55
import dmu.dasom.api.domain.member.service.MemberService;
6+
import dmu.dasom.api.global.auth.dto.TokenBox;
7+
import dmu.dasom.api.global.auth.userdetails.UserDetailsImpl;
68
import io.swagger.v3.oas.annotations.Operation;
79
import io.swagger.v3.oas.annotations.media.Content;
810
import io.swagger.v3.oas.annotations.media.ExampleObject;
@@ -11,11 +13,10 @@
1113
import io.swagger.v3.oas.annotations.responses.ApiResponses;
1214
import jakarta.validation.Valid;
1315
import lombok.RequiredArgsConstructor;
16+
import org.springframework.http.HttpHeaders;
1417
import org.springframework.http.ResponseEntity;
15-
import org.springframework.web.bind.annotation.PostMapping;
16-
import org.springframework.web.bind.annotation.RequestBody;
17-
import org.springframework.web.bind.annotation.RequestMapping;
18-
import org.springframework.web.bind.annotation.RestController;
18+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
19+
import org.springframework.web.bind.annotation.*;
1920

2021
@RestController
2122
@RequestMapping("/api")
@@ -39,16 +40,32 @@ public class MemberController {
3940
),
4041
@ExampleObject(
4142
name = "이메일 또는 비밀번호 형식 올바르지 않음",
42-
value = "{ \"code\": \"C007\", \"message\": \"요청한 값이 올바르지 않습니다.\" }"
43-
)
44-
}
45-
)
46-
)
47-
})
43+
value = "{ \"code\": \"C007\", \"message\": \"요청한 값이 올바르지 않습니다.\" }")}))})
4844
@PostMapping("/auth/signup")
4945
public ResponseEntity<Void> signUp(@Valid @RequestBody final SignupRequestDto request) {
5046
memberService.signUp(request);
5147
return ResponseEntity.ok().build();
5248
}
5349

50+
@Operation(summary = "토큰 갱신")
51+
@ApiResponses(value = {
52+
@ApiResponse(responseCode = "200", description = "토큰 갱신 성공 (Header로 토큰 반환)"),
53+
@ApiResponse(responseCode = "400", description = "실패 케이스",
54+
content = @Content(
55+
mediaType = "application/json",
56+
schema = @Schema(implementation = ErrorResponse.class),
57+
examples = {
58+
@ExampleObject(
59+
name = "RefreshToken 만료",
60+
value = "{ \"code\": \"C004\", \"message\": \"토큰이 만료되었습니다.\" }")}))})
61+
@GetMapping("/auth/rotation")
62+
public ResponseEntity<Void> tokenRotation(@AuthenticationPrincipal final UserDetailsImpl userDetails) {
63+
final TokenBox tokenBox = memberService.tokenRotation(userDetails);
64+
final HttpHeaders headers = new HttpHeaders();
65+
headers.add("Access-Token", tokenBox.getAccessToken());
66+
headers.add("Refresh-Token", tokenBox.getRefreshToken());
67+
68+
return ResponseEntity.ok().headers(headers).build();
69+
}
70+
5471
}

src/main/java/dmu/dasom/api/domain/member/service/MemberService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import dmu.dasom.api.domain.member.dto.SignupRequestDto;
44
import dmu.dasom.api.domain.member.entity.Member;
5+
import dmu.dasom.api.global.auth.dto.TokenBox;
6+
import dmu.dasom.api.global.auth.userdetails.UserDetailsImpl;
57

68
public interface MemberService {
79

@@ -11,4 +13,6 @@ public interface MemberService {
1113

1214
void signUp(final SignupRequestDto request);
1315

16+
TokenBox tokenRotation(final UserDetailsImpl userDetails);
17+
1418
}

src/main/java/dmu/dasom/api/domain/member/service/MemberServiceImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import dmu.dasom.api.domain.member.dto.SignupRequestDto;
66
import dmu.dasom.api.domain.member.entity.Member;
77
import dmu.dasom.api.domain.member.repository.MemberRepository;
8+
import dmu.dasom.api.global.auth.dto.TokenBox;
9+
import dmu.dasom.api.global.auth.jwt.JwtUtil;
10+
import dmu.dasom.api.global.auth.userdetails.UserDetailsImpl;
811
import lombok.RequiredArgsConstructor;
912
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
1013
import org.springframework.stereotype.Service;
@@ -17,6 +20,7 @@ public class MemberServiceImpl implements MemberService {
1720

1821
private final BCryptPasswordEncoder encoder;
1922
private final MemberRepository memberRepository;
23+
private final JwtUtil jwtUtil;
2024

2125
// 이메일로 사용자 조회
2226
@Override
@@ -42,4 +46,10 @@ public void signUp(final SignupRequestDto request) {
4246
memberRepository.save(request.toEntity(encoder.encode(request.getPassword())));
4347
}
4448

49+
// 토큰 갱신
50+
@Override
51+
public TokenBox tokenRotation(final UserDetailsImpl userDetails) {
52+
return jwtUtil.tokenRotation(userDetails);
53+
}
54+
4555
}

src/main/java/dmu/dasom/api/global/auth/config/SecurityConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public SecurityFilterChain filterChain(final HttpSecurity http, final Authentica
6868
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
6969
.authorizeHttpRequests(auth -> auth
7070
.requestMatchers("/api/admin/**").hasRole(Role.ROLE_ADMIN.getName())
71-
.requestMatchers("/api/auth/logout").authenticated()
71+
.requestMatchers("/api/auth/logout", "/api/auth/rotation").authenticated()
7272
.anyRequest().permitAll())
7373
.addFilterBefore(jwtFilter, CustomAuthenticationFilter.class)
7474
.addFilterAt(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

src/main/java/dmu/dasom/api/global/auth/jwt/JwtUtil.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import dmu.dasom.api.domain.common.exception.CustomException;
44
import dmu.dasom.api.domain.common.exception.ErrorCode;
55
import dmu.dasom.api.global.auth.dto.TokenBox;
6+
import dmu.dasom.api.global.auth.userdetails.UserDetailsImpl;
67
import io.jsonwebtoken.*;
78
import io.jsonwebtoken.security.SignatureException;
89
import org.springframework.beans.factory.annotation.Value;
@@ -130,4 +131,10 @@ public boolean isExpired(final String token) {
130131
}
131132
}
132133

134+
// Access, Refresh 토큰 갱신
135+
public TokenBox tokenRotation(final UserDetailsImpl userDetails) {
136+
blacklistTokens(userDetails.getUsername());
137+
return generateTokenBox(userDetails.getUsername());
138+
}
139+
133140
}

0 commit comments

Comments
 (0)