对于使用 @RolesAllowed、@Secured 或 @PreAuthorize 的任何授权请求,Spring 启动授权返回 403

Posted

技术标签:

【中文标题】对于使用 @RolesAllowed、@Secured 或 @PreAuthorize 的任何授权请求,Spring 启动授权返回 403【英文标题】:Spring boot authorization returns 403 for any authorization request using @RolesAllowed, @Secured or @PreAuthorize 【发布时间】:2020-09-24 03:28:44 【问题描述】:

我一直在研究这篇文章(以及其他一些类似的文章):https://medium.com/omarelgabrys-blog/microservices-with-spring-boot-authentication-with-jwt-part-3-fafc9d7187e8

客户端是一个 Angular 8 应用程序,它从一个独立的微服务获取 Jwt。尝试将过滤器添加到不同的微服务以要求通过 jwt 角色进行特定授权。

不断收到 403 错误。

安全配置:

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true,
        securedEnabled = true,
        jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 

    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public WebSecurityConfig() 

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
                .cors().and().csrf().disable()
                // make sure we use stateless session; session won't be used to store user's state.
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // Add a filter to validate the tokens with every request
                .addFilterAfter(new JwtAuthorizationFilter2(), UsernamePasswordAuthenticationFilter.class)
                // authorization requests config
                .authorizeRequests()
                // Any other request must be authenticated
                .anyRequest().authenticated();
    

过滤器:

public class JwtAuthorizationFilter2 extends OncePerRequestFilter 

    private final String HEADER = "Authorization";
    private final String PREFIX = "Bearer ";
    private final String SECRET = "foo";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException 
        String token = request.getHeader(SecurityConstants.HEADER_STRING);
        if (token != null) 
            // parse the token.
            DecodedJWT decoded = JWT.require(Algorithm.HMAC512(SecurityConstants.SECRET.getBytes()))
                    .build()
                    .verify(token.replace(SecurityConstants.TOKEN_PREFIX, ""));

            String user = decoded.getSubject();
            List<SimpleGrantedAuthority> sgas = Arrays.stream(
                    decoded.getClaim("roles").asArray(String.class))
                    .map( s -> new SimpleGrantedAuthority(s))
                    .collect( Collectors.toList());
            if (sgas != null) 
                sgas.add(new SimpleGrantedAuthority("FOO_Admin"));
                UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                        user,
                        null,
                        sgas);
                SecurityContextHolder.getContext().setAuthentication(auth);
            
            else 
                SecurityContextHolder.clearContext();
            
            chain.doFilter(request, response);
        
    

此代码在没有定义任何授权要求的情况下可以正常工作,但是如果在 WebSecurityConfig 中定义了授权,或者通过装饰控制器方法,则范围内的所有请求都会收到 http 403。

例子:

.authorizeRequests().antMatchers("/**").hasRole("FOO_Admin")

// or any of these 
@PreAuthorize("hasRole('FOO_Admin')")
@RolesAllowed("FOO_Admin")
@Secured("FOO_Admin")
Device get(@PathVariable String id) 
    // some code

当代码在SecurityContextHolder.getContext().setAuthentication(auth) 停止时, auth.authenticated = trueauth.authorities 包含“FOO_Admin”的 SimpleGrantedAuthority

所以我想知道是否: FilterChain 需要一个身份验证过滤器(还是在 JwtAuthorizationFilter2 中进行身份验证?)? 角色名称没有拼写、格式或大小写差异。

我惊呆了。任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

@PreAuthorize("hasRole('FOO_Admin')) 期望用户拥有ROLE_FOO_Admin 的权限,其前缀为ROLE_。但是用户只有FOO_Admin的权限,所以无法访问该方法。

你有几个选择:

(1) 通过声明GrantedAuthorityDefaults bean 来更改前缀:

@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() 
    return new GrantedAuthorityDefaults("FOO");

并使用@PreAuthorize(hasRole('Admin')) 来保护该方法。

(2) 或者更简单的是使用@PreAuthorize("hasAuthority('FOO_Admin')"),它会直接检查用户是否拥有FOO_Admin的权限,无需添加任何前缀。

P.S JwtAuthorizationFilter2 仅验证用户是否有效,并获取相关用户信息,为以后授权用户做准备。这是一种身份验证,我会将其重命名为 JwtAuthenticationFilter2 以更准确地描述它的实际作用。

【讨论】:

这是我的问题。谢谢!现在回去看看我是如何错过这个大会的......

以上是关于对于使用 @RolesAllowed、@Secured 或 @PreAuthorize 的任何授权请求,Spring 启动授权返回 403的主要内容,如果未能解决你的问题,请参考以下文章

@RolesAllowed() 在 keycloak 中检查啥角色

使用 Enum 类型作为 @RolesAllowed-Annotation 的值参数

Spring Security应用开发(20)基于方法的授权使用@RolesAllowed注解

在 SpringBoot 应用程序中使用 @RolesAllowed 的异常

如何在 ContainerRequest 中获取会话对象以使用注解 @RolesAllowed(Role_user)?

使用 ContainerRequestFilter 在 Jersey WebService 中自定义 @RolesAllowed 角色