Skip to content

Commit

Permalink
Merge pull request #58 from Orange-Co/feature/auth
Browse files Browse the repository at this point in the history
Feat: refreshed token & logout
  • Loading branch information
Kang1221 authored Aug 19, 2024
2 parents f71612f + 09a2daf commit 68ee18b
Show file tree
Hide file tree
Showing 12 changed files with 131 additions and 22 deletions.
2 changes: 2 additions & 0 deletions src/main/java/co/orange/ddanzi/common/error/Error.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum Error {
ERROR(HttpStatus.BAD_REQUEST, "Request processing failed"),

// 400 BAD REQUEST
REFRESH_TOKEN_IS_NULL(HttpStatus.BAD_REQUEST, "Refresh token is null"),
ACCOUNT_NAME_DOES_NOT_MATCH(HttpStatus.BAD_REQUEST, "The account name does not match to user name."),
DUE_DATE_IS_INCORRECT(HttpStatus.BAD_REQUEST, "The due date is incorrect."),
ITEM_IS_NOT_ON_SALE(HttpStatus.BAD_REQUEST, "The item is not on sale."),
Expand All @@ -24,6 +25,7 @@ public enum Error {
INVALID_JWT_EXCEPTION(HttpStatus.UNAUTHORIZED, "Invalid JWT"),
LOG_OUT_JWT_TOKEN(HttpStatus.UNAUTHORIZED,"Logged out user"),
JWT_EXPIRED(HttpStatus.UNAUTHORIZED,"JWT expired"),
REFRESH_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED,"Refresh Token expired"),
JWT_TOKEN_NOT_EXISTS(HttpStatus.UNAUTHORIZED,"JWT value does not exist in header"),


Expand Down
11 changes: 11 additions & 0 deletions src/main/java/co/orange/ddanzi/common/error/ErrorResponse.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package co.orange.ddanzi.common.error;

import co.orange.ddanzi.global.handler.GlobalControllerHandler;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.*;

Expand Down Expand Up @@ -45,4 +47,13 @@ public static ErrorResponse onFailure(Error error, String message) {
.build();
}

public String toJson() {
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
return "{\"message\":\"JSON 변환 오류\", \"code\":500}";
}
}

}
10 changes: 7 additions & 3 deletions src/main/java/co/orange/ddanzi/common/response/Success.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ public enum Success {
SUCCESS(HttpStatus.OK, "Request successfully processed."),

// 200 OK SUCCESS
SIGNIN_KAKAO_SUCCESS(HttpStatus.CREATED, "Successfully sign in using Kakao"),
SIGNIN_APPLE_SUCCESS(HttpStatus.CREATED, "Successfully sign in using Kakao"),

GET_REDIS_KEY_SUCCESS(HttpStatus.CREATED, "Successfully retrieved the redis key"),

GET_HOME_INFO_SUCCESS(HttpStatus.OK, "Successfully retrieved home information."),
Expand Down Expand Up @@ -52,8 +49,15 @@ public enum Success {


// 201 CREATED
SIGNIN_KAKAO_SUCCESS(HttpStatus.CREATED, "Successfully sign in using Kakao"),
SIGNIN_APPLE_SUCCESS(HttpStatus.CREATED, "Successfully sign in using Kakao"),

REFRESH_ACCESS_TOKEN_SUCCESS(HttpStatus.CREATED, "Successfully refreshed access token."),
LOGOUT_SUCCESS(HttpStatus.CREATED, "Successfully log out the user"),

SET_REDIS_KEY_SUCCESS(HttpStatus.CREATED, "Successfully set the redis key"),


CREATE_AUTHENTICATION_SUCCESS(HttpStatus.CREATED, "Successfully verified identity."),
CREATE_PRODUCT_SUCCESS(HttpStatus.CREATED, "Successfully confirmed the product."),
CREATE_PAYMENT_SUCCESS(HttpStatus.CREATED, "Successfully registered payment information."),
Expand Down
31 changes: 27 additions & 4 deletions src/main/java/co/orange/ddanzi/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
package co.orange.ddanzi.controller;

import co.orange.ddanzi.common.error.Error;
import co.orange.ddanzi.common.response.Success;
import co.orange.ddanzi.domain.user.enums.LoginType;
import co.orange.ddanzi.dto.auth.RefreshTokenRequestDto;
import co.orange.ddanzi.dto.auth.SigninRequestDto;
import co.orange.ddanzi.dto.auth.VerifyRequestDto;
import co.orange.ddanzi.common.response.ApiResponse;
import co.orange.ddanzi.global.jwt.JwtUtils;
import co.orange.ddanzi.service.auth.AuthService;
import co.orange.ddanzi.service.auth.OAuthService;
import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/v1/auth")
public class AuthController {
private final AuthService authService;
private final OAuthService oAuthService;
private final JwtUtils jwtUtils;

@PostMapping("/signin/test")
ApiResponse<?> test(@RequestBody SigninRequestDto requestDto){
Expand All @@ -32,6 +37,24 @@ ApiResponse<?> signin(@RequestBody SigninRequestDto requestDto) throws JsonProce
return oAuthService.kakaoSignIn(requestDto.getToken());
}

@PostMapping("/refreshtoken")
ApiResponse<?> refreshAccessToken(@RequestBody RefreshTokenRequestDto requestDto) throws JsonProcessingException{
return oAuthService.refreshAccessToken(requestDto.getRefreshtoken());
}

@PostMapping("/logout")
ApiResponse<?> logout(HttpServletRequest request){
String accesstoken = jwtUtils.resolveJWT(request);
if (accesstoken == null) {
return ApiResponse.onFailure(Error.JWT_TOKEN_NOT_EXISTS, null);
}

// 블랙리스트에 토큰 추가 (로그아웃 처리)
jwtUtils.setBlackList(accesstoken);

return ApiResponse.onSuccess(Success.LOGOUT_SUCCESS, true);
}

@PostMapping("/verification")
ApiResponse<?> verify(@RequestBody VerifyRequestDto requestDto) {
return authService.verify(requestDto);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package co.orange.ddanzi.dto.auth;

import lombok.Getter;

@Getter
public class RefreshTokenRequestDto {
private String refreshtoken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,19 @@
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

@ControllerAdvice
public class GlobalControllerHandler {
private static final ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<>();

@ModelAttribute
public void setRequest(HttpServletRequest request) {
requestHolder.set(request);
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
}

public static HttpServletRequest getRequest() {
return requestHolder.get();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
return attributes != null ? attributes.getRequest() : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.util.ContentCachingRequestWrapper;

import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR;

@Order(1)
@ControllerAdvice
@Slf4j
@RequiredArgsConstructor
Expand Down
26 changes: 18 additions & 8 deletions src/main/java/co/orange/ddanzi/global/jwt/JwtFilter.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package co.orange.ddanzi.global.jwt;

import co.orange.ddanzi.common.error.ErrorResponse;
import co.orange.ddanzi.common.exception.UnauthorizedException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
Expand All @@ -22,15 +25,22 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
String token = jwtUtils.resolveJWT(request);
log.info("Request to {}: token={}", request.getRequestURI(), token);

//home & search api
if(isHomeOrSearchRequest(request.getRequestURI())){
handleHomeOrSearchRequest(token);
try {
//home & search api
if (isHomeOrSearchRequest(request.getRequestURI())) {
handleHomeOrSearchRequest(token);
}
//other api
else {
handleGeneralRequest(token);
}
filterChain.doFilter(request, response);
} catch (UnauthorizedException e) {
log.error("Unauthorized Exception: {}", e.getMessage());
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType("application/json");
response.getWriter().write(ErrorResponse.onFailure(e.getError()).toJson());
}
//other api
else {
handleGeneralRequest(token);
}
filterChain.doFilter(request, response);
}

@Override
Expand Down
30 changes: 30 additions & 0 deletions src/main/java/co/orange/ddanzi/global/jwt/JwtUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ public boolean validateToken(String token) {
}
}

public boolean isValidRefreshToken(String email, String refreshToken) {
if (email == null) {
return false; // 이메일이 null인 경우 유효하지 않음
}
String storedRefreshToken = stringRedisTemplate.opsForValue().get(email);
return refreshToken.equals(storedRefreshToken);
}


public boolean validateTokenInLogoutPage(String token) {
if (!StringUtils.hasText(token)) {
return false;
Expand Down Expand Up @@ -146,15 +155,36 @@ public String getIdTokenFromToken(String token) {
return getClaims(token).get("email").toString();
}

public String getIdFromRefreshToken(String token) {
try {
Claims claims = Jwts.parser()
.setSigningKey(jwtSecretKey)
.parseClaimsJws(token)
.getBody();
return claims.get("email").toString();
} catch (Exception e) {
return null;
}
}


public Claims getClaims(String token) {
return Jwts.parser().setSigningKey(jwtSecretKey).parseClaimsJws(token).getBody();
}


public boolean isLogout(String accessToken) {
return !ObjectUtils.isEmpty(stringRedisTemplate.opsForValue().get(accessToken));
}

public void setBlackList(String accessToken) {
Long expiration = getExpiration(accessToken);
stringRedisTemplate.opsForValue().set(accessToken, "logout", expiration, TimeUnit.MILLISECONDS);
}

public Long getExpiration(String token) {
Date expiration = getClaims(token).getExpiration();
return expiration.getTime() - new Date().getTime();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class SecurityConfig {
private String[] permitList = {
"/api/v1/auth/signin",
"/api/v1/auth/signin/test",
"/api/v1/auth/refreshtoken",
"/api/v1/home/**",
"/api/v1/search/**"
};
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/co/orange/ddanzi/service/auth/AuthService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import co.orange.ddanzi.domain.user.Authentication;
import co.orange.ddanzi.domain.user.User;
import co.orange.ddanzi.domain.user.enums.UserStatus;
import co.orange.ddanzi.dto.auth.AuthResponseDto;
import co.orange.ddanzi.dto.auth.SigninResponseDto;
import co.orange.ddanzi.dto.auth.VerifyRequestDto;
import co.orange.ddanzi.dto.auth.VerifyResponseDto;
import co.orange.ddanzi.common.error.Error;
Expand Down Expand Up @@ -39,8 +39,9 @@ public ApiResponse<?> testSignin(String idToken){
}
User user = optionalUser.get();

AuthResponseDto responseDto = AuthResponseDto.builder()
SigninResponseDto responseDto = SigninResponseDto.builder()
.accesstoken(jwtUtils.createAccessToken(user.getEmail()))
.refreshtoken(jwtUtils.createRefreshToken(user.getEmail()))
.nickname(user.getNickname())
.build();
return ApiResponse.onSuccess(Success.SUCCESS, responseDto);
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/co/orange/ddanzi/service/auth/OAuthService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package co.orange.ddanzi.service.auth;

import co.orange.ddanzi.common.error.Error;
import co.orange.ddanzi.domain.user.User;
import co.orange.ddanzi.domain.user.enums.LoginType;
import co.orange.ddanzi.domain.user.enums.UserStatus;
Expand All @@ -26,6 +27,7 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -61,6 +63,23 @@ public ApiResponse<?> kakaoSignIn(String token) throws JsonProcessingException {
return ApiResponse.onSuccess(Success.SIGNIN_KAKAO_SUCCESS, responseDto);
}

@Transactional
public ApiResponse<?> refreshAccessToken(String refreshToken) throws JsonProcessingException {
if (refreshToken == null || refreshToken.isEmpty()) {
return ApiResponse.onFailure(Error.REFRESH_TOKEN_IS_NULL, Map.of("refreshtoken", refreshToken));
}

String email = jwtUtils.getIdFromRefreshToken(refreshToken);

if (!jwtUtils.isValidRefreshToken(email, refreshToken)) {
return ApiResponse.onFailure(Error.REFRESH_TOKEN_EXPIRED, Map.of("refreshtoken", refreshToken));
}

String newAccessToken = jwtUtils.createAccessToken(email);

return ApiResponse.onSuccess(Success.REFRESH_ACCESS_TOKEN_SUCCESS, Map.of("accesstoken",newAccessToken ));
}

public void kakaoSignUp(String email) {
User user = User.builder()
.email(email)
Expand Down

0 comments on commit 68ee18b

Please sign in to comment.