Skip to content

Commit

Permalink
[BE] Feature/#388 refresh token 및 로그아웃 기능 구현 (#411)
Browse files Browse the repository at this point in the history
* chore: redis 의존성 추가

* refactor: OauthService 필드에 final 추가

* feat: refreshToken 엔티티 및 레포지토리 구현

* feat: JwtTokenProvider RefreshToken 발급 구현

* feat: 로그인 시 RefreshToken 발급 기능 구현

* feat: Auth 패키지 커스텀 예외 추가

* refactor: validate 메서드 리팩터링

* chore: refreshToken 만료 시간 추가

* test: Test를 위한 설정 변경

* feat: 액세스 토큰 재발급 및 로그아웃 기능 구현

* chore: Redis 의존성 제거

* test: TestTokenProvider 객체 구현

* refactor: /logout HttpMethod 변경, cookie 관련 cors설정 및 maxAge 설정,

* test: DisplayName 추가

* feat: RTR 적용 및 OauthConntroller 제거, OauthService 및 TokenService 역할과 책임 재분배

* refactor : 피드백 반영

* refactor : 매직넘버 상수화

* refactor : 네이밍 수정

* feat: 쿠키 설정 추가
  • Loading branch information
junpakPark authored and kpeel5839 committed Sep 17, 2023
1 parent 3c358c3 commit 303a5fb
Show file tree
Hide file tree
Showing 6 changed files with 125 additions and 43 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.mapbefine.mapbefine.auth.exception;

import lombok.Getter;

@Getter
public enum AuthErrorCode {
ILLEGAL_MEMBER_ID("01100", "로그인에 실패하였습니다."),
ILLEGAL_TOKEN("01101", "로그인에 실패하였습니다."),
FORBIDDEN_ADMIN_ACCESS("01102", "로그인에 실패하였습니다."),
BLOCKING_MEMBER_ACCESS("01103", "로그인에 실패하였습니다."),
EXPIRED_TOKEN("01104", "기간이 만료된 토큰입니다.")
;

private final String code;
private final String message;

AuthErrorCode(String code, String message) {
this.code = code;
this.message = message;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class JwtTokenProvider implements TokenProvider {

private static final String EMPTY = "";

private final String secretKey;
private final long accessExpirationTime;
private final long refreshExpirationTime;
Expand All @@ -40,9 +41,7 @@ public String createAccessToken(String payload) {
}

public String createRefreshToken() {
UUID payload = UUID.randomUUID();

return createToken(payload.toString(), refreshExpirationTime);
return createToken(EMPTY, refreshExpirationTime);
}

private String createToken(String payload, Long validityInMilliseconds) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public void addCorsMappings(CorsRegistry registry) {
.allowedOrigins("http://localhost:3000", "https://mapbefine.kro.kr", "https://mapbefine.com")
.allowedHeaders("refresh-token")
.allowedMethods("*")
.allowCredentials(true)
.exposedHeaders(LOCATION, SET_COOKIE);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.mapbefine.mapbefine.common.interceptor;

import com.mapbefine.mapbefine.auth.application.AuthService;
import com.mapbefine.mapbefine.auth.dto.AuthInfo;
import com.mapbefine.mapbefine.auth.exception.AuthErrorCode;
import com.mapbefine.mapbefine.auth.exception.AuthException;
import com.mapbefine.mapbefine.auth.infrastructure.AuthorizationExtractor;
import com.mapbefine.mapbefine.auth.infrastructure.TokenProvider;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Objects;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class AdminAuthInterceptor implements HandlerInterceptor {

private final AuthorizationExtractor<AuthInfo> authorizationExtractor;
private final AuthService authService;
private final TokenProvider tokenProvider;

public AdminAuthInterceptor(
AuthorizationExtractor<AuthInfo> authorizationExtractor,
AuthService authService,
TokenProvider tokenProvider
) {
this.authorizationExtractor = authorizationExtractor;
this.authService = authService;
this.tokenProvider = tokenProvider;
}

@Override
public boolean preHandle(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull Object handler
) {
if (!(handler instanceof HandlerMethod)) {
return true;
}

Long memberId = extractMemberIdFromToken(request);

validateAdmin(memberId);
request.setAttribute("memberId", memberId);

return true;
}

private Long extractMemberIdFromToken(HttpServletRequest request) {
AuthInfo authInfo = authorizationExtractor.extract(request);
if (Objects.isNull(authInfo)) {
return null;
}
tokenProvider.validateAccessToken(authInfo.accessToken());

return Long.parseLong(tokenProvider.getPayload(authInfo.accessToken()));
}

private void validateAdmin(Long memberId) {
if (authService.isAdmin(memberId)) {
return;
}

throw new AuthException.AuthForbiddenException(AuthErrorCode.FORBIDDEN_ADMIN_ACCESS);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,39 @@
import com.mapbefine.mapbefine.auth.domain.AuthMember;
import com.mapbefine.mapbefine.auth.dto.AuthInfo;
import com.mapbefine.mapbefine.auth.infrastructure.AuthorizationExtractor;
import com.mapbefine.mapbefine.auth.infrastructure.JwtTokenProvider;
import com.mapbefine.mapbefine.common.exception.ErrorCode;
import com.mapbefine.mapbefine.common.exception.UnauthorizedException;
import com.mapbefine.mapbefine.auth.infrastructure.TokenProvider;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.Objects;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

@Component
public class AuthInterceptor implements HandlerInterceptor {

private static final String UNAUTHORIZED_ERROR_MESSAGE = "로그인에 실패하였습니다.";
private static final ErrorCode ILLEGAL_MEMBER_ID = new ErrorCode("03100", UNAUTHORIZED_ERROR_MESSAGE);
private static final ErrorCode ILLEGAL_TOKEN = new ErrorCode("03101", UNAUTHORIZED_ERROR_MESSAGE);

private final AuthorizationExtractor<AuthInfo> authorizationExtractor;
private final AuthService authService;
private final JwtTokenProvider jwtTokenProvider;
private final TokenProvider tokenProvider;

public AuthInterceptor(
AuthorizationExtractor<AuthInfo> authorizationExtractor,
AuthService authService,
JwtTokenProvider jwtTokenProvider
TokenProvider tokenProvider
) {
this.authorizationExtractor = authorizationExtractor;
this.authService = authService;
this.jwtTokenProvider = jwtTokenProvider;
this.tokenProvider = tokenProvider;
}

@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler
) throws Exception {
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull Object handler
) {
if (!(handler instanceof HandlerMethod handlerMethod)) {
return true;
}
Expand All @@ -52,22 +47,14 @@ public boolean preHandle(
Long memberId = extractMemberIdFromToken(request);

if (isLoginRequired((HandlerMethod) handler)) {
validateMember(memberId);
authService.validateMember(memberId);
}

request.setAttribute("memberId", memberId);

return true;
}

private void validateMember(Long memberId) {
if (authService.isMember(memberId)) {
return;
}

throw new UnauthorizedException(ILLEGAL_MEMBER_ID);
}

private boolean isAuthMemberNotRequired(HandlerMethod handlerMethod) {
return Arrays.stream(handlerMethod.getMethodParameters())
.noneMatch(parameter -> parameter.getParameterType().equals(AuthMember.class));
Expand All @@ -84,11 +71,9 @@ private Long extractMemberIdFromToken(HttpServletRequest request) {
if (Objects.isNull(authInfo)) {
return null;
}
String accessToken = authInfo.accessToken();
if (!jwtTokenProvider.validateToken(accessToken)) {
throw new UnauthorizedException(ILLEGAL_TOKEN);
}
return Long.parseLong(jwtTokenProvider.getPayload(accessToken));
tokenProvider.validateAccessToken(authInfo.accessToken());

return Long.parseLong(tokenProvider.getPayload(authInfo.accessToken()));
}

}
Original file line number Diff line number Diff line change
@@ -1,31 +1,29 @@
package com.mapbefine.mapbefine.oauth.application;

import com.mapbefine.mapbefine.auth.infrastructure.JwtTokenProvider;
import com.mapbefine.mapbefine.auth.exception.AuthErrorCode;
import com.mapbefine.mapbefine.auth.exception.AuthException.AuthUnauthorizedException;
import com.mapbefine.mapbefine.member.domain.Member;
import com.mapbefine.mapbefine.member.domain.MemberRepository;
import com.mapbefine.mapbefine.member.dto.response.MemberDetailResponse;
import com.mapbefine.mapbefine.oauth.domain.AuthCodeRequestUrlProviderComposite;
import com.mapbefine.mapbefine.oauth.domain.OauthMember;
import com.mapbefine.mapbefine.oauth.domain.OauthMemberClientComposite;
import com.mapbefine.mapbefine.oauth.domain.OauthServerType;
import com.mapbefine.mapbefine.oauth.dto.LoginInfoResponse;
import org.springframework.stereotype.Service;

@Service
public class OauthService {

private MemberRepository memberRepository;
private JwtTokenProvider jwtTokenProvider;
private AuthCodeRequestUrlProviderComposite authCodeRequestUrlProviderComposite;
private OauthMemberClientComposite oauthMemberClientComposite;
private final MemberRepository memberRepository;
private final AuthCodeRequestUrlProviderComposite authCodeRequestUrlProviderComposite;
private final OauthMemberClientComposite oauthMemberClientComposite;

public OauthService(
MemberRepository memberRepository,
JwtTokenProvider jwtTokenProvider,
AuthCodeRequestUrlProviderComposite authCodeRequestUrlProviderComposite,
OauthMemberClientComposite oauthMemberClientComposite
) {
this.memberRepository = memberRepository;
this.jwtTokenProvider = jwtTokenProvider;
this.authCodeRequestUrlProviderComposite = authCodeRequestUrlProviderComposite;
this.oauthMemberClientComposite = oauthMemberClientComposite;
}
Expand All @@ -34,19 +32,26 @@ public String getAuthCodeRequestUrl(OauthServerType oauthServerType) {
return authCodeRequestUrlProviderComposite.provide(oauthServerType);
}

public LoginInfoResponse login(OauthServerType oauthServerType, String code) {
public MemberDetailResponse login(OauthServerType oauthServerType, String code) {
OauthMember oauthMember = oauthMemberClientComposite.fetch(oauthServerType, code);
Member savedMember = memberRepository.findByOauthId(oauthMember.getOauthId())
.orElseGet(() -> register(oauthMember));

String accessToken = jwtTokenProvider.createToken(String.valueOf(savedMember.getId()));
validateMemberStatus(savedMember);

return LoginInfoResponse.of(accessToken, savedMember);
return MemberDetailResponse.from(savedMember);
}

private Member register(OauthMember oauthMember) {

return memberRepository.save(oauthMember.toRegisterMember());
}

private void validateMemberStatus(Member member) {
if (member.isNormalStatus()) {
return;
}
throw new AuthUnauthorizedException(AuthErrorCode.BLOCKING_MEMBER_ACCESS);
}

}

0 comments on commit 303a5fb

Please sign in to comment.