Spring Security 自定义身份验证过滤器和授权

Posted

技术标签:

【中文标题】Spring Security 自定义身份验证过滤器和授权【英文标题】:Spring Security Custom Authentication Filter and Authorization 【发布时间】:2018-12-11 04:14:14 【问题描述】:

我已经实现了一个自定义身份验证过滤器,效果很好。在设置会话并将身份验证对象添加到安全上下文后,我使用外部身份提供程序并重定向到我最初请求的 URL。

安全配置

@EnableWebSecurity(debug = true)
@Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter 

    // this is needed to pass the authentication manager into our custom security filter
    @Bean
    @Override
    AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean()
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
            .csrf().disable()
            .authorizeRequests()
                //.antMatchers("/admin/test").hasRole("METADATA_CURATORZ")
                .antMatchers("/**").permitAll()
            .anyRequest().authenticated()
            .and()
            .addFilterBefore(new CustomSecurityFilter(authenticationManagerBean()), UsernamePasswordAuthenticationFilter.class)
    

过滤逻辑

目前,我的自定义过滤器(一旦确认身份)只是对角色进行硬编码:

SimpleGrantedAuthority myrole = new SimpleGrantedAuthority("METADATA_CURATORZ")
                return new PreAuthenticatedAuthenticationToken(securityUser, null, [myrole])

然后在重定向到所需的端点之前将该身份验证对象(上面返回)添加到我的 SecurityContext:

SecurityContextHolder.getContext().setAuthentication(authentication)

控制器端点

  @RequestMapping(path = '/admin/test', method = GET, produces = 'text/plain')
  String test(HttpServletRequest request) 
    Authentication auth = SecurityContextHolder.getContext().getAuthentication()

    String roles = auth.getAuthorities()
    return "roles: $roles"
  

这个端点然后在浏览器中产生一个响应:

“角色:[METADATA_CURATORZ]”

太好了。因此,我的身份验证和向我的用户应用角色运行良好。

现在,如果我从安全配置中取消注释此行:

//.antMatchers("/admin/test").hasRole("METADATA_CURATORZ")

我无法再访问该资源并收到 403 错误——即使我们已经证明角色已设置。

这对我来说似乎完全荒谬和破碎,但我不是 Spring Security 专家。

我可能遗漏了一些非常简单的东西。有什么想法吗?

我的一些问题:

是否需要将我的自定义过滤器放在特定的内置过滤器之前,以确保在执行该过滤器之后执行授权步骤? 在请求周期中何时进行 antMatcher/hasRole 检查? 我是否需要更改我在安全配置链中调用的顺序,以及我应该如何理解我当前编写的配置?它显然没有做我认为应该做的事情。

【问题讨论】:

这个问题你解决了吗?您能否分享使用 Spring Security 的自定义角色的分步指南。 【参考方案1】:

我的自定义过滤器是否需要放置在特定的内置过滤器之前,以确保在执行该过滤器之后发生授权步骤?

您的过滤器必须位于FilterSecurityInterceptor 之前,因为这是进行授权和身份验证的地方。此过滤器是最后被调用的过滤器之一。

现在,过滤器的最佳位置可能在哪里,这真的取决于。例如,您确实希望您的过滤器出现在AnonymousAuthenticationFilter 之前,因为如果不是,则在调用您的过滤器时,未经身份验证的用户将始终使用AnonymousAuthenticationToken 进行“身份验证”。

您可以在FilterComparator 中查看过滤器的默认顺序。 AbstractPreAuthenticatedProcessingFilter 几乎与您正在做的事情相对应 - 它按照过滤器的顺序放置让您了解可以放置您的过滤器的位置。无论如何,您的过滤器的顺序应该没有问题。

在请求周期中何时进行 antMatcher/hasRole 检查?

所有这些都发生在FilterSecurityInterceptor,更准确地说,发生在其父级AbstractSecurityInterceptor

protected InterceptorStatusToken beforeInvocation(Object object) 

    Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
            .getAttributes(object);

    if (attributes == null || attributes.isEmpty()) 
        ...
    

    ...

    Authentication authenticated = authenticateIfRequired();

    // Attempt authorization
    try 
        this.accessDecisionManager.decide(authenticated, object, attributes);
    
    catch (AccessDeniedException accessDeniedException) 

        ...

        throw accessDeniedException;
    

额外信息: 本质上,FilterSecurityInterceptor 有一个 ExpressionBasedFilterInvocationSecurityMetadataSource,其中包含一个 Map&lt;RequestMatcher, Collection&lt;ConfigAttribute&gt;&gt;。在运行时,您的请求会根据Map 检查是否有任何RequestMatcher 键匹配。如果是,则将Collection&lt;ConfigAttribute&gt; 传递给AccessDecisionManager,后者最终会授予或拒绝访问权限。默认的AccessDecisionManagerAffirmativeBased 并包含处理ConfigAttribute 集合的对象(通常是WebExpressionVoter),并通过反射调用与您的"hasRole('METADATA_CURATORZ')" 对应的SpelExpression 对一个SecurityExpressionRoot 对象用你的Authentication初始化。

我是否需要更改我在安全配置链中调用的顺序,以及我应该如何理解我当前编写的配置?它显然没有做我认为应该做的事情。

不,您的过滤器应该没有任何问题。顺便说一句,除了 configure(HttpSecurity http) 方法中的内容之外,您扩展的 WebSecurityConfigurerAdapter 还具有一些默认值:

http
    .csrf().and()
    .addFilter(new WebAsyncManagerIntegrationFilter())
    .exceptionHandling().and()
    .headers().and()
    .sessionManagement().and()
    .securityContext().and()
    .requestCache().and()
    .anonymous().and()
    .servletApi().and()
    .apply(new DefaultLoginPageConfigurer<>()).and()
    .logout();

如果您想确切了解它们的作用以及它们添加了哪些过滤器,可以查看HttpSecurity

问题

当您执行以下操作时:

.authorizeRequests()
    .antMatchers("/admin/test").hasRole("METADATA_CURATORZ")

...搜索的角色是"ROLE_METADATA_CURATORZ"。为什么? ExpressionUrlAuthorizationConfigurer的静态hasRole(String role)方法最终处理"METADATA_CURATORZ"

if (role.startsWith("ROLE_")) 
    throw new IllegalArgumentException(
                "role should not start with 'ROLE_' since it is automatically inserted. Got '"
                        + role + "'");
    
    return "hasRole('ROLE_" + role + "')";

所以你的授权表达式变成了"hasRole('ROLE_METADATA_CURATORZ'",这最终会在SecurityExpressionRoot 上调用hasRole('ROLE_METADATA_CURATORZ') 方法,该方法又会在Authentication 的权限中搜索角色ROLE_METADATA_CURATORZ

解决方案

改变

SimpleGrantedAuthority myrole = new SimpleGrantedAuthority("METADATA_CURATORZ");

到:

SimpleGrantedAuthority myrole = new SimpleGrantedAuthority("ROLE_METADATA_CURATORZ");

【讨论】:

以上是关于Spring Security 自定义身份验证过滤器和授权的主要内容,如果未能解决你的问题,请参考以下文章

Spring Security 和自定义外部身份验证

Spring自定义身份验证过滤器和提供者不调用控制器方法

必须指定 Spring Security authenticationmanager - 用于自定义过滤器

Spring Security 3.0.5 自定义过滤器问题

Grails 3.2.4:未调用自定义身份验证过滤器

JAX-RS 和 Spring Security - 获取经过身份验证的用户