Spring @Secured 和 @PreAuthorize 不能正常工作(总是状态 403)

Posted

技术标签:

【中文标题】Spring @Secured 和 @PreAuthorize 不能正常工作(总是状态 403)【英文标题】:Spring @Secured and @PreAuthorize doesn`t work properly(always status 403) 【发布时间】:2020-08-27 08:45:11 【问题描述】:

我的 Spring REST 应用程序具有 JWT 授权。在我使用 @Secured 或 @PreAuthorize("hasRole('ROLE_SOME')") 注释控制器中的某些方法之前,一切正常。在这种情况下,每次向这些方法发送请求时,我都会得到状态 403。我的代码有什么问题?

网络安全

@Configuration
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter 

    private UserDetailsServiceImpl userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public WebSecurity(UserDetailsServiceImpl userDetailsService,
                       BCryptPasswordEncoder bCryptPasswordEncoder
    ) 
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.cors().and().csrf().disable().authorizeRequests()
                .antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
                .antMatchers(HttpMethod.GET,"/api/person/activate/*").permitAll()
                .anyRequest().authenticated()
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
    

    @Bean
    CorsConfigurationSource corsConfigurationSource() 
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    



我的用户主体

public class MyUserPrincipal implements UserDetails 

    private Person user;
    MyUserPrincipal(Person user) 
        this.user = user;
    

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() 
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(user.getRole().getName()));

        return authorities;
    

    @Override
    public String getPassword() 
        return user.getPassword();
    

    @Override
    public String getUsername() 
        return user.getEmail();
    

    @Override
    public boolean isAccountNonExpired() 
        return true;
    

    @Override
    public boolean isAccountNonLocked() 
        return true;
    

    @Override
    public boolean isCredentialsNonExpired() 
        return true;
    

    @Override
    public boolean isEnabled() 
        return user.isEnabled();
    

UserDetailsS​​erviceImpl

@Service
public class UserDetailsServiceImpl implements UserDetailsService 

    private PersonRepo personRepo;
    private static final Logger logger = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
    public UserDetailsServiceImpl(PersonRepo personRepo) 
        this.personRepo = personRepo;
    

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException 
        Person person = personRepo.findByEmail(email);
        if (person != null) 
            logger.debug("role " + person.getRole().getName());
            return new MyUserPrincipal(person);
        
        else 
            logger.error("user not found or inactive : " + person.getEmail());
            throw new UsernameNotFoundException(email);
        
    


方法安全配置

@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration 


JWTAuthenticationFilter

public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter 
    private AuthenticationManager authenticationManager;
    private static final Logger logger = LoggerFactory.getLogger(JWTAuthenticationFilter.class);

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) 
        this.authenticationManager = authenticationManager;
    



    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
                                                HttpServletResponse res) throws AuthenticationException 
        try 

            Person creds = new ObjectMapper()
                    .readValue(req.getInputStream(), Person.class);
            logger.debug("login attempt " + creds.getEmail());
                return authenticationManager.authenticate(
                        new UsernamePasswordAuthenticationToken(
                                creds.getEmail(),
                                creds.getPassword(),
                                new ArrayList<>())
                );
         catch (IOException e) 
            logger.error("unsuccessful attempt " + e);
            throw new RuntimeException(e);
        
    

    @Override
    protected void successfulAuthentication(HttpServletRequest req,
                                            HttpServletResponse res,
                                            FilterChain chain,
                                            Authentication auth) 

        String token = JWT.create()
                .withSubject(((UserDetails) auth.getPrincipal()).getUsername())
                .withExpiresAt(new Date(System.currentTimeMillis() + EXPIRATION_TIME))
                .sign(HMAC512(SECRET.getBytes()));
        logger.debug("successful log in, token : " + token);
        res.addHeader(HEADER_STRING, TOKEN_PREFIX + token);
    

JWTAuthorizationFilter

public class JWTAuthorizationFilter extends BasicAuthenticationFilter 

    JWTAuthorizationFilter(AuthenticationManager authManager) 
        super(authManager);
    

    @Override
    protected void doFilterInternal(HttpServletRequest req,
                                    HttpServletResponse res,
                                    FilterChain chain) throws IOException, ServletException 
        String header = req.getHeader(HEADER_STRING);

        if (header == null || !header.startsWith(TOKEN_PREFIX)) 
            chain.doFilter(req, res);
            return;
        

        UsernamePasswordAuthenticationToken authentication = getAuthentication(req);

        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(req, res);
    

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) 
        String token = request.getHeader(HEADER_STRING);
        if (token != null) 

            String user = JWT.require(Algorithm.HMAC512(SECRET.getBytes()))
                    .build()
                    .verify(token.replace(TOKEN_PREFIX, ""))
                    .getSubject();

            if (user != null) 
                return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
            
            return null;
        
        return null;
    

【问题讨论】:

【参考方案1】:

在 MyUserPrincipal 类中,我找到了以下行。

authorities.add(new SimpleGrantedAuthority(user.getRole().getName()));

能否请您检查一下角色是如何保存在数据库中的。由于您有 @PreAuthorize("hasRole('ROLE_SOME')") 有时如果您的角色未在数据库中保存为 ROLE_SOME ,您可能会收到 403 错误。

如果您在数据库中的角色在数据库中保存为“SOME”,请尝试更改以下代码。

  authorities.add(new SimpleGrantedAuthority("ROLE_" + user.getRole().getName()));

【讨论】:

以上是关于Spring @Secured 和 @PreAuthorize 不能正常工作(总是状态 403)的主要内容,如果未能解决你的问题,请参考以下文章

spring security 3 中的@Secured 和@PreAuthorize 有啥区别?

使用@Secured 时,spring 会话范围表单为空

Spring Security 3.2:不考虑@Secured注解

Spring Security,注释@Secured 不起作用

如何使用 Spring Security @Secured 注解测试 Spring Boot @WebFluxTest

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