[TIL] Spring Security 3 (권한부여)
코드스테이츠 백엔드 부트캠프 D+95
어제 공부했던 Spring Security 인증처리 과정에서대해
코드를 찾아가보면서 어느정도 깨달음이 있어서
진전이 있는 느낌이 들어 오랜만에 편안한 마음으로 공부를 했다.
오늘은 Spring Security 권한관련 Filter를 공부하는 날이고
섹션 중 Spring Security는 마지막인 시간이다.
어제 (Spring Security 인증처리)에 대한 내용을
코드와 아케텍쳐를 통해 쭉 살펴보았다.
오늘은 권한과 관련된 Filter인
AuthorizationFilter
클래스에 대한 권한 부여의
간단한 처리 흐름과 코드를 살펴보려고한다.
Spring Security 권한부여
어제는 인증처리에 관련된 내용을 배웠다.
인증이란 이 사이트를 이용할 수 있는 유저인지 확인하는 과정이다.
하지만 권한부여는 뭘까?
인증된 사용자가 모든 리소스에 접근을 제한하는 것을 말한다.
헤딩 사용자가 이용할 수 있는 리소스를 제한하거나 풀어주는 것을
권한 부여라고 한다.
권한부여 처리흐름
AuthorizationFilter
Spring Security에서는 권한 부여를 위해
AuthorizationFilter
클래스를 이용해 처리를 한다.
SecurityFilterChain을 구성하는 Filter 중 하나로
인증 Filter를 거친 후 진행하게 되어진다.
본격적으로 AuthorizationFilter
의 권한처리 흐름을 알아보자
(1). SecurityContextHolder
로 부터 Authentication
객채를 가져온다.
(Spring Security 인증처리) 과정에서 인증이 완료된 객체 Authentication
를
SecurityContextHolder
에 넣어줬던 흐름이 인증처리의 마지막이어었다.
public class AuthorizationFilter extends OncePerRequestFilter {
... 이상 생략
private Authentication getAuthentication() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationCredentialsNotFoundException(
"An Authentication object was not found in the SecurityContext");
}
return authentication;
}
... 이하 생략
}
실제로 AuthorizationFilter
클래스 코드 중 getAuthentication();
메서드를 이용해
Authentication
객체를 꺼내는 모습을 코드로 볼 수 있다.
(2). Authentication
객체와 HttpServletRequest
객체를
AuthorizationManager
에게 전달한다.
public class AuthorizationFilter extends OncePerRequestFilter {
private final AuthorizationManager<HttpServletRequest> authorizationManager;
... 생략
public AuthorizationFilter(AuthorizationManager<HttpServletRequest> authorizationManager) {
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.authorizationManager = authorizationManager;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
if (decision != null && !decision.isGranted()) {
throw new AccessDeniedException("Access Denied");
}
filterChain.doFilter(request, response);
}
... 이하 생략
}
실제로 코드를 확인해보면
생성자로 AuthorizationManager
의 객체를 DI받고 있다.
그리고 .check(this::getAuthentication, request);
메서드를 호출해
Authentication
와 HttpServletRequest
객체를 매개변수로 넘겨주고있다.
(3). AuthorizationManager
는
권한 부여 처리를 총괄하는 매니저 역할의 인터페이스이다.
구현체는 RequestMatcherDelegatingAuthorizationManager
클래스이다.
@FunctionalInterface
public interface AuthorizationManager<T> {
... 이상 생략
@Nullable
AuthorizationDecision check(Supplier<Authentication> authentication, T object);
}
코드로도 볼 수 있듯이 인터페이스에 메서드가 정의되어있고
public final class RequestMatcherDelegatingAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
...
이상 생략
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest request) {
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorizing %s", request));
}
for (RequestMatcherEntry<AuthorizationManager<RequestAuthorizationContext>> mapping : this.mappings) {
RequestMatcher matcher = mapping.getRequestMatcher();
MatchResult matchResult = matcher.matcher(request);
if (matchResult.isMatch()) {
AuthorizationManager<RequestAuthorizationContext> manager = mapping.getEntry();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
}
return manager.check(authentication,
new RequestAuthorizationContext(request, matchResult.getVariables()));
}
}
this.logger.trace("Abstaining since did not find matching RequestMatcher");
return null;
}
... 이하 생략
}
구현체인 RequestMatcherDelegatingAuthorizationManager
클래스에
check();
메서드가 Override되어 있는 모습을 확인할 수 있다.
(4),(5). 이제 전달받은 객체들로 check();
메서드 내부에서
권한 처리에 대한 로직이 수행된다.
RequestMatcherEntry
정보를 얻은 후에RequestMatcher
객체를 얻는다.RequestMatcher
객체로HttpServletRequest
객체를 넣어MatchResult
객체를 만든다.MatchResult
객체의 메서드.isMatch();
로 권한을 체크한다.
만약에 권한이 존재해 if (matchResult.isMatch())
메서드를 통과한다면
manager.check();
를 통해서 matchResult.getVariables();
를 통해서
Map을 담고있는 객체가 반환되며 권한 부여과정이 진행되고
만약에 권한이 존재하지 않아 if (matchResult.isMatch())
메서드를 통과하지 못한다면
return null;
로 인해 null값이 반환되게 되어진다.
즉, 맨처음에 우리가 check();
메서드를 호출했던 AuthorizationFilter
에서
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
AuthorizationDecision decision = this.authorizationManager.check(this::getAuthentication, request);
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, decision);
if (decision != null && !decision.isGranted()) {
throw new AccessDeniedException("Access Denied");
}
filterChain.doFilter(request, response);
}
throw new AccessDeniedException("Access Denied");
예외를 던지게되어
권한을 부여하지 않도록 처리를 해주는 것이다.
접근 제어 표현식
Spring Security 웹 및 메서드 보안을 위한 표현식
Spring EL
표현식 | : 설명 : |
hasRole | 현재 보안 주체(principal)가 지정된 역할을 갖고 있는지 여부를 확인하고 가지고 있다면 true를 리턴한다. hasRole(’admin’)처럼 파라미터로 넘긴 role이 ROLE_ 로 시작하지 않으면 기본적으로 추가한다. (DefaultWebSecurityExpressionHandler의 defaultRolePrefix를 수정하면 커스텀할 수 있다.) |
hasAnyRole | 현재 보안 주체가 지정한 역할 중 1개라도 가지고 있으면 true를 리턴한다. (문자열 리스트를 콤마로 구분해서 전달한다.) ex) hasAnyRole(’admin’, ‘user’) |
hasAuthority | 현재 보안 주체가 지정한 권한을 갖고 있는지 여부를 확인하고 가지고 있다면 true를 리턴한다. ex) hasAuthority(’read’) |
hasAnyAuthority | 현재 보안 주체가 지정한 권한 중 하나라도 있으면 true를 리턴한다. ex) hasAnyAuthority(’read’, ‘write’) |
principal | 현재 사용자를 나타내는 principal 객체에 직접 접근할 수 있다. |
authentication | SecurityContext로 조회할 수 있는 현재 Authentication 객체에 직접 접근할 수 있다. |
permitAll | 항상 true로 평가한다. |
denyAll | 항상 false로 평가한다. |
isAnonymous() | 현재 보안 주체가 익명 사용자면 true를 리턴한다. |
isRememberMe() | 현재 보안 주체가 remember-me 사용자면 true를 리턴한다. |
isAuthenticated() | 사용자가 익명이 아닌 경우 true를 리턴한다. |
isFullyAuthenticated() | 사용자가 익명 사용자나 remember-me 사용자가 아니면 true를 리턴한다. |
hasPermission (Object target, Object permission) |
사용자가 target에 해당 permission 권한이 있으면 true를 리턴한다. ex) hasPermission(domainObject, ‘read’) |
hasPermission (Object targetId, String targetType, Object permission) |
사용자가 target에 해당 permission 권한이 있으면 true를 리턴한다. ex) hasPermission(1, ‘com.example.domain.Message’, ‘read’) |
이렇게 오늘을 끝으로 Spring Security의
인증과 권한에 대해 알아보았다.
좀 더 깊은내용을 이해하기 위해서는 여러가지 Filter에 대해 공부가
필요해보이고, 내일부터 공부하는 JWT를 이용할때 어떠한 방식으로
Spring Security 로직이 변경되는지 내일부터 다시 공부해보자
오늘 공부는 여기서 끝 !!
오늘의 커피량: ☕️ ☕️
오늘의 점심: 삽겹살, 된장찌개, 밥