[spring security] AuthorizationManager 뜯어보기

rejs·2024년 3월 7일

spring security가 다 좋은데 @Deprecated가 너무 많다.

레거시 class 대신 AuthorizationManager를 사용하라는 데 도대체가 어떻게 사용하는 지는 알려주지 않고 있다.
그래서 이것저것 뜯어보고 뜯어본 결과를 대충 확인해보겠다.

Authorization 아키텍처

스프링시큐리티 인증 아키텍처 문서
Delegate-based AuthorizationManager Implementations 문단의 이미지를 참고할 것

  • AuthorizationManager는 3개의 구현체를 제공한다. (사실 7개지만 오른쪽의 4개의 구현체는 메소드 인가관련 구현체이므로 넘어가겠다.)
  1. RequestMatcherDelegatingAuthorizationManager
  2. AuthorityAuthorizationManager
  3. AuthenticatedAuthorizationManager

사실 permitall을 처리해주는 AuthorizationManager도 있지만 다음 문단에서 다루도록 하겠다.

RequestMatcherDelegatiAuthorizationManager

@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) {

		/* 1. RequestMatcher를 사용하여 URL이 매칭되는 게 있는 지 찾는다. */
		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));
			}
            /* 2. 찾았으면 해당 URL 인가를 담당하는 매니저에게 보낸다. */
			return manager.check(authentication,
					new RequestAuthorizationContext(request, matchResult.getVariables()));
		}
	}
	if (this.logger.isTraceEnabled()) {
		this.logger.trace(LogMessage.of(() -> "Denying request since did not find matching RequestMatcher"));
	}
	return DENY;
}

RequestMatcherDelegatiAuthorizationManager의 역할은 간단하다.

  1. RequestMatcher로 요청 URL과 매칭되는 지 확인한다.
  2. 찾았으면 해당 URL 인가를 담당하는 매니저에게 보낸다.

위의 코드는 실제 RequestMatcherDelegatiAuthorizationManager의 check 메서드의 코드이다.

RequestMatcherDelegatiAuthorizationManager는 생성될 때 {URL : manager} 형태의 데이터들을 받은 뒤 요청에 대하여 위에 설명한 과정을 통해서 인가업무를 위임한다고 할 수 있겠다.

그렇다면 {URL : manager} 형태의 데이터는 누가 주입해줄까?

AuthorizeHttpRequestsConfigurer

정답은 AuthorizeHttpRequestsConfigurer이다. 이 class가 어떻게 RequestMatcherDelegatiAuthorizationManager를 초기화하는 지 추적해보자.

http.authorizeHttpRequests((request)->request
				.requestMatchers("/css/**").permitAll()
				.requestMatchers("/js/**").permitAll()
				.anyRequest().authenticated()
);

스프링시큐리티를 공부 중이라면 위와 같은 코드가 익숙할 것이다.
위의 이미지는 여기서 .requestMatchers("/css/**").permitAll()을 처리하고 있다.
정확히는 requestMatchers가 아니라 permitAll이 어떻게 처리되는 지 확인하고 있다.

1. AuthorizedUrl.permitAll

public AuthorizationManagerRequestMatcherRegistry permitAll() {
	return access(permitAllAuthorizationManager);
}

우리가 permitAll을 했기 때문에 permitAll 처리 해주는 AuthorizationManager가 반환되는 모습을 볼 수 있다.

authenticated를 사용했다면 AuthenticatedAuthorizationManager 매니저가
hasRole을 사용했다면 AuthorityAuthorizationManager 매니저가 반환된다.

AuthenticatedAuthorizationManager, AuthorityAuthorizationManager 모두 Authorization 아키텍처 파트에서 확인했던 AuthorizationManager의 구현체이다.

참고로 permitAllAuthorizationManager는 아래와 같이 생긴 AuthorizeHttpRequestsConfigurer의 내장 클래스이다.

static final AuthorizationManager<RequestAuthorizationContext> permitAllAuthorizationManager = (a,
		o) -> new AuthorizationDecision(true);

2. AuthorizedUrl.access

public AuthorizationManagerRequestMatcherRegistry access(
		AuthorizationManager<RequestAuthorizationContext> manager) {
	Assert.notNull(manager, "manager cannot be null");
	return AuthorizeHttpRequestsConfigurer.this.addMapping(this.matchers, manager);
}

스프링시큐리티에서는 custom AuthorizationManager를 지원한다.

	.anyRequest().access(new CustomAuthorizationManager)

3~5. addMapping

  • requestMatcher와 manager를 매핑한 뒤 ({URL:Manager}), RequestMatcherDelegatiAuthorizationManager의 Builder에게 전달한다.

AuthorizationManager의 구현체 확인

AuthorityAuthorizationManager

// AuthorityAuthorizationManager::check
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, T object) {
	return this.delegate.check(authentication, this.authorities);
}

AuthoritiesAuthorizationManager에게 인가처리를 위임한다.

대신 AuthorityAuthorizationManagerSet<String> authorities를 설정해준다.

authoritiesauthentication을 전달받은 AuthoritiesAuthorizationManager가 실질적인 인가처리를 해준다.

AuthoritiesAuthorizationManager

@Override
public AuthorityAuthorizationDecision check(Supplier<Authentication> authentication,
		Collection<String> authorities) {
	boolean granted = isGranted(authentication.get(), authorities);
	return new AuthorityAuthorizationDecision(granted, AuthorityUtils.createAuthorityList(authorities));
}

private boolean isGranted(Authentication authentication, Collection<String> authorities) {
	return authentication != null && isAuthorized(authentication, authorities);
}

private boolean isAuthorized(Authentication authentication, Collection<String> authorities) {
	for (GrantedAuthority grantedAuthority : getGrantedAuthorities(authentication)) {
		if (authorities.contains(grantedAuthority.getAuthority())) {
			return true;
		}
	}
	return false;
}

AuthenticatedAuthorizationManager

AuthenticatedAuthorizationManager는 사용자가 인증이 되었는 지 확인하는 class이다.

AbstractAuthorizationStrategy 라고 해서 사용자가 어떻게 인증했는 지에 따라서 다르게 인가처리하는 것을 지원한다.

fullyAuthenticated, authenticated, anonymous, rememberMe를 지원한다.

0개의 댓글