弹簧安全过滤器没有启动

Posted

技术标签:

【中文标题】弹簧安全过滤器没有启动【英文标题】:Spring security filter not firing up 【发布时间】:2019-01-12 04:35:05 【问题描述】:

我的身份验证过滤器未按请求启动。

我有 2 种安全配置,一种仅用于登录端点,使用 AuthenticationFromCredentialsFilter 过滤器进行身份验证,另一种用于其他端点,使用 AuthenticationFromTokenFilter 过滤器进行身份验证。

我希望调用过滤器的 attemptAuthentication 方法,但实际上没有。

在过滤器中而不是在登录控制器中更喜欢验证凭据并创建令牌有什么意义?

登录控制器现在存在,但它不应该存在,因为它的工作应该由过滤器完成。

我在安全配置中分别设置了它们:

@EnvProd
@EnableWebSecurity
@ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages =  "com.thalasoft.user.rest.security", "com.thalasoft.user.rest.filter" )
public class WebSecurityConfiguration 

    @Order(1)
    @Configuration
    public class CredentialsConfiguration extends WebSecurityConfigurerAdapter 

        public AuthenticationManager authenticationManagerBean() throws Exception 
            return super.authenticationManagerBean();
        

        public AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter() throws Exception 
            AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter = new AuthenticationFromCredentialsFilter();
            authenticationFromCredentialsFilter.setAuthenticationManager(authenticationManagerBean());
            return authenticationFromCredentialsFilter;
        

        @Override
        protected void configure(HttpSecurity http) throws Exception 
            http.antMatcher("/api/users/login")
            .addFilterBefore(authenticationFromCredentialsFilter(), UsernamePasswordAuthenticationFilter.class)
            .authorizeRequests()
            .antMatchers("/api/users/login").permitAll()
            .anyRequest().authenticated();
        
    

    @Order(2)
    @Configuration
    public class TokenConfiguration extends WebSecurityConfigurerAdapter 

        @Autowired
        private AuthenticationFromTokenFilter authenticationFromTokenFilter;

        @Autowired
        private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;

        @Autowired
        private SimpleCORSFilter simpleCORSFilter;

        @Override
        protected void configure(HttpSecurity http) throws Exception 
            http
                .csrf().disable();

            http
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

            http
                .headers()
                .cacheControl().disable()
                .frameOptions().disable();

            http
                .httpBasic()
                .authenticationEntryPoint(restAuthenticationEntryPoint);

            http 
                .addFilterBefore(simpleCORSFilter, UsernamePasswordAuthenticationFilter.class);

            http
                .addFilterBefore(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class);

            http.antMatcher("/api/**")
                .addFilterBefore(authenticationFromTokenFilter(), UsernamePasswordAuthenticationFilter.class)
                .authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/error").permitAll()
                .antMatchers("/admin/**").hasRole(UserDomainConstants.ROLE_ADMIN)
                .anyRequest().authenticated();    
        
    

这是两个过滤器:

public class AuthenticationFromCredentialsFilter extends UsernamePasswordAuthenticationFilter 

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private TokenAuthenticationService tokenAuthenticationService;

    @Autowired
    CredentialsService credentialsService;

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException 
        try 
            CredentialsResource credentialsResource = new ObjectMapper().readValue(req.getInputStream(),
                    CredentialsResource.class);
            return authenticationManager.authenticate(credentialsService.authenticate(credentialsResource));
         catch (IOException e) 
            throw new RuntimeException(e);
        
    

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
            Authentication authentication) throws IOException, ServletException 
        tokenAuthenticationService.addTokenToResponseHeader(response, authentication);
    



public class AuthenticationFromTokenFilter extends UsernamePasswordAuthenticationFilter 

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private TokenAuthenticationService tokenAuthenticationService;

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException 
        tokenAuthenticationService.authenticate(request);
        return authenticationManager.authenticate(tokenAuthenticationService.authenticate(request));
    

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
            Authentication authentication) throws IOException, ServletException 
    


下面是一个登录请求示例,应该由安全配置中的AuthenticationFromCredentialsFilter 过滤器捕获,但未被捕获,因此允许继续到控制器并给出具有201 状态的响应:

