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

[LINKER-X] RefreshToken을 통한 AccessToken재발급 #37

Merged
merged 2 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

Expand Down Expand Up @@ -45,7 +46,11 @@ public class SecurityConfiguration {
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable);
.httpBasic(AbstractHttpConfigurer::disable)
.headers(
httpSecurityHeadersConfigurer ->
httpSecurityHeadersConfigurer.frameOptions(
HeadersConfigurer.FrameOptionsConfig::sameOrigin));

// oAuthHandler
http.oauth2Login(
Expand All @@ -72,7 +77,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
authorization
.requestMatchers(ignoredPath)
.permitAll()
.requestMatchers("/oauth2/**")
.requestMatchers("/oauth2/**", "/api/v1/auth/**")
.permitAll()
.anyRequest()
.authenticated());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
package com.imlinker.coreapi.core.auth.controller;

import com.imlinker.coreapi.core.auth.controller.request.ReIssueTokenRequest;
import com.imlinker.coreapi.core.auth.security.jwt.JwtTokenRefresher;
import com.imlinker.coreapi.core.auth.security.jwt.TokenResponse;
import com.imlinker.coreapi.support.response.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
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;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/auth")
@Tag(name = "Auth API", description = "인증 관련 API")
public class AuthController {}
public class AuthController {

private final JwtTokenRefresher refresher;

@PostMapping("/token/re-issue")
public ApiResponse<TokenResponse> reissueAccessToken(@RequestBody ReIssueTokenRequest request) {
TokenResponse response = refresher.refresh(request.accessToken(), request.refreshToken());

return ApiResponse.success(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.imlinker.coreapi.core.auth.controller.request;

import io.swagger.v3.oas.annotations.media.Schema;

public record ReIssueTokenRequest(
@Schema(description = "만료된 AccessToken") String accessToken,
@Schema(description = "유효한 RefreshToken") String refreshToken) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.imlinker.coreapi.core.auth.security.jwt;

import com.imlinker.domain.auth.AuthService;
import com.imlinker.domain.user.model.User;
import com.imlinker.error.ApplicationException;
import com.imlinker.error.ErrorType;
import java.util.Date;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtTokenRefresher {

private final JwtTokenProvider provider;
private final AuthService authService;

public TokenResponse refresh(String accessToken, String refreshToken) {
Long userId =
Long.parseLong(
provider.parseClaims(accessToken, TokenType.ACCESS_TOKEN).get("id").toString());
User user = authService.findByUserId(userId);

if (user.getRefreshToken() == null || !user.getRefreshToken().equals(refreshToken)) {
log.info("[AccessToken][재발급][실패] refreshToken이 존재하지 않거나, 일치하지 않음 (userId={})", userId);
throw new ApplicationException(ErrorType.UNAUTHORIZED);
}

Date expiration = provider.parseClaims(refreshToken, TokenType.REFRESH_TOKEN).getExpiration();
if (expiration.before(new Date())) {
log.info("[AccessToken][재발급][실패] 이미 만료된 RefreshToken (userId={})", userId);
throw new ApplicationException(ErrorType.UNAUTHORIZED);
}

String reIssuedAccessToken =
provider.generateToken(user.getId(), user.getEmail(), TokenType.ACCESS_TOKEN);

return new TokenResponse(reIssuedAccessToken, refreshToken);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.imlinker.coreapi.core.auth.security.jwt;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class TokenResponse {
private String accessToken;
private String refreshToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import com.imlinker.coreapi.core.auth.security.jwt.JwtTokenProvider;
import com.imlinker.coreapi.core.auth.security.jwt.TokenType;
import com.imlinker.coreapi.core.auth.security.oauth2.vendor.OAuthVendorAttributeResolver;
import com.imlinker.domain.auth.AuthService;
import com.imlinker.domain.auth.OAuthVendor;
import com.imlinker.domain.common.Email;
import com.imlinker.domain.common.URL;
import com.imlinker.domain.user.UserService;
import com.imlinker.domain.user.model.User;
import com.imlinker.error.ApplicationException;
import com.imlinker.error.ErrorType;
Expand All @@ -24,18 +24,18 @@
@Slf4j
@Component
public class CustomAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final UserService userService;
private final AuthService authService;
private final JwtTokenProvider jwtTokenProvider;
private final CustomClientOriginHostCache clientOriginHostCache;
private final Map<OAuthVendor, OAuthVendorAttributeResolver> vendorAttributeResolverRegistry;

public CustomAuthenticationSuccessHandler(
UserService userService,
AuthService authService,
JwtTokenProvider jwtTokenProvider,
CustomClientOriginHostCache clientOriginHostCache,
List<OAuthVendorAttributeResolver> vendorAttributeResolvers) {

this.userService = userService;
this.authService = authService;
this.jwtTokenProvider = jwtTokenProvider;
this.clientOriginHostCache = clientOriginHostCache;
this.vendorAttributeResolverRegistry =
Expand All @@ -59,21 +59,23 @@ public void onAuthenticationSuccess(
String oAuthId = resolver.getOAuthId(oAuth2User.getAttributes());
Email email = resolver.getEmail(oAuth2User.getAttributes());

boolean isMember = userService.isMember(oAuth2User.getVendor(), oAuthId);
boolean isMember = authService.isMember(oAuth2User.getVendor(), oAuthId);

if (!isMember) {
String nickname = resolver.getNickname(oAuth2User.getAttributes());
URL profileImgUrl = URL.of(resolver.getProfileImgUrl(oAuth2User.getAttributes()));

userService.create(oAuthId, nickname, email, profileImgUrl, oAuth2User.getVendor());
authService.create(oAuthId, nickname, email, profileImgUrl, oAuth2User.getVendor());
}

User user = userService.findByOAuthInfo(oAuth2User.getVendor(), oAuthId);
User user = authService.findByOAuthInfo(oAuth2User.getVendor(), oAuthId);
String accessToken =
jwtTokenProvider.generateToken(user.getId(), user.getEmail(), TokenType.ACCESS_TOKEN);
String refreshToken =
jwtTokenProvider.generateToken(user.getId(), user.getEmail(), TokenType.REFRESH_TOKEN);

authService.updateRefreshToken(user.getId(), refreshToken);

String redirectUri =
String.format(
"%s/my/feed?accessToken=%s&refreshToken=%s",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,6 @@ public ApiResponse<SearchSchedulesResponse.Schedules> getContactSchedules(
return ApiResponse.success(new SearchSchedulesResponse.Schedules(schedules));
}

@PostMapping
@Operation(summary = "일정 생성하기")
public ApiResponse<OperationResult> createSchedule(@RequestBody CreateScheduleRequest request) {
return ApiResponse.success(OperationResult.SUCCESS);
}

@PutMapping
@Operation(summary = "일정 수정하기")
public ApiResponse<OperationResult> updateSchedule(@RequestBody UpdateScheduleRequest request) {
return ApiResponse.success(OperationResult.SUCCESS);
}

@GetMapping("/upcoming/recommendation")
@Operation(summary = "다가오는 일정 추천")
public ApiResponse<GetUpComingScheduleRecommendationResponse.Schedule>
Expand All @@ -134,4 +122,22 @@ public ApiResponse<OperationResult> updateSchedule(@RequestBody UpdateScheduleRe
"https://r.yna.co.kr/global/home/v01/img/yonhapnews_logo_600x600_kr01.jpg")))));
return ApiResponse.success(response);
}

@PostMapping
@Operation(summary = "일정 생성하기")
public ApiResponse<OperationResult> createSchedule(@RequestBody CreateScheduleRequest request) {
return ApiResponse.success(OperationResult.SUCCESS);
}

@PutMapping
@Operation(summary = "일정 수정하기")
public ApiResponse<OperationResult> updateSchedule(@RequestBody UpdateScheduleRequest request) {
return ApiResponse.success(OperationResult.SUCCESS);
}

@DeleteMapping("/{scheduleId}")
@Operation(summary = "일정 삭제하기")
public ApiResponse<OperationResult> deleteSchedule(@PathVariable Long scheduleId) {
return ApiResponse.success(OperationResult.SUCCESS);
}
}
46 changes: 46 additions & 0 deletions domain/src/main/java/com/imlinker/domain/auth/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.imlinker.domain.auth;

import com.imlinker.domain.common.Email;
import com.imlinker.domain.common.URL;
import com.imlinker.domain.user.UserReader;
import com.imlinker.domain.user.UserUpdater;
import com.imlinker.domain.user.model.User;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Slf4j
@Service
@RequiredArgsConstructor
public class AuthService {
private final UserReader userReader;
private final UserUpdater userUpdater;

public User findByUserId(Long userId) {
return userReader.findById(userId);
}

public User findByOAuthInfo(OAuthVendor oAuthVendor, String oAuthIdentifier) {
return userReader.findByOAuthInfo(oAuthVendor, oAuthIdentifier);
}

public boolean isMember(OAuthVendor oAuthVendor, String oAuthIdentifier) {
return userReader.existByOAuthInfo(oAuthVendor, oAuthIdentifier);
}

public User create(
String oAuthId, String name, Email email, URL profileImgUrl, OAuthVendor oAuthVendor) {
log.info(
"[회원가입][시작] oAuthId: {}, name: {}, email: {}, oAuthVendor: {}",
oAuthId,
name,
email,
oAuthVendor);

return userUpdater.create(name, email, profileImgUrl, oAuthId, oAuthVendor);
}

public User updateRefreshToken(Long id, String refreshToken) {
return userUpdater.updateRefreshToken(id, refreshToken);
}
}
24 changes: 0 additions & 24 deletions domain/src/main/java/com/imlinker/domain/user/UserService.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.imlinker.domain.user;

import com.imlinker.domain.auth.OAuthVendor;
import com.imlinker.domain.common.Email;
import com.imlinker.domain.common.URL;
import com.imlinker.domain.contacts.Contacts;
import com.imlinker.domain.schedules.Schedules;
import com.imlinker.domain.tag.Tag;
Expand Down Expand Up @@ -46,27 +43,6 @@ public MyProfile getMyProfile(Long userId) {
.build();
}

public User findByOAuthInfo(OAuthVendor oAuthVendor, String oAuthIdentifier) {
return userReader.findByOAuthInfo(oAuthVendor, oAuthIdentifier);
}

public boolean isMember(OAuthVendor oAuthVendor, String oAuthIdentifier) {
return userReader.existByOAuthInfo(oAuthVendor, oAuthIdentifier);
}

public User create(
String oAuthId, String name, Email email, URL profileImgUrl, OAuthVendor oAuthVendor) {
log.info(
"[회원가입][시작] oAuthId: {}, name: {}, email: {}, profileImgUrl: {}, oAuthVendor: {}",
oAuthId,
name,
email,
profileImgUrl,
oAuthVendor);

return userUpdater.create(name, email, profileImgUrl, oAuthId, oAuthVendor);
}

@Transactional
public OperationResult update(UpdateUserParam param) {
User user = userUpdater.update(param.getId(), param.getName(), param.getEmail());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ public User create(
String oAuthIdentifier,
OAuthVendor oAuthVendor) {
return userRepository.save(
new User(null, oAuthVendor, oAuthIdentifier, name, email, profileImgUrl));
new User(null, oAuthVendor, oAuthIdentifier, name, email, profileImgUrl, null));
}

public User update(Long userId, String name, Email email) {
User updatedUser = fetch(userId).update(name, email);
return userRepository.save(updatedUser);
}

public User updateRefreshToken(Long id, String refreshToken) {
User updatedUser = fetch(id).update(refreshToken);
return userRepository.save(updatedUser);
}

public User fetch(Long id) {
return userRepository
.findById(id)
Expand Down
7 changes: 7 additions & 0 deletions domain/src/main/java/com/imlinker/domain/user/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ public class User {

private URL profileImgUrl;

private String refreshToken;

public User update(String name, Email email) {
this.name = name;
this.email = email;
Expand All @@ -34,4 +36,9 @@ public User update(URL profileImgUrl) {
this.profileImgUrl = profileImgUrl;
return this;
}

public User update(String refreshToken) {
this.refreshToken = refreshToken;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public class UserEntity extends BaseTimeEntity {
@Column(name = "email")
private String email;

@Column(name = "refresh_token")
private String refreshToken;

@Column(name = "profile_img_url")
private String profileImgUrl;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public static User toModel(UserEntity entity) {
.name(entity.getName())
.profileImgUrl(URL.of(entity.getProfileImgUrl()))
.email(Email.of(entity.getEmail()))
.refreshToken(entity.getRefreshToken())
.build();
}

Expand All @@ -30,6 +31,7 @@ public static UserEntity toEntity(User model) {
.name(model.getName())
.profileImgUrl(model.getProfileImgUrl().getValue())
.email(model.getEmail().getValue())
.refreshToken(model.getRefreshToken())
.build();
}
}
Loading