Spring/Spring Security

[Spring Security] AuthorizationFilter로 HttpServletRequests 인증하기 (Authorize HttpServletRequests with AuthorizationFilter)

공대키메라 2022. 8. 9. 20:56

지난 시간에는 Spring Security의 인가 구조에 대해 알아보았다.

(지난 내용이 궁금하다면 여기 🤦‍♀️🤦‍♀️ 클릭!!)

 

이번 시간에는 AuthorizationFilter로 HttpServletRequests를 어떻게 인증하는지 알아볼 것이다.

 

참고한 사이트는 다음과 같다.

 

출처:

https://docs.spring.io/spring-security/reference/servlet/authorization/authorize-http-requests.html

 

Authorize HttpServletRequests with AuthorizationFilter

 

AuthroizationFilter는 FilterSecurityInterceptor를 대체한다. 뒤에 호환이 되게 남기기 위해, FilterSecurityInterceptor는 default로 남는다. 이 섹션은 어떻게 AuthroizationFilter가 작동하고 어떤 방식으로 default 설정을 오버라이드하는지에 대해 토론한다.

 

AuthorizationFilter는 HttpServletRequest에게 인증(authorization)을 제공한다. 이것은 Security Filter들의 하나인 FilterChainProxy안에 삽입되어있다. 

 

당신은 SecurityFilterChain을 선언할 때 default를 오버라이드 할 수 있다. authorizaRequests를 사용하는 대신에, authorizaHttpRequests를 사용해라. 다음처럼 말이다.

 

Example 1. Use authorizeHttpRequests

@Bean
SecurityFilterChain web(HttpSecurity http) throws AuthenticationException {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().authenticated();
        )
        // ...

    return http.build();
}

 

이것은 많은 방법으로 authorizeRequest를 개선한다. 

 

   1. metadasta source, config attributes, decision managers, 그리고 voter들 대신에 간편화된 AuthorizationManager API         를 사용해라. 이것은 재사용과 customization을 간단하게 한다. 

 

   2. Authentication lookup을 미뤄라. 모든 요청마다 확인해야 하는 authentication대신에, 이것은 인가(authorization) 결정       이 인증(authentication)을 필요로 하는 곳의 요청에서만 오직 확인한다. 

 

   3. bean 기반의(bean-based) 설정을 지원한다. 

 

authorizationmRequests가 authorizeRequests대신에 사용될 때, AuthorizationFilter가 FilterSecurityInterceptor 대신에 사용된다. 

 

https://docs.spring.io/spring-security/reference/_images/servlet/authorization/authorizationfilter.png

 

    1. 먼저, AuthorizationFilter가 SecurityContextHolder에서 Authentication 객체를 얻는다. 이것은 lookup을 지연하기 위            해  Supplier (Funtional interface 중 하나 말하는 거임!)로 감싸있다. 

   

    2. 두번째로, AuthorizationFilter가 HttpServletRequest, HttpServletResponse, FilterChain으로부터 FilterInvocation                  을 생성한다.

 

   3. 다음으로, Supplier<Authentication>과 FilterInvocation을 AuthorizationManager에게 넘긴다.

 

   4. 만약 인증이 거부되면, AccessDeniedException이 던져진다. 이러한 경우에 ExceptionTranslationFilter가 AccessDeninedException을 처리한다.

 

   5. 만약 접근이 허가된다면, AuthorizationFilter는 application이 일반적으로 처리하는것을 허용하는 FilterChain을 계속 이어간다. 

 

위에 나오는 것들 중 우리가 많이 봐왔던 것들이 있어서 다시 한 번 상기하고 넘어가려고 한다. 

SecurityContextHolder는....
SecurityContext를 저장하는 곳이며, 어디서는 접근 가능하게 global하게 선언되어있다. 
사용자의 인증 정보를 가지고 있는 곳이다.

AuthorizationManager는....
AuthorizationManager는 AccessDecisionManager와 AccessDecisionVoter를 둘 다 대체한다.
AuthenticationManager들은 AuthorizationFilter에 의해 호출되고, 최종 접근 제어 결정을 내리는데 책임이 있다. 
말 그대로 인가 처리자이다(authorization manager)


ExceptionTranslationFilter는...
AccessDeniedException과  AuthenticationException을 HTTP 응답으로 변형되도록 돕는다. 
한국어로 읽어보면 오류 처리 필터다. 발생한 오류를 잘 전달하는 filter인 것이다.

여윽시 지식이란 쌓기 시작하면, 결국 반복되는 것이 있기 마련인 것 같다.

 

우리는 우선 순위에 따라 더 많은 규칙을 추가함으로써 다른 규칙들을 가지기 위해 Spring Security를 설정할 수 있다. 

 

