Repository navigation
Feat/#15/week6 hw #16
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
base: main
Are you sure you want to change the base?
Changes from all commits
912856d
52ea35c
efef28a
d89ea6c
dfa734a
cce58a4
320744a
e369bf3
333fe70
5384001
15180d2
3f3ab82
9d51553
40bcc26
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,45 +1,103 @@ | ||
| package org.sopt.auth.controller; | ||
|
|
||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.sopt.auth.code.AuthSuccessCode; | ||
| import org.sopt.auth.dto.request.SignUpRequest; | ||
| import org.sopt.auth.service.AuthService; | ||
| import org.sopt.auth.dto.TokenResponse; | ||
| import org.sopt.auth.util.AccessTokenResolver; | ||
| import org.sopt.auth.util.CookieUtil; | ||
| import org.sopt.auth.util.HeaderUtil; | ||
| import org.sopt.global.api.response.BaseResponse; | ||
| import org.sopt.user.dto.UserResponse; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.core.Authentication; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import org.springframework.web.bind.annotation.*; | ||
| import org.sopt.auth.dto.request.LoginRequest; | ||
| import org.sopt.auth.dto.request.KakaoLoginRequest; | ||
|
|
||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/api/v1/auth") | ||
| @RequestMapping("/auth") | ||
| public class AuthController { | ||
|
|
||
| private final AuthService authService; | ||
|
|
||
| @Operation(summary = "ํ์๊ฐ์ (Access Token + Refresh Token ๋ฐ๊ธ)") | ||
| @PostMapping("/signup") | ||
| public ResponseEntity<BaseResponse<Void>> signup( | ||
| @RequestBody @Valid final SignUpRequest request, | ||
| HttpServletResponse response | ||
| ) { | ||
| TokenResponse tokens = authService.signUp(request); | ||
| setTokens(response, tokens); | ||
| return ResponseEntity.ok(BaseResponse.success(AuthSuccessCode.SIGNUP_SUCCESS)); | ||
| } | ||
|
|
||
|
|
||
| @Operation(summary = "๋ก๊ทธ์ธ (Access Token + Refresh Token ๋ฐ๊ธ)") | ||
| @PostMapping("/login") | ||
| public ResponseEntity<BaseResponse<TokenResponse>> login( | ||
| @RequestParam("email") String email, | ||
| @RequestParam("password") String password | ||
| public ResponseEntity<BaseResponse<Void>> login( | ||
| @RequestBody @Valid final LoginRequest request, | ||
| HttpServletResponse response | ||
| ) { | ||
| TokenResponse tokens = authService.login(email, password); | ||
| //todo: ๋ฆฌํ๋ ์ ํ ํฐ ์ฟ ํค๋ก ๋ด์์ ๋ณด๋ด๋๋ก helper metohd private์ผ๋ก ํ๊ธฐ !! | ||
| return ResponseEntity.ok(BaseResponse.success(AuthSuccessCode.TOKEN_GENERATED,tokens)); | ||
| TokenResponse tokens = authService.login(request); | ||
| setTokens(response, tokens); | ||
| return ResponseEntity.ok(BaseResponse.success(AuthSuccessCode.LOGIN_SUCCESS)); | ||
| } | ||
|
|
||
| @Operation(summary = "๋ด ์ ๋ณด ์กฐํ (Access Token ๊ฒ์ฆ)") | ||
| @GetMapping("/api/v1/me") | ||
| public ResponseEntity<BaseResponse<UserResponse>> me(Authentication authentication) { | ||
| @Operation(summary = "์นด์นด์ค ๋ก๊ทธ์ธ (์ธ๊ฐ์ฝ๋๋ก ํ ํฐ ๋ฐ๊ธ)") | ||
| @PostMapping("/kakao") | ||
| public ResponseEntity<BaseResponse<Void>> kakaoLogin( | ||
| @RequestBody @Valid final KakaoLoginRequest request, | ||
| HttpServletResponse response | ||
| ) { | ||
| TokenResponse tokens = authService.kakaoLogin(request.code()); | ||
| setTokens(response, tokens); | ||
| return ResponseEntity.ok(BaseResponse.success(AuthSuccessCode.KAKAO_LOGIN_SUCCESS)); | ||
| } | ||
|
|
||
| if (authentication == null || authentication.getPrincipal() == null) { | ||
| throw new IllegalArgumentException("์ธ์ฆ๋์ง ์์์ต๋๋ค."); | ||
| } | ||
| @Operation(summary = "ํ ํฐ ์ฌ๋ฐ๊ธ") | ||
| @PostMapping("/reissue") | ||
| public ResponseEntity<BaseResponse<Void>> reissue( | ||
| HttpServletRequest request, | ||
| HttpServletResponse response | ||
| ) { | ||
| String refreshToken = CookieUtil.getRefreshToken(request); | ||
| TokenResponse tokens = authService.reissue(refreshToken); | ||
| setTokens(response, tokens); | ||
| return ResponseEntity.ok(BaseResponse.success(AuthSuccessCode.TOKEN_GENERATED)); | ||
| } | ||
|
|
||
| Long memberId = Long.parseLong(authentication.getName()); | ||
| @Operation(summary = "๋ด ์ ๋ณด ์กฐํ (Access Token ๊ฒ์ฆ)") | ||
| @GetMapping("/me") | ||
| public ResponseEntity<BaseResponse<UserResponse>> me( | ||
| @AuthenticationPrincipal Long memberId | ||
| ) { | ||
| UserResponse member = authService.getMemberById(memberId); | ||
|
|
||
| return ResponseEntity.ok(BaseResponse.success(AuthSuccessCode.USER_SEARCHED,member)); | ||
| } | ||
|
|
||
| @Operation(summary = "๋ก๊ทธ์์") | ||
| @PostMapping("/logout") | ||
| public ResponseEntity<BaseResponse<Void>> logout( | ||
| @AuthenticationPrincipal Long memberId, | ||
| HttpServletRequest request, | ||
| HttpServletResponse response | ||
| ) { | ||
| String accessToken = AccessTokenResolver.resolve(request); | ||
| authService.logout(memberId,accessToken); | ||
| CookieUtil.deleteCookie(response, "refreshToken"); | ||
| return ResponseEntity.ok(BaseResponse.success(AuthSuccessCode.LOGOUT_SUCCESS)); | ||
| } | ||
|
|
||
| private void setTokens(HttpServletResponse response, TokenResponse tokens) { | ||
| HeaderUtil.setAuthorizationHeader(response, tokens.accessToken()); | ||
| CookieUtil.addRefreshTokenCookie(response, tokens.refreshToken()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ฟ ํค ๊ด๋ จ ์ธํ ํจ์๋ค์ util ํด๋์ค๋ก ๋นผ์ ์ฝ๋๊ฐ ๊ฐ๊ฒฐํด์ง ๊ฑฐ ๊ฐ์์ ๋ฆฌ๋ทฐ์ด ์ ์ฅ์์ ์ฝ๊ธฐ๊ฐ ์ข๋ค์๐ |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| package org.sopt.auth.dto.request; | ||
|
|
||
| public record KakaoLoginRequest( | ||
| String code | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package org.sopt.auth.dto.request; | ||
|
|
||
| public record LoginRequest( | ||
| String email, | ||
| String password | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package org.sopt.auth.dto.request; | ||
|
|
||
| public record SignUpRequest( | ||
| String nickname, | ||
| String email, | ||
| String password | ||
| ) {} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package org.sopt.auth.exception.code; | ||
|
|
||
| import org.sopt.global.api.code.ResponseCode; | ||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| public enum AuthErrorCode implements ResponseCode { | ||
| INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "์ ํจํ์ง ์์ ๋ฆฌํ๋ ์ ํ ํฐ์ ๋๋ค."), | ||
| USER_NOT_FOUND(HttpStatus.NOT_FOUND, "์ ์ ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."), | ||
| KAKAO_USER_CANNOT_LOGIN(HttpStatus.BAD_REQUEST, "์นด์นด์ค๋ก ๊ฐ์ ํ ์ ์ ์ ๋๋ค. ์นด์นด์ค ๋ก๊ทธ์ธ์ ์ด์ฉํด์ฃผ์ธ์."), | ||
| UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "์ธ์ฆ์ ์คํจํ์ต๋๋ค."), | ||
| EMAIL_ALREADY_EXISTS(HttpStatus.CONFLICT, "์ด๋ฏธ ์กด์ฌํ๋ ์ด๋ฉ์ผ์ ๋๋ค."), | ||
| SIGNUP_REQUIRED(HttpStatus.NOT_FOUND,"ํ์๊ฐ์ ์ด ํ์ํฉ๋๋ค."), | ||
| INVALID_PASSWORD(HttpStatus.UNAUTHORIZED, "๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค."), | ||
|
|
||
| EMPTY_TOKEN(HttpStatus.UNAUTHORIZED, "ํ ํฐ์ด ๋น์ด์์ต๋๋ค."), | ||
| MALFORMED_TOKEN(HttpStatus.UNAUTHORIZED, "ํ ํฐ ํ์์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค."), | ||
| EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "ํ ํฐ์ด ๋ง๋ฃ๋์์ต๋๋ค."); | ||
|
|
||
| private final HttpStatus status; | ||
| private final String message; | ||
|
|
||
| AuthErrorCode(HttpStatus status, String message) { | ||
| this.status = status; | ||
| this.message = message; | ||
| } | ||
|
|
||
| @Override | ||
| public HttpStatus getStatus() { return status; } | ||
|
|
||
| @Override | ||
| public String getMessage() { return message; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,12 @@ | ||
| package org.sopt.global.api.jwt; | ||
| package org.sopt.auth.jwt; | ||
|
|
||
| import com.auth0.jwt.exceptions.JWTVerificationException; | ||
| import jakarta.servlet.FilterChain; | ||
| import jakarta.servlet.ServletException; | ||
| import jakarta.servlet.http.HttpServletRequest; | ||
| import jakarta.servlet.http.HttpServletResponse; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.sopt.auth.service.JwtService; | ||
| import org.sopt.auth.service.BlacklistService; | ||
| import org.sopt.global.api.exception.BaseException; | ||
| import org.springframework.http.HttpHeaders; | ||
| import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
| import org.springframework.security.core.context.SecurityContextHolder; | ||
|
|
@@ -22,6 +22,7 @@ | |
| public class JwtAuthFilter extends OncePerRequestFilter { | ||
|
|
||
| private final JwtService jwtService; | ||
| private final BlacklistService blacklistService; | ||
|
|
||
| @Override | ||
| protected void doFilterInternal( | ||
|
|
@@ -30,20 +31,26 @@ protected void doFilterInternal( | |
| FilterChain filterChain | ||
| ) throws ServletException, IOException { | ||
| String header = request.getHeader(HttpHeaders.AUTHORIZATION); | ||
| System.out.println(">>> Header: " + header); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๋ก๊น
ํ ๋ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ ๋ ๊ฐ์ ์๊ฐ์ด์์!! @slf4j + log.debug()๋ก ๊ต์ฒดํ๊ฑฐ๋, ๋๋ฒ๊น ์ฉ์ด์๋ค๋ฉด ์ ๊ฑฐํ๋ ๊ฒ ์ข์ ๊ฒ ๊ฐ์์! |
||
|
|
||
| if (header != null && header.startsWith("Bearer ")) { | ||
| String token = header.substring("Bearer ".length()).trim(); | ||
| try { | ||
| if (blacklistService.isBlacklisted(token)) { | ||
| filterChain.doFilter(request, response); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๋ก๊ทธ์์๋ Access Token์ JwtAuthFilter ๋จ๊ณ์์ ๋ธ๋๋ฆฌ์คํธ๋ก ํ์ธํ๋๋ก ํ ์ ์ด ์ข์ ๊ฒ ๊ฐ์ต๋๋ค! JWT๋ ๋ง๋ฃ ์ ๊น์ง ์ ํจํ ์ ์๋๋ฐ ๋ธ๋๋ฆฌ์คํธ๋ฅผ ํตํด ๋ก๊ทธ์์๋ ํ ํฐ์ ์ถ๊ฐ๋ก ๊ฒ์ฆํ๋ ํ๋ฆ์ด ๋ณด์ ์ธก๋ฉด์์ ์ข๋ค๊ณ ๋๊ผ์ต๋๋ค.๐๐ ๊ทผ๋ฐ ๋ธ๋๋ฆฌ์คํธ์ ํด๋นํ๋ ๊ฒฝ์ฐ ์ง๊ธ ์ฝ๋์์๋ filterChain์ผ๋ก ๋๊ธฐ๋ ๊ตฌ์กฐ๋ก ๋ณด์ฌ์ ๋ก๊ทธ์์๋ ํ ํฐ์ด๋ผ๋ ์ํฉ์ด ์๋ต์์ ๋ช ํํ ๋๋ฌ๋๋์ง ํ ๋ฒ ํ์ธํด๋ณด๋ฉด ์ข์ ๊ฒ ๊ฐ์์! |
||
| return; | ||
| } | ||
| Long memberId = jwtService.verifyAndGetMemberId(token); | ||
| System.out.println(">>> ์ธ์ฆ ์ฑ๊ณต, memberId: " + memberId); | ||
| UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( | ||
| String.valueOf(memberId), null, Collections.emptyList()); | ||
| memberId, null, Collections.emptyList()); | ||
| auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); | ||
| SecurityContextHolder.getContext().setAuthentication(auth); | ||
| } catch (IllegalArgumentException | JWTVerificationException e) { | ||
| // ์ ํจํ์ง ์์ ํ ํฐ ๋๋ ํ ํฐ์ด ์๋ ๊ฒฝ์ฐ, ์ธ์ฆ ์์ด ๋ค์ ํํฐ๋ก ๋๊ฒจ์. | ||
| // ์ฌ๊ธฐ์ ์์ธ๋ฅผ ๋์ง์ง ์๋ ์ด์ ๋, /v1/login ๊ฐ์ด ์ธ์ฆ์ด ํ์ ์๋ API๋ | ||
| // ์ด ํํฐ๋ฅผ ๊ฑฐ์น๊ธฐ ๋๋ฌธ์ด์์. ์ธ์ฆ ์ฌ๋ถ ํ๋จ์ SecurityConfig์ | ||
| // authorizeHttpRequests ์ค์ ์์ ๋ด๋นํฉ๋๋ค. | ||
| } catch (BaseException e) { | ||
| System.out.println(">>> ํ ํฐ ๊ฒ์ฆ ์คํจ: " + e.getMessage()); | ||
| } | ||
| } else { | ||
| System.out.println(">>> Authorization ํค๋ ์์ ๋๋ Bearer ํ์ ์๋"); | ||
| } | ||
|
|
||
| filterChain.doFilter(request, response); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package org.sopt.auth.jwt.exception; | ||
|
|
||
| import org.sopt.auth.exception.code.AuthErrorCode; | ||
| import org.sopt.global.api.exception.BaseException; | ||
|
|
||
| public class EmptyTokenException extends BaseException { | ||
| public EmptyTokenException() { | ||
| super(AuthErrorCode.EMPTY_TOKEN); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package org.sopt.auth.jwt.exception; | ||
|
|
||
| import org.sopt.auth.exception.code.AuthErrorCode; | ||
| import org.sopt.global.api.exception.BaseException; | ||
|
|
||
| public class MalformedTokenException extends BaseException { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์๋ฌ์ฝ๋ ๋ณ๋ก Exception ํด๋์ค๋ฅผ ๋ฐ๋ก ๋ง๋ค๋ฉด ํ์คํ ์๋ฌ ์ถ์ ์ ์ฉ์ดํ ๊ฒ ๊ฐ๋ค์! |
||
| public MalformedTokenException() { | ||
| super(AuthErrorCode.MALFORMED_TOKEN); | ||
| } | ||
| } | ||
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.
spring-boot-starter-security๊ฐ 8๋ฒ์งธ ์ค๊ณผ 16๋ฒ์งธ ์ค์ ์ค๋ณต์ผ๋ก ์ ์ธ๋์ด ์์ด์!! ๋ ์ค ํ๋ ์ ๊ฑฐํ๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค!