Skip to content

Commit

Permalink
[BE] refactor: JWT에 권한에 따른 클레임을 담기 위해 인증 기능 개편 (#982) (#985)
Browse files Browse the repository at this point in the history
* refactor: MemberAuthCommandService oAuth2Login 메서드명 변경

- oAuth2Login -> login

* refactor: Authentication을 사용한 인증/인가 리팩터링

* refactor: AuthenticationArgumentResolver 파라미터에 어노테이션 제거

* feat: 토큰 Subject에 식별자 추가

- 하위 호환성을 지키기 위해 기존 방식은 유지
  • Loading branch information
seokjin8678 authored May 22, 2024
1 parent 21629ab commit 9728b00
Show file tree
Hide file tree
Showing 50 changed files with 982 additions and 477 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.festago.auth;

import com.festago.auth.domain.authentication.AdminAuthentication;
import com.festago.auth.domain.authentication.Authentication;
import com.festago.common.exception.UnexpectedException;
import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class AdminAuthenticationArgumentResolver implements HandlerMethodArgumentResolver {

private final AuthenticateContext authenticateContext;

public AdminAuthenticationArgumentResolver(AuthenticateContext authenticateContext) {
Assert.notNull(authenticateContext, "The authenticateContext must not be null");
this.authenticateContext = authenticateContext;
}

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(AdminAuthentication.class);
}

@Override
public AdminAuthentication resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory
) {
Authentication authentication = authenticateContext.getAuthentication();
if (authentication instanceof AdminAuthentication adminAuthentication) {
return adminAuthentication;
}
throw new UnexpectedException("인가된 권한이 인자의 권한과 맞지 않습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.festago.auth;

import com.festago.auth.annotation.Authorization;
import com.festago.auth.application.HttpRequestTokenExtractor;
import com.festago.auth.domain.AuthenticationTokenExtractor;
import com.festago.auth.domain.authentication.Authentication;
import com.festago.common.exception.ErrorCode;
import com.festago.common.exception.ForbiddenException;
import com.festago.common.exception.UnauthorizedException;
import com.festago.common.exception.UnexpectedException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

public class AnnotationAuthorizationInterceptor implements HandlerInterceptor {

private final HttpRequestTokenExtractor httpRequestTokenExtractor;
private final AuthenticationTokenExtractor authenticationTokenExtractor;
private final AuthenticateContext authenticateContext;

public AnnotationAuthorizationInterceptor(
HttpRequestTokenExtractor httpRequestTokenExtractor,
AuthenticationTokenExtractor authenticationTokenExtractor,
AuthenticateContext authenticateContext)
{
Assert.notNull(httpRequestTokenExtractor, "The httpRequestTokenExtractor must not be null");
Assert.notNull(authenticationTokenExtractor, "The authenticationTokenExtractor must not be null");
Assert.notNull(authenticateContext, "The authenticateContext must not be null");
this.httpRequestTokenExtractor = httpRequestTokenExtractor;
this.authenticationTokenExtractor = authenticationTokenExtractor;
this.authenticateContext = authenticateContext;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Authorization authorization = handlerMethod.getMethodAnnotation(Authorization.class);
if (authorization == null) {
throw new UnexpectedException("HandlerMethod에 Authorization 어노테이션이 없습니다.");
}
String token = httpRequestTokenExtractor.extract(request)
.orElseThrow(() -> new UnauthorizedException(ErrorCode.NEED_AUTH_TOKEN));
Authentication authentication = authenticationTokenExtractor.extract(token);
if (authentication.getRole() != authorization.role()) {
throw new ForbiddenException(ErrorCode.NOT_ENOUGH_PERMISSION);
}
authenticateContext.setAuthentication(authentication);
return true;
}
}
82 changes: 0 additions & 82 deletions backend/src/main/java/com/festago/auth/AuthInterceptor.java

This file was deleted.

18 changes: 11 additions & 7 deletions backend/src/main/java/com/festago/auth/AuthenticateContext.java
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
package com.festago.auth;

import com.festago.auth.domain.Role;
import com.festago.auth.domain.authentication.AnonymousAuthentication;
import com.festago.auth.domain.authentication.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;

@Component
@RequestScope
public class AuthenticateContext {

private Long id;
private Role role = Role.ANONYMOUS;
private Authentication authentication = AnonymousAuthentication.getInstance();

public void setAuthenticate(Long id, Role role) {
this.id = id;
this.role = role;
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}

public Long getId() {
return id;
return authentication.getId();
}

public Role getRole() {
return role;
return authentication.getRole();
}

public Authentication getAuthentication() {
return authentication;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.festago.auth;

import com.festago.auth.application.HttpRequestTokenExtractor;
import com.festago.auth.domain.AuthenticationTokenExtractor;
import com.festago.auth.domain.Role;
import com.festago.auth.domain.authentication.Authentication;
import com.festago.common.exception.ErrorCode;
import com.festago.common.exception.ForbiddenException;
import com.festago.common.exception.UnauthorizedException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.util.Assert;
import org.springframework.web.servlet.HandlerInterceptor;

public class FixedAuthorizationInterceptor implements HandlerInterceptor {

private final HttpRequestTokenExtractor httpRequestTokenExtractor;
private final AuthenticationTokenExtractor authenticationTokenExtractor;
private final AuthenticateContext authenticateContext;
private final Role role;

public FixedAuthorizationInterceptor(
HttpRequestTokenExtractor httpRequestTokenExtractor,
AuthenticationTokenExtractor authenticationTokenExtractor,
AuthenticateContext authenticateContext,
Role role
) {
Assert.notNull(httpRequestTokenExtractor, "The httpRequestTokenExtractor must not be null");
Assert.notNull(authenticationTokenExtractor, "The authenticationTokenExtractor must not be null");
Assert.notNull(authenticateContext, "The authenticateContext must not be null");
Assert.notNull(role, "The role must not be null");
this.httpRequestTokenExtractor = httpRequestTokenExtractor;
this.authenticationTokenExtractor = authenticationTokenExtractor;
this.authenticateContext = authenticateContext;
this.role = role;
}

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = httpRequestTokenExtractor.extract(request)
.orElseThrow(() -> new UnauthorizedException(ErrorCode.NEED_AUTH_TOKEN));
Authentication authentication = authenticationTokenExtractor.extract(token);
if (authentication.getRole() != role) {
throw new ForbiddenException(ErrorCode.NOT_ENOUGH_PERMISSION);
}
authenticateContext.setAuthentication(authentication);
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.festago.auth;

import com.festago.auth.domain.authentication.Authentication;
import com.festago.auth.domain.authentication.MemberAuthentication;
import com.festago.common.exception.UnexpectedException;
import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class MemberAuthenticationArgumentResolver implements HandlerMethodArgumentResolver {

private final AuthenticateContext authenticateContext;

public MemberAuthenticationArgumentResolver(AuthenticateContext authenticateContext) {
Assert.notNull(authenticateContext, "The authenticateContext must not be null");
this.authenticateContext = authenticateContext;
}

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(MemberAuthentication.class);
}

@Override
public MemberAuthentication resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory
) {
Authentication authentication = authenticateContext.getAuthentication();
if (authentication instanceof MemberAuthentication memberAuthentication) {
return memberAuthentication;
}
throw new UnexpectedException("인가된 권한이 인자의 권한과 맞지 않습니다.");
}
}
12 changes: 8 additions & 4 deletions backend/src/main/java/com/festago/auth/RoleArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package com.festago.auth;

import com.festago.auth.domain.Role;
import com.festago.common.exception.ErrorCode;
import com.festago.common.exception.ForbiddenException;
import com.festago.common.exception.UnexpectedException;
import org.springframework.core.MethodParameter;
import org.springframework.util.Assert;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

/**
* @deprecated 기존 Long으로 식별자를 받는 Controller가 많기에, 해당 클래스 삭제하지 않고 유지
*/
@Deprecated
public class RoleArgumentResolver implements HandlerMethodArgumentResolver {

private final Role role;
Expand All @@ -30,9 +33,10 @@ public boolean supportsParameter(MethodParameter parameter) {

@Override
public Long resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
NativeWebRequest webRequest, WebDataBinderFactory binderFactory
) {
if (authenticateContext.getRole() != this.role) {
throw new ForbiddenException(ErrorCode.NOT_ENOUGH_PERMISSION);
throw new UnexpectedException("인가된 권한이 인자의 권한과 맞지 않습니다.");
}
return authenticateContext.getId();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.festago.auth.annotation;

import com.festago.auth.domain.Role;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Authorization {

Role role();
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.festago.auth.annotation;

import com.festago.auth.domain.Role;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Expand All @@ -9,6 +10,7 @@
@SecurityRequirement(name = "bearerAuth")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Authorization(role = Role.MEMBER)
public @interface MemberAuth {

}

This file was deleted.

This file was deleted.

Loading

0 comments on commit 9728b00

Please sign in to comment.