弹簧安全过滤器没有启动
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;
【讨论】:
以上是关于弹簧安全过滤器没有启动的主要内容,如果未能解决你的问题,请参考以下文章