MvC 中 Spring Security 中的每个路径/路由有不同的 AuthenticationManager

Eug*_*yer 3 rest spring-mvc spring-security

前言

由于 StackOverflow 上已经有很多关于此问题的问题,我首先想确保这不是重复和区分的。

这是关于

  • 在 2 个不同的 AuthenticationManager 中拥有 2 个(或更多)不同的 AuthenticationProvider,用于不同的路由。
  • 使用 Spring Security 5.5 而非 3.x 中的方法
  • 使用基于非 XML 配置的方法

所以问题不在于:

  • 如何在 AuthenticationManager 中包含多个 AuthenticationProvider 以提供“替代身份验证”(大多数问题往往是)

案件

假设有 2 个自定义 AuthenticationProvider:CATApiTokenProviderDOGApiTokenProvider。我们故意不谈论 AOuth/JWT/Basic/Form 提供者,因为它们提供了快捷方式。

现在我们有 2 个 REST API 端点/dog/endpoint/cat/endpoint.

问题

今天,如何使用 Spring Security 5.5 正确实现这一点:

  • 我们希望身份验证提供者CATApiTokenProvider只能对以下请求进行身份验证/cat/endpoint
  • 我们希望身份验证提供者DOGApiTokenProvider只能对以下请求进行身份验证/dog/endpoint

因此,无法使用 cat 令牌进行身份验证/dog/endpoint,也无法使用狗令牌进行身份验证/cat/endpoint

我的想法/方法

a) 据我所知,由于我有自定义的 Cat/Dog 过​​滤器,因此可以在创建 bean 时使用AuthenticationManagerResolver并将一个实例传递到过滤器中。这个解析器可能看起来像

  public AuthenticationManagerResolver<HttpServletRequest> resolver()
  {
    return request -> {
      if (request.getPathInfo().startsWith("/dog/")) {
        try {
          return ???;
        } catch (Exception exception) {
          log.error(exception);
        }
      }
      if (request.getPathInfo().startsWith("/cat/")) {
        try {
          return ???;
        } catch (Exception exception) {
          log.error(exception);
        }
      }
    };
  }
Run Code Online (Sandbox Code Playgroud)

两个问题是:

  • 如何在这里返回不同的身份验证管理器?如何用 CatAP 和 DogAP 分别实例化 2 个不同的 AM?目前我使用 public void configure(AuthenticationManagerBuilder auth),但据我所知,我只会配置“一个”AuthenticationManager,我可以在那里添加 DogAP 和 CatAP,但这会让 1 AM 具有 2 个 AP,所以当使用这个 AM 时,我可以使用cat 端点上的狗令牌
  • 这真的是实施这个的正确方法吗?我本来期望能够提供 SecurityConfiguration 级别的 AM

b) 以某种方式实例化 2 个不同的 AuthenticationManager,然后使用 SecurityConfiguration 将它们分配给不同的匹配器。

两个问题:

  • 使用不同的提供商生成 2 个不同的 AM 的正确方法是什么?
  • 我无法理解如何为规范添加 AM
http.authorizeRequests()
      .antMatchers("/dog/**")
      .?
Run Code Online (Sandbox Code Playgroud)

Mar*_*gio 5

您可以发布多个过滤器链或将您自己的AuthenticationFilter过滤器链与AuthenticationManagerResolver

您可以使用AuthenticationManagerResolver返回不同的AuthenticationManagers。从 Spring Security 5.4.0 开始,我们不再需要扩展WebSecurityConfigurerAdapter来配置我们的SecurityFilterChain,您可以定义一个 beanSecurityFilterChain类型。

我将详细介绍您自己的接线AuthenticationFilter

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((authz) -> authz
                .anyRequest().authenticated());
        http.addFilterBefore(apiAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    private AuthenticationFilter apiAuthenticationFilter() {
        AuthenticationFilter authenticationFilter = new AuthenticationFilter(new ApiAuthenticationManagerResolver(), new BasicAuthenticationConverter());
        authenticationFilter.setSuccessHandler((request, response, authentication) -> {});
        return authenticationFilter;
    }

    public static class ApiAuthenticationManagerResolver implements AuthenticationManagerResolver<HttpServletRequest> {

        private final Map<RequestMatcher, AuthenticationManager> managers = Map.of(
                new AntPathRequestMatcher("/dog/**"), new DogAuthenticationProvider()::authenticate,
                new AntPathRequestMatcher("/cat/**"), new CatAuthenticationProvider()::authenticate
        );

        @Override
        public AuthenticationManager resolve(HttpServletRequest request) {
            for (Map.Entry<RequestMatcher, AuthenticationManager> entry : managers.entrySet()) {
                if (entry.getKey().matches(request)) {
                    return entry.getValue();
                }
            }
            throw new IllegalArgumentException("Unable to resolve AuthenticationManager");
        }
    }

    public static class DogAuthenticationProvider implements AuthenticationProvider {

        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            if (authentication.getName().endsWith("_dog")) {
                return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(),
                        AuthorityUtils.createAuthorityList("ROLE_DOG"));
            }
            throw new BadCredentialsException("Username should end with _dog");
        }

        @Override
        public boolean supports(Class<?> authentication) {
            return true;
        }

    }

    public static class CatAuthenticationProvider implements AuthenticationProvider {

        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            if (authentication.getName().endsWith("_cat")) {
                return new UsernamePasswordAuthenticationToken(authentication.getName(), authentication.getCredentials(),
                        AuthorityUtils.createAuthorityList("ROLE_CAT"));
            }
            throw new BadCredentialsException("Username should end with _cat");
        }

        @Override
        public boolean supports(Class<?> authentication) {
            return true;
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,我们有两个AuthenticationProviders,一个代表 cat,另一个代表狗。它们是根据 . 内部和端点的AntPathRequestMatcher匹配来解析的。无需为每只狗和猫定义一个,因为它们具有相同的接口。/dog/**/cat/**ApiAuthenticationManagerResolverAuthenticationManagerAuthenticationProvider/Manager

ApiAuthenticationManagerResolver然后将其连接到AuthenticationFilter过滤器链中。

您还可以为每个端点定义两个不同的过滤器链,如下所示:

@Bean
public SecurityFilterChain dogApiSecurity(HttpSecurity http) throws Exception {
    http.requestMatchers((matchers) -> matchers
                .antMatchers("/dog/**"));
    http.authorizeRequests((authz) -> authz
            .anyRequest().authenticated());
    http.httpBasic();
    http.authenticationProvider(new DogAuthenticationProvider());
    return http.build();
}

@Bean
public SecurityFilterChain catApiSecurity(HttpSecurity http) throws Exception {
    http.requestMatchers((matchers) -> matchers
                .antMatchers("/cat/**"));
    http.authorizeRequests((authz) -> authz
            .anyRequest().authenticated());
    http.httpBasic();
    http.authenticationProvider(new CatAuthenticationProvider());
    return http.build();
}
Run Code Online (Sandbox Code Playgroud)

@Order请在定义多个过滤器链时,顺序很重要,请在这些场景中使用注释。

当您这样做时,http.requestMatcher(new AntPathRequestMatcher("/endpoint/**"));您是在告诉 Spring Security 仅在请求与该路径匹配时调用过滤器链。

Spring Security 的存储库中还有一个票证,提供了一个AuthenticationManagerResolver接受的实现Map<RequestMatcher, AuthenticationManager>,如果您认为它有道理,那就太好了,请在那里竖起大拇指。