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

for*_*rgo 6 authentication spring authorization filter spring-security

我已经实现了一个自定义身份验证过滤器,并且效果很好。我使用外部身份提供程序并在设置会话并将我的身份验证对象添加到我的安全上下文后重定向到我最初请求的 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

过滤逻辑

现在,我的自定义过滤器(一旦确认身份)只是硬编码一个角色:

SimpleGrantedAuthority myrole = new SimpleGrantedAuthority("METADATA_CURATORZ")
                return new PreAuthenticatedAuthenticationToken(securityUser, null, [myrole])
Run Code Online (Sandbox Code Playgroud)

然后在重定向到所需端点之前,该身份验证对象(上面返回)被添加到我的 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}"
  }
Run Code Online (Sandbox Code Playgroud)

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

“角色:[METADATA_CURATORZ]”

伟大的。所以我的身份验证和向我的用户应用角色工作得很好。

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

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

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

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

我可能错过了一些非常简单的东西。有任何想法吗?

我有一些问题:

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

Nat*_*Far 6

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

您的过滤器必须在 之前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;
    }
Run Code Online (Sandbox Code Playgroud)

额外信息: 本质上,FilterSecurityInterceptoraExpressionBasedFilterInvocationSecurityMetadataSource包含 a Map<RequestMatcher, Collection<ConfigAttribute>>。在运行时,您的请求会根据 进行检查,Map以查看是否有任何RequestMatcher键匹配。如果是,则将 aCollection<ConfigAttribute>传递给AccessDecisionManager,最终将授予或拒绝访问权限。默认AccessDecisionManagerAffirmativeBased和包含对象(通常是一个WebExpressionVoter),该处理的集合ConfigAttribute,并通过反射调用SpelExpression对应于您的"hasRole('METADATA_CURATORZ')"针对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();
Run Code Online (Sandbox Code Playgroud)

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

问题

当您执行以下操作时:

.authorizeRequests()
    .antMatchers("/admin/test").hasRole("METADATA_CURATORZ")
Run Code Online (Sandbox Code Playgroud)

... 搜索的角色是"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 + "')";
}
Run Code Online (Sandbox Code Playgroud)

所以,你的授权表达成为"hasRole('ROLE_METADATA_CURATORZ'"这最终调用的方法hasRole('ROLE_METADATA_CURATORZ')SecurityExpressionRoot,它在为角色转弯搜索ROLE_METADATA_CURATORZAuthentication的部门。

解决方案

改变

SimpleGrantedAuthority myrole = new SimpleGrantedAuthority("METADATA_CURATORZ");
Run Code Online (Sandbox Code Playgroud)

到:

SimpleGrantedAuthority myrole = new SimpleGrantedAuthority("ROLE_METADATA_CURATORZ");
Run Code Online (Sandbox Code Playgroud)