Skip to content

Commit

Permalink
[Feat]: 토큰 재발급 API 구현
Browse files Browse the repository at this point in the history
기존의 AuthService 명칭 SocialService로 변경
토큰 재발급 API 구현

Related to: #13
  • Loading branch information
dev-Crayon committed Dec 19, 2023
1 parent d081307 commit 916c272
Show file tree
Hide file tree
Showing 12 changed files with 134 additions and 66 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,45 @@
package io.sobok.SobokSobok.auth.application;

import io.sobok.SobokSobok.auth.ui.dto.SocialLoginRequest;
import io.sobok.SobokSobok.auth.ui.dto.SocialLoginResponse;
import io.sobok.SobokSobok.auth.ui.dto.SocialSignupRequest;
import io.sobok.SobokSobok.auth.ui.dto.SocialSignupResponse;
import io.sobok.SobokSobok.auth.infrastructure.UserRepository;
import io.sobok.SobokSobok.auth.ui.dto.JwtTokenResponse;
import io.sobok.SobokSobok.exception.ErrorCode;
import io.sobok.SobokSobok.exception.model.NotFoundException;
import io.sobok.SobokSobok.security.jwt.Jwt;
import io.sobok.SobokSobok.security.jwt.JwtProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

public abstract class AuthService {
@Service
@RequiredArgsConstructor
public class AuthService {

public abstract SocialSignupResponse signup(SocialSignupRequest request);
private final UserRepository userRepository;

public abstract SocialLoginResponse login(SocialLoginRequest request);
private final RedisTemplate<String, String> redisTemplate;
private final JwtProvider jwtProvider;

@Transactional
public JwtTokenResponse refresh(String refresh) {

ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
String socialId = valueOperations.get(refresh);

if (socialId == null) {
throw new NotFoundException(ErrorCode.UNREGISTERED_TOKEN);
}

if(!userRepository.existsBySocialInfoSocialId(socialId)) {
throw new NotFoundException(ErrorCode.UNREGISTERED_USER);
}

Jwt jwt = jwtProvider.getUserJwt(socialId);

return JwtTokenResponse.builder()
.accessToken(jwt.accessToken())
.refreshToken(jwt.refreshToken())
.build();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,19 @@
import io.sobok.SobokSobok.security.jwt.Jwt;
import io.sobok.SobokSobok.security.jwt.JwtProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
public class KakaoAuthService extends AuthService {
public class KakaoSocialService extends SocialService {

private final KakaoService kakaoService;

private final UserRepository userRepository;

private final JwtProvider jwtProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;

@Override
@Transactional
Expand All @@ -60,7 +55,7 @@ public SocialSignupResponse signup(SocialSignupRequest request) {
.roles(Role.USER.name())
.build());

Jwt jwt = getUserJwt(signupUser.getSocialInfo().getSocialId());
Jwt jwt = jwtProvider.getUserJwt(signupUser.getSocialInfo().getSocialId());

return SocialSignupResponse.builder()
.id(signupUser.getId())
Expand All @@ -83,16 +78,11 @@ public SocialLoginResponse login(SocialLoginRequest request) {
loginUser.updateDeviceToken(request.deviceToken());
}

Jwt jwt = getUserJwt(loginUser.getSocialInfo().getSocialId());
Jwt jwt = jwtProvider.getUserJwt(loginUser.getSocialInfo().getSocialId());

return SocialLoginResponse.builder()
.accessToken(jwt.accessToken())
.refreshToken(jwt.refreshToken())
.build();
}

private Jwt getUserJwt(String principle) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(principle, null, List.of(new SimpleGrantedAuthority(Role.USER.name())));
return jwtProvider.createToken(authenticationToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package io.sobok.SobokSobok.auth.application;

import io.sobok.SobokSobok.auth.ui.dto.*;

public abstract class SocialService {

public abstract SocialSignupResponse signup(SocialSignupRequest request);

public abstract SocialLoginResponse login(SocialLoginRequest request);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.sobok.SobokSobok.auth.application;

import io.sobok.SobokSobok.auth.domain.SocialType;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@RequiredArgsConstructor
@Component
public class SocialServiceProvider {

private static final Map<SocialType, SocialService> socialServiceMap = new HashMap<>();

private final KakaoSocialService kakaoSocialService;

@PostConstruct
void initializeSocialServiceMap() {
socialServiceMap.put(SocialType.KAKAO, kakaoSocialService);
}

public SocialService getSocialService(SocialType socialType) {
return socialServiceMap.get(socialType);
}
}
36 changes: 26 additions & 10 deletions src/main/java/io/sobok/SobokSobok/auth/ui/AuthController.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package io.sobok.SobokSobok.auth.ui;

import io.sobok.SobokSobok.auth.application.AuthService;
import io.sobok.SobokSobok.auth.application.AuthServiceProvider;
import io.sobok.SobokSobok.auth.application.SocialService;
import io.sobok.SobokSobok.auth.application.SocialServiceProvider;
import io.sobok.SobokSobok.auth.domain.SocialType;
import io.sobok.SobokSobok.auth.ui.dto.SocialLoginRequest;
import io.sobok.SobokSobok.auth.ui.dto.SocialLoginResponse;
import io.sobok.SobokSobok.auth.ui.dto.SocialSignupRequest;
import io.sobok.SobokSobok.auth.ui.dto.SocialSignupResponse;
import io.sobok.SobokSobok.auth.ui.dto.*;
import io.sobok.SobokSobok.common.dto.ApiResponse;
import io.sobok.SobokSobok.exception.SuccessCode;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -23,7 +21,8 @@
@Tag(name = "Auth", description = "인증 관련 컨트롤러")
public class AuthController {

private final AuthServiceProvider authServiceProvider;
private final AuthService authService;
private final SocialServiceProvider socialServiceProvider;

@PostMapping("/signup")
@Operation(
Expand All @@ -32,12 +31,12 @@ public class AuthController {
)
public ResponseEntity<ApiResponse<SocialSignupResponse>> signup(@RequestBody @Valid final SocialSignupRequest request) {

AuthService authService = authServiceProvider.getAuthService(request.socialType());
SocialService socialService = socialServiceProvider.getSocialService(request.socialType());
return ResponseEntity
.status(HttpStatus.CREATED)
.body(ApiResponse.success(
SuccessCode.SOCIAL_SIGNUP_SUCCESS,
authService.signup(request)
socialService.signup(request)
));
}

Expand All @@ -52,16 +51,33 @@ public ResponseEntity<ApiResponse<SocialLoginResponse>> login(
@RequestParam final String deviceToken
) {

AuthService authService = authServiceProvider.getAuthService(socialType);
SocialService socialService = socialServiceProvider.getSocialService(socialType);
return ResponseEntity
.status(HttpStatus.OK)
.body(ApiResponse.success(
SuccessCode.SOCIAL_LOGIN_SUCCESS,
authService.login(SocialLoginRequest.builder()
socialService.login(SocialLoginRequest.builder()
.code(code)
.socialType(socialType)
.deviceToken(deviceToken)
.build())
));
}

@GetMapping("/refresh")
@Operation(
summary = "JWT 토큰 재발급",
description = "Refresh token을 통해 JWT 토큰을 재발급받는 API 입니다."
)
public ResponseEntity<ApiResponse<JwtTokenResponse>> refresh(
@RequestHeader("Refresh-Token") final String refreshToken
) {

return ResponseEntity
.status(HttpStatus.OK)
.body(ApiResponse.success(
SuccessCode.TOKEN_REFRESH_SUCCESS,
authService.refresh(refreshToken)
));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.sobok.SobokSobok.auth.ui.dto;

import lombok.Builder;

@Builder
public record JwtTokenResponse(

String accessToken,

String refreshToken
) { }
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
@Builder
public record SocialLoginRequest(

@Schema(required = true)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
String code,

@Schema(required = true)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
SocialType socialType,

@Schema(required = true)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
String deviceToken
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@

public record SocialSignupRequest(

@Schema(required = true)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
String code,

@Schema(required = true)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
SocialType socialType,

@Schema(required = true)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
String username,

@Schema(required = true)
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
String deviceToken
) {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/io/sobok/SobokSobok/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public enum ErrorCode {

// auth
UNREGISTERED_USER(HttpStatus.NOT_FOUND, "등록되지 않은 사용자입니다."),
UNREGISTERED_TOKEN(HttpStatus.NOT_FOUND, "등록되지 않은 토큰입니다."),
ALREADY_EXISTS_USER(HttpStatus.CONFLICT, "이미 회원가입이 완료된 사용자입니다."),
ALREADY_USING_USERNAME(HttpStatus.CONFLICT, "이미 사용중인 username입니다."),

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/io/sobok/SobokSobok/exception/SuccessCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public enum SuccessCode {

// auth
SOCIAL_SIGNUP_SUCCESS(HttpStatus.CREATED, "소셜 회원가입에 성공했습니다."),
SOCIAL_LOGIN_SUCCESS(HttpStatus.OK, "소셜 로그인에 성공했습니다.")
SOCIAL_LOGIN_SUCCESS(HttpStatus.OK, "소셜 로그인에 성공했습니다."),
TOKEN_REFRESH_SUCCESS(HttpStatus.OK, "토큰 재발급에 성공했습니다,"),
;

private final HttpStatus code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.sobok.SobokSobok.auth.domain.Role;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
Expand All @@ -19,6 +20,7 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -57,6 +59,11 @@ public void afterPropertiesSet() {
this.key = Keys.hmacShaKeyFor(keyBytes);
}

public Jwt getUserJwt(String principle) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(principle, null, List.of(new SimpleGrantedAuthority(Role.USER.name())));
return createToken(authenticationToken);
}

public Jwt createToken(Authentication authentication) {

String authorities = authentication.getAuthorities().stream()
Expand All @@ -67,7 +74,7 @@ public Jwt createToken(Authentication authentication) {
String refreshToken = generateToken(authentication.getName(), "", REFRESH_TOKEN_TYPE, refreshTokenExpiration);

ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
valueOperations.set(authentication.getName(), refreshToken, refreshTokenExpiration, TimeUnit.SECONDS);
valueOperations.set(refreshToken, authentication.getName(), refreshTokenExpiration, TimeUnit.SECONDS);

return Jwt.builder()
.accessToken(accessToken)
Expand Down

0 comments on commit 916c272

Please sign in to comment.