【问题标题】:Different AuthenticationManager per path/route in spring security in MvCMvC中spring security中每个路径/路由的不同AuthenticationManager
【发布时间】:2021-10-03 01:29:18
【问题描述】:

序言

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

这是关于

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

所以问题不在于:

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

案例

假设一个有 2 个自定义 AuthenticationProviders:CATApiTokenProviderDOGApiTokenProvider。我们不讨论 AOuth/JWT/Basic/Form 提供程序是设计使然,因为它们提供了快捷方式。

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

问题

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

  • 我们希望身份验证提供程序 CATApiTokenProvider能够对 /cat/endpoint 上的请求进行身份验证
  • 我们希望身份验证提供程序DOGApiTokenProvider能够对/dog/endpoint 上的请求进行身份验证

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

我的想法/方法

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

  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);
        }
      }
    };
  }

两个问题是:

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

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

两个问题:

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

【问题讨论】:

    标签: rest spring-mvc spring-security


    【解决方案1】:

    您可以发布多个过滤器链或将您自己的AuthenticationFilterAuthenticationManagerResolver 连接

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

    我将详细介绍如何连接您自己的AuthenticationFilter

    @EnableWebSecurity
    public class SecurityConfig {
    
        @Bean
        public SecurityFilterChain apiSecurity(HttpSecurity http) throws Exception {
            http.authorizeRequests((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;
            }
    
        }
    }
    

    在上面的示例中,我们有两个AuthenticationProviders,一个用于猫,另一个用于狗。它们在AntPathRequestMatcher 匹配/dog/**/cat/** 端点时解析,在ApiAuthenticationManagerResolver 内。无需为每只狗和猫定义AuthenticationManager,因为AuthenticationProvider/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();
    }
    

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

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

    还有一个ticket within Spring Security's repository 提供了一个接受Map&lt;RequestMatcher, AuthenticationManager&gt;AuthenticationManagerResolver 实现,如果您认为它有道理,那就太好了,在那儿给个大拇指。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-25
      • 1970-01-01
      • 2017-04-30
      • 1970-01-01
      相关资源
      最近更新 更多