$ curl -i -H "Accept:application/json" -H "Content-Type: application/json" "http://localhost:8080/api/users/login" -X POST -d " \"email\" : \"xxxxxx@yahoo.se\", \"password\" : \"xxxxx\" "
HTTP/1.1 201 
Cache-Control: no-store
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzQwNTE5MjYsInN1YiI6Im1pdHRpcHJvdmVuY2VAeWFob28uc2UifQ.LOJvr5jWouWsLN_Pinlr_F5dntON45hwpUFVmXD2Xqo
Location: http://localhost:8080/api/users/1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 05 Aug 2018 05:32:07 GMT


"firstname":"Stephane","lastname":"Eybert","email":"xxxxx@yahoo.se","confirmedEmail":false,"password":"bWl0dGlwcm92ZW5jZUB5YWhvby5zZTptaWduZXQxYjE4ZDQ5MS00ZGRhLTQxZWYtYWM5ZS04N2Y5ODk=","workPhone":null,"userRoles":["role":"ROLE_ADMIN","id":1],"_links":"self":"href":"http://localhost:8080/api/users/1","roles":"href":"http://localhost:8080/api/users/1/roles","id":1[stephane@stephane-ThinkPad-X201 user-rest (master)]

我期望登录请求触发AuthenticationFromCredentialsFilter 过滤器是否正确?使用过滤器进行身份验证并使用令牌响应?登录控制器根本没有被调用?

这是另一个密码更改请求示例,应由安全配置中的AuthenticationFromTokenFilter 过滤器捕获,但未被捕获,因此允许继续到控制器并给出具有200 状态的响应:

$ curl -i -H "Accept:application/json" -H "Content-Type: application/json" "http://localhost:8080/api/users/1/password" -X PUT -d "\"xxxxx\""
HTTP/1.1 200 
Cache-Control: no-store
Location: http://localhost:8080/api/users/1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 04 Aug 2018 20:23:17 GMT

"firstname":"Stephane","lastname":"Eybert","email":"xxxx@yahoo.se","confirmedEmail":false,"password":"bWl0dGlwcm92ZW5jZUB5YWhvby5zZTptaWduZXRhYTA4OTNiZS0yMzZlLTQ3ZjktOTE2Ny0zOTU0NTY=","workPhone":null,"userRoles":["role":"ROLE_ADMIN","id":1],"_links":"self":"href":"http://localhost:8080/api/users/1","roles":"href":"http://localhost:8080/api/users/1/roles","id":1

对于登录请求使用CustomAuthenticationProvider implements AuthenticationProvider 而不是AuthenticationFromCredentialsFilter 过滤器怎么样?在Spring Boot 2.0.3 下还有可能吗?

我在想这样的事情:

@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception 
    auth.authenticationProvider(customAuthenticationProvider);

