使用 JWT / antMatchers 阻止访问的 Spring 安全配置

Posted

技术标签:

【中文标题】使用 JWT / antMatchers 阻止访问的 Spring 安全配置【英文标题】:Spring security configuration with JWT / antMatchers blocking access 【发布时间】:2015-12-22 02:26:24 【问题描述】:

我们正在 1.3 Spring Boot 应用程序中设置 Spring Security。我们创建了一个类来使用 Java config 配置所有内容,但由于某种原因,每次我尝试访问任何配置为“permitAll()”的 URL 时,我都会收到与此类似的消息响应:


  "timestamp": 1443099232454,
  "status": 403,
  "error": "Forbidden",
  "message": "Access Denied",
  "path": "/api/register"

如果我将 antMatchers 设置为允许访问注册、身份验证和激活 url,我不确定为什么会得到这个。如果我禁用这三行,我就可以访问这三个端点。

这是我当前的配置:

SecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Inject
    private Http401UnauthorizedEntryPoint authenticationEntryPoint;

    @Inject
    private UserDetailsService userDetailsService;

    @Inject
    private TokenProvider tokenProvider;

    public SecurityConfig() 
        super(true);
    

    @Bean
    public PasswordEncoder passwordEncoder() 
        return new BCryptPasswordEncoder();
    

    @Inject
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        // @formatter:off
        http
            .exceptionHandling()
            .authenticationEntryPoint(authenticationEntryPoint)
        .and()
            .csrf()
            .disable()
            .headers()
            .frameOptions()
            .disable()
        .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
            .authorizeRequests()
            .antMatchers("/api/register").permitAll()
            .antMatchers("/api/activate").permitAll()
            .antMatchers("/api/authenticate").permitAll()
        .and()
            .authorizeRequests()
            .anyRequest()
            .authenticated()
        .and()
            .apply(securityConfigurerAdapter());
        // @formatter:on
    

    private JwtTokenConfigurer securityConfigurerAdapter() 
        return new JwtTokenConfigurer(tokenProvider);
    

UserDetailsS​​ervice.java

@Service("userDetailsService")
@Log4j2
public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService 

    @Inject
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(final String email) 
        log.debug("Authenticating ", email);
        String lowercaseEmail = email.toLowerCase();
        Optional<User> userFromDatabase = userRepository.findOneByEmail(lowercaseEmail);
        return userFromDatabase.map(
                user -> 
                    if (!user.isEnabled()) 
                        throw new DisabledException("User " + lowercaseEmail + " is disabled");
                    

                    List<GrantedAuthority> grantedAuthorities = user.getRoles().stream()
                            .map(role -> role.getGrantedAuthority()).collect(Collectors.toList());

                    return new org.springframework.security.core.userdetails.User(lowercaseEmail, user.getPassword(),
                        grantedAuthorities);
                ).orElseThrow(
                () -> new UsernameNotFoundException("User " + lowercaseEmail + " was not found in the database"));
    

JwtTokenConfigurer.java

public class JwtTokenConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> 

    private TokenProvider tokenProvider;

    public JwtTokenConfigurer(TokenProvider tokenProvider) 
        this.tokenProvider = tokenProvider;
    

    @Override
    public void configure(HttpSecurity http) throws Exception 
        JwtTokenFilter customFilter = new JwtTokenFilter(tokenProvider);
        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
    

JwtTokenFilter.java

public class JwtTokenFilter extends GenericFilterBean 
    private final static String JWT_TOKEN_HEADER_NAME = "Authorization";
    private TokenProvider tokenProvider;

    public JwtTokenFilter(TokenProvider tokenProvider) 
        this.tokenProvider = tokenProvider;
    

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException 
        try 
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            String jwtToken = httpServletRequest.getHeader(JWT_TOKEN_HEADER_NAME);

            if (StringUtils.hasText(jwtToken)) 
                String authorizationSchema = "Bearer";
                if (jwtToken.indexOf(authorizationSchema) == -1) 
                    throw new InsufficientAuthenticationException("Authorization schema not found");
                
                jwtToken = jwtToken.substring(authorizationSchema.length()).trim();

                JwtClaims claims = tokenProvider.parseToken(jwtToken);
                String email = (String) claims.getClaimValue(TokenConstants.EMAIL.name());
                List<GrantedAuthority> grantedAuthorities = claims.getStringListClaimValue(TokenConstants.ROLES.name())
                    .stream().map(role -> new SimpleGrantedAuthority(role)).collect(Collectors.toList());

                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    email, null, grantedAuthorities);
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            
            filterChain.doFilter(servletRequest, servletResponse);
         catch (Exception ex) 
            throw new RuntimeException(ex);
        
    

Http401UnauthorizedEntryPoint.java

@Component
public class Http401UnauthorizedEntryPoint implements AuthenticationEntryPoint 

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2)
        throws IOException, ServletException 
        log.debug("Pre-authenticated entry point called. Rejecting access");
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied");
    

正如我之前提到的,每次我尝试访问这三个端点中的任何一个时:

.antMatchers("/api/register").permitAll()
.antMatchers("/api/activate").permitAll()
.antMatchers("/api/authenticate").permitAll()

我的访问被拒绝...有什么想法吗?

【问题讨论】:

你能发布你的 /api/register 控制器吗?检查 FilterSecurityInterceptor 中发生了什么。似乎您正在请求对 /api/register 进行某种授权。也许在 Controller 类中使用 PreAuthorize 或 Secured 注释,这对所有方法都有效。 【参考方案1】:

您需要允许匿名用户。

@Override
protected void configure(HttpSecurity http) throws Exception 
    // @formatter:off
    http
        .exceptionHandling()
        .authenticationEntryPoint(authenticationEntryPoint)
    .and()
        .csrf()
        .disable()
        .headers()
        .frameOptions()
        .disable()
    .and()
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
    .and()
        .authorizeRequests()
        .antMatchers("/api/register").permitAll()
        .antMatchers("/api/activate").permitAll()
        .antMatchers("/api/authenticate").permitAll()
    .and()
        .anonymous()
        .authorizeRequests()
        .anyRequest()
        .authenticated()
    .and()
        .apply(securityConfigurerAdapter());
    // @formatter:on

因为 AbstractSecurityInterceptor 总是询问 SecurityContextHolder 中是否有东西。 AbstractSecurityInterceptor#beforeInvocation第221行

if (SecurityContextHolder.getContext().getAuthentication() == null) 
 credentialsNotFound(messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound","An Authentication object was not found in the SecurityContext"),object, attributes);


【讨论】:

以上是关于使用 JWT / antMatchers 阻止访问的 Spring 安全配置的主要内容,如果未能解决你的问题,请参考以下文章

尝试使用 express-jwt 阻止访问除登录页面以外的 Angular 应用程序时出现问题

如何阻止用户使用 JWT 创建自定义 POST 请求?

弹簧安全 AntMatcher 不工作

安全配置不允许我在某些页面上使用 antMatchers()

Spring security antMatchers permitAll 不起作用

使用 JWT 令牌使用 AdonisJS 注销