Example 2. Authorize Requests

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
	http
		// ...
		.authorizeHttpRequests(authorize -> authorize    // 1              
			.mvcMatchers("/resources/**", "/signup", "/about").permitAll()// 2
			.mvcMatchers("/admin/**").hasRole("ADMIN")// 3  
			.mvcMatchers("/db/**").access((authentication, request) ->  
			    Optional.of(hasRole("ADMIN").check(authentication, request))
			        .filter((decision) -> !decision.isGranted())
			        .orElseGet(() -> hasRole("DBA").check(authentication, request));
			)  // 4
			.anyRequest().denyAll()       // 5
		);

	return http.build();
}

 

1. 다양한 구체화된 인가 규칙을 순서대로 선언하고 고려하고 있음.

 

2. 우리는 어떤 사용자가 접근하는 다양한 URL 패턴을 구체화한다. 구체적으로, 어떤 사용자는 만약 URL이 /resources/, /signup"으로 또는 /about으로 시작한다면 요청을 접근할 수 있다. 

(물론 /resources 이후에 모든 접근은 **로 인해 전부 허용된다.)

 

3. 특정 역할(role)을 가진 사람들만 해당 주소로 접근 가능

 

4. /db로 시작하는 모든 URL은 ROLE_ADMIN과 ROLE_DBA를 가진 사용자를 요청한다. 

(현재  Optional로 ADMIN role을 가지고 있는지 먼저 확인하고, 없으면 DBA로 확인한다.)

 

5. 이미 매칭되지 않는 모든 URL은 접근 거부된다. 이건 당신이 우연히 권한 rule을 업데이트하는것을 까먹길 원하지 않는다면, 좋은 전략이다!

 

당신은 자신만의 REquestMatcherDelegatingAuthorizationManager를 만들어서 빈 기반의 접근을 할 수 있다.

 

Example 3. Configure RequestMatcherDelegatingAuthorizationManager

@Bean
SecurityFilterChain web(HttpSecurity http, AuthorizationManager<RequestAuthorizationContext> access)
        throws AuthenticationException {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest().access(access)
        )
        // ...

    return http.build();
}

@Bean
AuthorizationManager<RequestAuthorizationContext> requestMatcherAuthorizationManager(HandlerMappingIntrospector introspector) {
    RequestMatcher permitAll =
            new AndRequestMatcher(
                    new MvcRequestMatcher(introspector, "/resources/**"),
                    new MvcRequestMatcher(introspector, "/signup"),
                    new MvcRequestMatcher(introspector, "/about"));
    RequestMatcher admin = new MvcRequestMatcher(introspector, "/admin/**");
    RequestMatcher db = new MvcRequestMatcher(introspector, "/db/**");
    RequestMatcher any = AnyRequestMatcher.INSTANCE;
    AuthorizationManager<HttpRequestServlet> manager = RequestMatcherDelegatingAuthorizationManager.builder()
            .add(permitAll, (context) -> new AuthorizationDecision(true))
            .add(admin, AuthorityAuthorizationManager.hasRole("ADMIN"))
            .add(db, AuthorityAuthorizationManager.hasRole("DBA"))
            .add(any, new AuthenticatedAuthorizationManager())
            .build();
    return (context) -> manager.check(context.getRequest());
}

 

사실 그렇게 까지 뭐...customize할 일이 있을 까 싶지만... 언제든지 유연하게 변경 가능한 여지를 남기는 spring 대단하다옹!

 

또한 모든 요청 matcher에서 당신의 custom 인가 manager를 연결할 수 있다.

 

Example 4. Custom Authorization Manager

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .mvcMatchers("/my/authorized/endpoint").access(new CustomAuthorizationManager());
        )
        // ...

    return http.build();
}

 

혹은 모든 요청에 제공되는 것도 가능하다.

 

Example 5. Custom Authorization Manager for All Requests

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .anyRequest.access(new CustomAuthorizationManager());
        )
        // ...

    return http.build();
}

 

 

기본적으로, AuthorizationFilter는 DispatcherType.ERROR와 DispatcherType.ASYNC를 적용하지 않는다.

 

우리는 shouldFilterAllDispatcherTypes 메소드를 사용함으로써, 모든 dispatcher 타입들이 인증 규칙을 적용하도록 Spring security에서 설정할 수 있다.

 

Example 6. Set shouldFilterAllDispatcherTypes to true

@Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
    http
        .authorizeHttpRequests((authorize) -> authorize
            .shouldFilterAllDispatcherTypes(true)
            .anyRequest.authenticated()
        )
        // ...

    return http.build();
}