身份验证提供者为:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider 

    @Autowired
    CredentialsService credentialsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException 
        return credentialsService.authenticate(authentication);
    

    @Override
    public boolean supports(Class<?> authentication) 
        boolean value = (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
        return value;
    


更新:我也试过这个配置,但它没有改变任何问题:

public AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter() throws Exception 
    AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter = new AuthenticationFromCredentialsFilter();
    authenticationFromCredentialsFilter.setAuthenticationManager(authenticationManagerBean());
    authenticationFromCredentialsFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/users/login"));
    return authenticationFromCredentialsFilter;


public AuthenticationFromTokenFilter authenticationFromTokenFilter() throws Exception 
    AuthenticationFromTokenFilter authenticationFromTokenFilter = new AuthenticationFromTokenFilter();
    authenticationFromTokenFilter.setAuthenticationManager(authenticationManagerBean());
    authenticationFromTokenFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/**"));
    return authenticationFromTokenFilter;


@Override
protected void configure(HttpSecurity http) throws Exception 
    http.csrf().disable();

    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    http.headers().cacheControl().disable().frameOptions().disable();

    http.httpBasic().authenticationEntryPoint(restAuthenticationEntryPoint);

    http.addFilterBefore(simpleCORSFilter, UsernamePasswordAuthenticationFilter.class);

    http.antMatcher("/api/**")
    .addFilterBefore(authenticationFromCredentialsFilter(), UsernamePasswordAuthenticationFilter.class)
    .addFilterBefore(authenticationFromTokenFilter(), UsernamePasswordAuthenticationFilter.class)
    .authorizeRequests()
    .antMatchers("/").permitAll()
    .antMatchers("/error").permitAll()
    .antMatchers("/api/users/login").permitAll()
    .antMatchers("/admin/**").hasRole(UserDomainConstants.ROLE_ADMIN)
    .anyRequest().authenticated();

【问题讨论】:

【参考方案1】:

我终于可以在Spring Boot 2 环境下启动过滤器,并且每个过滤器都有自己的请求。

凭据过滤器在对/users/login 端点的登录请求时启动。

令牌过滤器会根据登录请求以外的任何请求触发。

要将 url 模式分配给过滤器,过滤器必须扩展 AbstractAuthenticationProcessingFilter 类。这两个过滤器扩展了该类。模式在其构造函数中指定。

凭据过滤器是:

public class AuthenticationFromCredentialsFilter extends AbstractAuthenticationProcessingFilter 

    @Autowired
    private TokenAuthenticationService tokenAuthenticationService;

    @Autowired
    CredentialsService credentialsService;

    public AuthenticationFromCredentialsFilter(final RequestMatcher requestMatcher) 
        super(requestMatcher);
    

    @Override
    public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res)
            throws AuthenticationException 
        try 
            CredentialsResource credentialsResource = new ObjectMapper().readValue(req.getInputStream(),
                    CredentialsResource.class);
            return credentialsService.authenticate(credentialsResource);
         catch (IOException e) 
            throw new RuntimeException(e);
        
    

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
            Authentication authentication) throws IOException, ServletException 
        tokenAuthenticationService.addTokenToResponseHeader(response, authentication);
    


请注意,在成功的身份验证后,过滤器链不会被追踪,即没有对filterChain.doFilter(httpRequest, httpResponse); 的此类调用,因为不需要访问控制器端点,因为响应已经与令牌一起发回。

使用@Bean注解显式实例化,以指定匹配器模式:

@Bean
public AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter() throws Exception 
    AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter = new AuthenticationFromCredentialsFilter(new AntPathRequestMatcher("/users/login", RequestMethod.POST.name()));
    authenticationFromCredentialsFilter.setAuthenticationManager(authenticationManagerBean());
    return authenticationFromCredentialsFilter;

令牌过滤器是:

public class AuthenticationFromTokenFilter extends AbstractAuthenticationProcessingFilter 

    @Autowired
    private TokenAuthenticationService tokenAuthenticationService;

    public AuthenticationFromTokenFilter(final RequestMatcher requestMatcher) 
        super(requestMatcher);
    

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException 
        return tokenAuthenticationService.authenticate(request);
    

    @Override
    protected void successfulAuthentication(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
            FilterChain filterChain, Authentication authResult) throws IOException, ServletException 
        filterChain.doFilter(httpRequest, httpResponse);
    


使用@Bean注解显式实例化,以指定匹配模式:

@Bean
public AuthenticationFromTokenFilter authenticationFromTokenFilter() throws Exception 
    AuthenticationFromTokenFilter authenticationFromTokenFilter = new AuthenticationFromTokenFilter(new NegatedRequestMatcher(new AntPathRequestMatcher("/users/login", RequestMethod.POST.name())));
    authenticationFromTokenFilter.setAuthenticationManager(authenticationManagerBean());
    return authenticationFromTokenFilter;

与之前的凭据过滤器相反,此过滤器需要在配置中明确指定:

http
.addFilterBefore(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class)

为了避免这个过滤器在过滤器链中被触发两次,一个 bean 指示 Spring Boot 不要自动注入它:

@Bean
FilterRegistrationBean<AuthenticationFromTokenFilter> disableAutoRegistration(final AuthenticationFromTokenFilter filter) 
    final FilterRegistrationBean<AuthenticationFromTokenFilter> registration = new FilterRegistrationBean<AuthenticationFromTokenFilter>(filter);
    registration.setEnabled(false);
    return registration;

请注意,在成功验证后,将继续使用过滤器链,即调用filterChain.doFilter(httpRequest, httpResponse);,因为在成功验证后需要访问控制器端点。

完整配置为:

@ComponentScan(nameGenerator = PackageBeanNameGenerator.class, basePackages =  "com.thalasoft.user.rest.security",
"com.thalasoft.user.rest.service", "com.thalasoft.user.rest.filter" )
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter 

    @Autowired
    private RESTAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Autowired
    private SimpleCORSFilter simpleCORSFilter;

    @Autowired
    AuthenticationFromTokenFilter authenticationFromTokenFilter;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean();
    

    @Bean
    public AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter() throws Exception 
        AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter = new AuthenticationFromCredentialsFilter(new AntPathRequestMatcher("/users/login", RequestMethod.POST.name()));
        authenticationFromCredentialsFilter.setAuthenticationManager(authenticationManagerBean());
        return authenticationFromCredentialsFilter;
    

    @Bean
    public AuthenticationFromTokenFilter authenticationFromTokenFilter() throws Exception 
        AuthenticationFromTokenFilter authenticationFromTokenFilter = new AuthenticationFromTokenFilter(new NegatedRequestMatcher(new AntPathRequestMatcher("/users/login", RequestMethod.POST.name())));
        authenticationFromTokenFilter.setAuthenticationManager(authenticationManagerBean());
        return authenticationFromTokenFilter;
    

    @Bean
    FilterRegistrationBean<AuthenticationFromTokenFilter> disableAutoRegistration(final AuthenticationFromTokenFilter filter) 
        final FilterRegistrationBean<AuthenticationFromTokenFilter> registration = new FilterRegistrationBean<AuthenticationFromTokenFilter>(filter);
        registration.setEnabled(false);
        return registration;
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.cors();

        http
        .csrf().disable()
        .formLogin().disable()
        .httpBasic().disable()
        .logout().disable();

        http.exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint);

        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.addFilterBefore(simpleCORSFilter, ChannelProcessingFilter.class);

        http
        .addFilterBefore(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class)
        .authorizeRequests()
        .antMatchers("/", "/error").permitAll()
        .antMatchers("/users/login").permitAll()
        .antMatchers("/admin/**").hasRole(UserDomainConstants.ROLE_ADMIN)
        .anyRequest().authenticated();
    


另外,请注意,在这个 Spring Boot 配置中根本没有使用:

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider 

    @Autowired
    CredentialsService credentialsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException 
        return credentialsService.authenticate(authentication);
    

    @Override
    public boolean supports(Class<?> authentication) 
        return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    


我想知道它可以放在哪里以及如何替换凭据过滤器...但那是另一回事了。

【讨论】:

【参考方案2】:

您的过滤器扩展UsernamePasswordAuthenticationFilter,默认情况下,此过滤器仅适用于 URL /login,请参阅UsernamePasswordAuthenticationFilter

此过滤器默认响应 URL /login

如果您想将默认网址更改为其他网址,请参阅AbstractAuthenticationProcessingFilter#setFilterProcessesUrl

设置确定是否需要身份验证的 URL

您修改后的代码:

public AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter() throws Exception 
    AuthenticationFromCredentialsFilter authenticationFromCredentialsFilter = new AuthenticationFromCredentialsFilter();
    authenticationFromCredentialsFilter.setAuthenticationManager(authenticationManagerBean());
    authenticationFromCredentialsFilter.setFilterProcessesUrl("/api/users/login");
    return authenticationFromCredentialsFilter;

如果要使用模式,请参阅AbstractAuthenticationProcessingFilter:

如果请求与setRequiresAuthenticationRequestMatcher(RequestMatcher) 匹配,此过滤器将拦截请求并尝试对该请求执行身份验证。

您修改后的代码:

public AuthenticationFromTokenFilter authenticationFromTokenFilter() throws Exception 
    AuthenticationFromTokenFilter authenticationFromTokenFilter= new AuthenticationFromTokenFilter();
    authenticationFromTokenFilter.setAuthenticationManager(authenticationManagerBean());
    authenticationFromTokenFilter.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/api/**");
    return authenticationFromTokenFilter;

【讨论】:

以上是关于弹簧安全过滤器没有启动的主要内容,如果未能解决你的问题,请参考以下文章

弹簧安全 CORS 过滤器

弹簧安全CORS过滤器

弹簧安全CORS过滤器

弹簧安全CORS过滤器

弹簧安全CORS过滤器

仅在需要身份验证时应用弹簧安全过滤器(由我的应用程序)