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/#146/리프레쉬 토큰 도입 #156

Merged
merged 27 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
aeccc18
refactor : OauthTestController 코드 -> TestController 로 이동
seongjunnoh Sep 8, 2024
9f7c8e7
feat : tokenDTO, enum 클래스 추가
seongjunnoh Sep 8, 2024
7e6e479
refactor : login 메서드에서 jwt 타입별로 토큰 발급 요청하도록 수정
seongjunnoh Sep 8, 2024
c6dec8c
refactor : generateToken 메서드 로직 수정
seongjunnoh Sep 8, 2024
3802066
feat : 로컬 로그인 시 refresh, access token 발급 & response 헤더에 추가
seongjunnoh Sep 8, 2024
d7bdf99
feat : 카카오 로그인 시 refresh token 발급 추가
seongjunnoh Sep 8, 2024
2f56cfd
feat : 카카오 로그인 시 refresh token 정보 db에 저장
seongjunnoh Sep 8, 2024
b38cbd7
feat : access token 만료시 refresh token을 포함한 access token 갱신 요청에 대한 int…
seongjunnoh Sep 8, 2024
068ec55
Revert "feat : 카카오 로그인 시 refresh token 정보 db에 저장"
seongjunnoh Sep 10, 2024
b2b1aa2
feat : 카카오 로그인 시 refresh token db에 저장 & 기존 refresh token 인터셉터 코드 삭제
seongjunnoh Sep 10, 2024
e456315
feat : access token 갱신 요청 api 컨트롤러 코드 추가
seongjunnoh Sep 10, 2024
565d4cd
feat : access token 갱신 요청 api
seongjunnoh Sep 10, 2024
c705e48
refactor : access token, refresh token 생성시 만료시간 value 분리
seongjunnoh Sep 10, 2024
a704a7e
fix : access token, refresh token 시크릿키 분리 & 그에따른 코드 수정
seongjunnoh Sep 10, 2024
06b53d4
fix : 로그인 시 access token, refresh token 서로 반대로 헤더에 주입되는 에러 해결
seongjunnoh Sep 10, 2024
f5df44e
fix : 엑세스 토큰 갱신 요청 헤더의 refresh token 파싱 에러 수정
seongjunnoh Sep 10, 2024
2c42a77
refactor : yml 파일 구조 변경
seongjunnoh Sep 11, 2024
373b392
refactor : refresh token 생성시 payload에 userId 값 제거
seongjunnoh Sep 11, 2024
84de52a
refactor : TokenStorage 엔티티 생성 & refresh token 저장을 해당 엔티티에게 위임
seongjunnoh Sep 11, 2024
5af7702
refactor : access token 갱신 api 수정
seongjunnoh Sep 22, 2024
2c0bf76
fix : BeanCreationException 해결을 위해 ByteBuddy 의존성 추가
seongjunnoh Sep 22, 2024
8f4ae5c
fix : 만료된 access token 으로부터 userId get 할 때 exception 발생하는 에러 해결
seongjunnoh Sep 22, 2024
4044089
fix : 충돌 해결
seongjunnoh Sep 22, 2024
432d54f
refactor : build.gradle 에 추가한 buddy 의존성 제거
seongjunnoh Sep 22, 2024
89a444c
refactor : TokenDTO -> TokenPairDTO DTO 이름 변경
seongjunnoh Sep 23, 2024
d5329d8
refactor : JwtLoginProvider.getUserIdFromToken 메서드 수정 -> 파라미터의 TokenT…
seongjunnoh Sep 25, 2024
8b96674
refactor : user, auth 도메인 생성 & 도메인 구조로 변경
seongjunnoh Sep 25, 2024
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: 1 addition & 3 deletions src/main/java/space/space_spring/config/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
public class WebConfig implements WebMvcConfigurer {

private final JwtLoginAuthInterceptor jwtLoginAuthInterceptor;
private final UserSpaceValidationInterceptor userSpaceValidationInterceptor;

private final JwtLoginAuthHandlerArgumentResolver jwtLoginAuthHandlerArgumentResolver;
private final UserSpaceIdHandlerArgumentResolver userSpaceIdHandlerArgumentResolver;
private final UserSpaceAuthHandlerArgumentResolver userSpaceAuthHandlerArgumentResolver;
private final UserSpaceValidationInterceptor userSpaceValidationInterceptor;


@Override
Expand All @@ -45,8 +45,6 @@ public void addInterceptors(InterceptorRegistry registry) {
for(UserSpaceValidationInterceptorURL url:UserSpaceValidationInterceptorURL.values()) {
userSpaceRegistration.addPathPatterns(url.getUrlPattern());
}


}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package space.space_spring.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
Expand Down Expand Up @@ -52,6 +53,4 @@ public BaseResponse<String> LoginPassAnnotaionTest(
+"");
}



}
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
package space.space_spring.controller;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/oauth")
@Slf4j
public class TestOAuthController {
public class ViewTestController {

/**
* 카카오 로그인 요청 처리
* 카카오 인증 서버의 인증 및 동의 요청 페이지로 redirect
*/
@Value("${oauth.kakao.client.id}")
private String clientId;

@Value("${oauth.kakao.redirect.uri}")
private String redirectUri;

/**
* 카카오 로그인 요청 처리
* 카카오 인증 서버의 인증 및 동의 요청 페이지로 redirect
*/
@GetMapping("/kakao")
@GetMapping("/oauth/kakao")
public String kakaoConnect() {
StringBuffer url = new StringBuffer();
url.append("https://kauth.kakao.com/oauth/authorize?");
Expand Down
7 changes: 2 additions & 5 deletions src/main/java/space/space_spring/dao/UserSpaceDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,15 @@
import jakarta.persistence.TypedQuery;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import space.space_spring.dto.user.dto.SpaceChoiceInfo;
import space.space_spring.dto.user.dto.SpaceChoiceViewDto;
import space.space_spring.domain.user.model.dto.SpaceChoiceInfo;
import space.space_spring.domain.user.model.dto.SpaceChoiceViewDto;
import space.space_spring.dto.userSpace.UserInfoInSpace;
import space.space_spring.entity.Space;
import space.space_spring.entity.User;
import space.space_spring.entity.UserSpace;
import space.space_spring.entity.enumStatus.UserSpaceAuth;

import java.util.*;
import java.util.stream.Stream;

import static space.space_spring.entity.enumStatus.UserSpaceAuth.MANAGER;

@Repository
public class UserSpaceDao {
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -1,19 +1,16 @@
package space.space_spring.controller;
package space.space_spring.domain.authorization.OAuth.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import space.space_spring.domain.authorization.jwt.service.JwtService;
import space.space_spring.domain.authorization.jwt.model.TokenPairDTO;
import space.space_spring.dto.oAuth.KakaoInfo;
import space.space_spring.dto.oAuth.OAuthLoginResponse;
import space.space_spring.entity.User;
import space.space_spring.response.BaseResponse;
import space.space_spring.service.OAuthService;
import space.space_spring.domain.authorization.OAuth.service.OAuthService;

import java.io.IOException;

Expand All @@ -24,6 +21,7 @@
public class OAuthController {

private final OAuthService oAuthService;
private final JwtService jwtService;

@Value("${oauth.kakao.client.id}")
private String clientId;
Expand Down Expand Up @@ -69,18 +67,25 @@ public void kakaoCallback(@RequestParam(name = "code") String code, HttpServletR
User userByOAuthInfo = oAuthService.findUserByOAuthInfo(kakaoInfo);

// TODO 5. 카카오 로그인 유저에게 jwt 발급
String jwtOAuthLogin = oAuthService.provideJwtToOAuthUser(userByOAuthInfo);
response.setHeader("Authorization", "Bearer " + jwtOAuthLogin);
log.info("jwtOAuthLogin = {}", jwtOAuthLogin);
TokenPairDTO tokenPairDTO = jwtService.provideJwtToOAuthUser(userByOAuthInfo);

// Construct the redirect URL with the JWT and userId as query parameters
// TODO 6. 카카오 로그인 유저에게 발급한 refresh token을 db에 저장
jwtService.updateRefreshToken(userByOAuthInfo, tokenPairDTO.getRefreshToken());

System.out.println("tokenPairDTO.getAccessToken() = " + tokenPairDTO.getAccessToken());
System.out.println("tokenPairDTO.getRefreshToken() = " + tokenPairDTO.getRefreshToken());

// 클라이언트로 response 전달
// -> 메서드 분리 ??
// 공백문자가 %20 으로 전달되는 듯 함 -> 프론트 분들과 협의 필요할 듯
String redirectUrl = String.format(
"https://kuit-space.github.io/KUIT-Space-front/login?jwt=Bearer %s&userId=%s",
jwtOAuthLogin,
"https://kuit-space.github.io/KUIT-Space-front/login?access-token=%s&refresh-token=%s&userId=%s",
"Bearer " + tokenPairDTO.getAccessToken(),
"Bearer " + tokenPairDTO.getRefreshToken(),
userByOAuthInfo.getUserId()
);

// Redirect to the specified URL
response.sendRedirect(redirectUrl);
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package space.space_spring.service;
package space.space_spring.domain.authorization.OAuth.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
Expand All @@ -13,9 +13,9 @@
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import space.space_spring.domain.user.repository.UserDao;
import space.space_spring.dto.oAuth.KakaoInfo;
import space.space_spring.entity.User;
import space.space_spring.jwt.JwtLoginProvider;
import space.space_spring.util.user.UserUtils;

import static space.space_spring.entity.enumStatus.UserSignupType.KAKAO;
Expand All @@ -25,7 +25,8 @@
public class OAuthService {

private final UserUtils userUtils;
private final JwtLoginProvider jwtLoginProvider;
private final UserDao userDao;


/**
* 카카오 인증 서버가 전달해준 유저의 인가코드로 토큰 발급 요청
Expand Down Expand Up @@ -103,7 +104,7 @@ public User findUserByOAuthInfo(KakaoInfo kakaoInfo) {
return userUtils.findOrCreateUserForOAuthInfo(email, nickname, KAKAO);
}

public String provideJwtToOAuthUser(User userByOAuthInfo) {
return jwtLoginProvider.generateToken(userByOAuthInfo);
}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package space.space_spring.domain.authorization.auth.controller;

import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
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 space.space_spring.domain.authorization.auth.service.AuthService;
import space.space_spring.domain.user.model.PostLoginDto;
import space.space_spring.exception.CustomException;
import space.space_spring.response.BaseResponse;

import static space.space_spring.response.status.BaseExceptionResponseStatus.INVALID_USER_LOGIN;
import static space.space_spring.util.bindingResult.BindingResultUtils.getErrorMessage;

@RestController
@RequiredArgsConstructor
@RequestMapping("/user")
public class AuthController {

private final AuthService authService;

/**
* 로그인
*/
@PostMapping("/login")
public BaseResponse<PostLoginDto.Response> login(@Validated @RequestBody PostLoginDto.Request request, BindingResult bindingResult, HttpServletResponse response) {
if (bindingResult.hasErrors()) {
throw new CustomException(INVALID_USER_LOGIN, getErrorMessage(bindingResult));
}

PostLoginDto login = authService.login(request);

System.out.println("login.getTokenPairDTO().getRefreshToken() = " + login.getTokenPairDTO().getRefreshToken());
System.out.println("login.getTokenPairDTO().getAccessToken() = " + login.getTokenPairDTO().getAccessToken());

response.setHeader("Authorization-refresh", "Bearer " + login.getTokenPairDTO().getRefreshToken());
response.setHeader("Authorization", "Bearer " + login.getTokenPairDTO().getAccessToken());

return new BaseResponse<>(new PostLoginDto.Response(login.getUserId()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package space.space_spring.domain.authorization.auth.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import space.space_spring.domain.authorization.jwt.model.TokenPairDTO;
import space.space_spring.domain.authorization.jwt.model.TokenType;
import space.space_spring.domain.authorization.jwt.repository.JwtRepository;
import space.space_spring.domain.user.model.PostLoginDto;
import space.space_spring.entity.TokenStorage;
import space.space_spring.entity.User;
import space.space_spring.exception.CustomException;
import space.space_spring.jwt.JwtLoginProvider;
import space.space_spring.util.user.UserUtils;

import static space.space_spring.entity.enumStatus.UserSignupType.LOCAL;
import static space.space_spring.response.status.BaseExceptionResponseStatus.PASSWORD_NO_MATCH;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Slf4j
public class AuthService {

private final JwtLoginProvider jwtLoginProvider;
private final JwtRepository jwtRepository;
private final PasswordEncoder passwordEncoder;
private final UserUtils userUtils;


@Transactional
public PostLoginDto login(PostLoginDto.Request request) {
// TODO 1. 이메일 존재 여부 확인(아이디 존재 여부 확인)
User userByEmail = userUtils.findUserByEmail(request.getEmail(), LOCAL);
log.info("userByEmail.getUserId: {}", userByEmail.getUserId());

// TODO 2. 비밀번호 일치 여부 확인
validatePassword(userByEmail, request.getPassword());

// TODO 3. JWT 발급 -> access token, refresh token 2개 발급
String accessToken = jwtLoginProvider.generateToken(userByEmail, TokenType.ACCESS);
String refreshToken = jwtLoginProvider.generateToken(userByEmail, TokenType.REFRESH);

// TODO 4. refresh token db에 저장
TokenStorage tokenStorage = TokenStorage.builder()
.user(userByEmail)
.tokenValue(refreshToken)
.build();
jwtRepository.save(tokenStorage);

// TODO 5. return
TokenPairDTO tokenPairDTO = TokenPairDTO.builder()
.refreshToken(refreshToken)
.accessToken(accessToken)
.build();

return PostLoginDto.builder()
.TokenPairDTO(tokenPairDTO)
.userId(userByEmail.getUserId())
.build();
}

private void validatePassword(User userByEmail, String password) {
String encodePassword = userByEmail.getPassword();
if(!passwordEncoder.matches(password,encodePassword)){
throw new CustomException(PASSWORD_NO_MATCH);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package space.space_spring.domain.authorization.jwt.controller;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import space.space_spring.domain.authorization.jwt.service.JwtService;
import space.space_spring.domain.authorization.jwt.model.TokenPairDTO;
import space.space_spring.entity.User;
import space.space_spring.response.BaseResponse;

import java.io.IOException;

@RestController
@RequiredArgsConstructor
@RequestMapping("/jwt")
public class JwtController {

private final JwtService jwtService;

/**
* 엑세스 토큰 갱신 요청 처리
* -> 엑세스 토큰, 리프레시 토큰 갱신 (RTR 패턴)
*/
@PostMapping("/new-token")
public BaseResponse<String> updateAccessToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
// access token, refresh token 파싱
TokenPairDTO tokenPairDTO = jwtService.resolveTokenPair(request);

// access token 로부터 user find
User userByAccessToken = jwtService.getUserByAccessToken(tokenPairDTO.getAccessToken());

// refresh token 유효성 검사
jwtService.validateRefreshToken(userByAccessToken, tokenPairDTO.getRefreshToken());

// access token, refresh token 새로 발급
TokenPairDTO newTokenPairDTO = jwtService.updateTokenPair(userByAccessToken);

// response header에 새로 발급한 token pair set
response.setHeader("Authorization-refresh", "Bearer " + newTokenPairDTO.getRefreshToken());
response.setHeader("Authorization", "Bearer " + newTokenPairDTO.getAccessToken());

System.out.println("tokenPairDTO.getAccessToken() = " + newTokenPairDTO.getAccessToken());
System.out.println("tokenPairDTO.getRefreshToken() = " + newTokenPairDTO.getRefreshToken());

// return
return new BaseResponse<>("토큰 갱신 요청 성공");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package space.space_spring.domain.authorization.jwt.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TokenPairDTO {

private String refreshToken;
private String accessToken;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package space.space_spring.domain.authorization.jwt.model;

public enum TokenType {
REFRESH,
ACCESS
}
Loading
Loading