-
Notifications
You must be signed in to change notification settings - Fork 0
Pochak 회원가입 & 로그인 로직 정리
Hagoeun edited this page Aug 15, 2023
·
1 revision
Written by Goeun Ha
-
프론트: 인가 코드 넘기기
-
백엔드: 인가 코드로 Access Token 발급 후 Access Token으로 유저 정보(email, socialId, name) 가져옴
→ socialId의 경우 소셜 회원가입을 한 회원인지 알기 위함
-
소셜 회원가입 완료 후 프로필 생성까지 완료하면 JWT 발급, DB에 유저 정보 저장
-
access token 만료 시 fresh token을 이용하여 재발급
→ fresh token 만료 시 재로그인
/**
* GOOGLE 소셜 로그인 기능
* 프로트가 넘겨줄 인가코드
* https://localhost:8080/login/oauth2/code/google?code=코드정보
*/
@ResponseBody
@GetMapping("/login/oauth2/code/google")
public BaseResponse<?> googleOAuthRequest(@RequestParam String code) throws BaseException {
return new BaseResponse<>(googleOAuthService.login(code));
}
- 인가코드로 access token 받아오기
/**
* Get Access Token
*/
public String getAccessToken(String code) throws BaseException {
GoogleTokenResponse googleTokenResponse = webClient.post()
.uri(GOOGLE_BASE_URL, uriBuilder -> uriBuilder
.queryParam("grant_type", "authorization_code")
.queryParam("client_id", GOOGLE_CLIENT_ID)
.queryParam("client_secret", GOOGLE_CLIENT_SECRET)
.queryParam("redirect_uri", GOOGLE_REDIRECT_URI)
.queryParam("code", code)
.build())
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> Mono.error(new RuntimeException("Social Access Token is unauthorized")))
.onStatus(HttpStatus::is5xxServerError, response -> Mono.error(new RuntimeException("Internal Server Error")))
.bodyToMono(GoogleTokenResponse.class)
.flux()
.toStream()
.findFirst()
.orElseThrow(() -> new BaseException(INVALID_ACCESS_TOKEN));
log.info(googleTokenResponse.getAccessToken());
return googleTokenResponse.getAccessToken();
}
- access token으로 유저 정보 받아오기
/**
* Get User Info Using Access Token
*/
public GoogleUserResponse getUserInfo(String accessToken) throws BaseException {
GoogleUserResponse googleUserResponse = webClient.get()
.uri(GOOGLE_USER_BASE_URL, uriBuilder -> uriBuilder.queryParam("access_token", accessToken).build())
.retrieve()
.onStatus(HttpStatus::is4xxClientError, response -> Mono.error(new RuntimeException("Social Access Token is unauthorized")))
.onStatus(HttpStatus::is5xxServerError, response -> Mono.error(new RuntimeException("Internal Server Error")))
.bodyToMono(GoogleUserResponse.class)
.flux()
.toStream()
.findFirst()
.orElseThrow(() -> new BaseException(INVALID_USER_INFO));
return googleUserResponse;
}
- 이미 회원가입을 한 유저라면 앱의 access token과 refresh token을 발행함
- 회원가입을 해야 하는 유저라면 isNewMember를 true로 하여 유저 정보를 넘김
public OAuthResponse login(String code) throws BaseException {
String accessToken = getAccessToken(code);
GoogleUserResponse userResponse = getUserInfo(accessToken);
User user = userRepository.findUserWithSocialId(userResponse.getId()).orElse(null);
if (user == null) {
return OAuthResponse.builder()
.isNewMember(true)
.socialType("google")
.id(userResponse.getId())
.name(userResponse.getName())
.email(userResponse.getEmail())
.build();
}
String appRefreshToken = jwtService.createRefreshToken();
String appAccessToken = jwtService.createAccessToken(user.getHandle());
user.updateRefreshToken(appRefreshToken);
userRepository.saveUser(user);
return OAuthResponse.builder()
.isNewMember(false)
.accessToken(appAccessToken)
.refreshToken(appRefreshToken)
.build();
}
- 추가 정보(이름, 별명, 프로필 이미지, 한 줄 소개) 입력 후 실제 유저 정보가 저장되는 코드
- access token과 refresh token 발급도 이루어짐
public OAuthResponse signup(UserInfoRequest userInfoRequest) throws BaseException {
userRepository.findUserWithSocialId(userInfoRequest.getSocialId())
.ifPresent(i -> new BaseException(EXIST_USER_ID));
String refreshToken = jwtService.createRefreshToken();
String accessToken = jwtService.createAccessToken(userInfoRequest.getHandle());
User user = User.signupUser()
.name(userInfoRequest.getName())
.email(userInfoRequest.getEmail())
.handle(userInfoRequest.getHandle())
.message(userInfoRequest.getMessage())
.socialId(userInfoRequest.getSocialId())
.profileImage(userInfoRequest.getProfileImage())
.socialType(SocialType.of(userInfoRequest.getSocialType()))
.build();
user.updateRefreshToken(refreshToken);
userRepository.saveUser(user);
return OAuthResponse.builder()
.isNewMember(false)
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
- access token과 refresh token으로 구현
- access token을 30분 refresh token을 한 달로 잡음
- access token이 만료되면 refresh token을 이용하여 재발급을 할 수 있음
- access token는 유저의 정보(pochak의 경우 handle)을 가지고 있음
- 프론트는 access token의 유효 기간이 만료 또는 만료 30초 전일 때 재발행을 요청함
- access token을 파싱 하여 handle을 얻고 handle을 통해 유저를 조회하여 db에 저장된 refresh token과 header를 통해 전달된 refresh token이 같은지 비교하여 유효성을 검증함
- refresh token의 유효성이 확인되면 재발급 그렇지 않다면 재로그인
- access token과 refresh token을 얻음
// OAuth
// TOKEN_PREFIX = "Bearer ";
// HEADER_AUTHORIZATION = "Authorization";
// HEADER_REFRESH_TOKEN = "RefreshToken";
public class JwtHeaderUtil {
public static String getAccessToken() {
return getToken(HEADER_AUTHORIZATION);
}
public static String getRefreshToken() {
return getToken(HEADER_REFRESH_TOKEN);
}
private static String getToken(String tokenHeader) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
String headerValue = request.getHeader(tokenHeader);
if (headerValue == null) return null;
if (StringUtils.hasText(headerValue) && headerValue.startsWith(TOKEN_PREFIX)) {
return headerValue.substring(TOKEN_PREFIX.length());
}
return headerValue;
}
}
- refresh token의 유효성 검사를 위해 access token이 만료되어도 파싱 할 것
public String getHandle(String token) throws BaseException {
try {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
} catch (ExpiredJwtException e) {
return e.getClaims().getSubject();
}
}
- access token 재발급
public PostTokenResponse reissueAccessToken() throws BaseException {
String accessToken = JwtHeaderUtil.getAccessToken();
String refreshToken = JwtHeaderUtil.getRefreshToken();
if (!validate(refreshToken)) {
throw new BaseException(INVALID_TOKEN);
}
String handle = validateRefreshToken(accessToken, refreshToken);
String newAccessToken = createAccessToken(handle);
return PostTokenResponse.builder()
.accessToken(newAccessToken)
.build();
}
- refresh token 유효성 검증
- access token에서 얻은 handle로 유저 조회해서 refresh token이 동일한지 확인
public String validateRefreshToken(String accessToken, String refreshToken) throws BaseException {
String handle = getHandle(accessToken);
User user = userRepository.findUserWithUserHandle(handle);
if (user.getRefreshToken() != refreshToken) {
throw new BaseException(INVALID_TOKEN);
}
return user.getHandle();
}