Spring 安全过滤器未启动 [英] Spring security filter not firing up
问题描述
我的身份验证过滤器未按要求启动.
My authentication filters are not firing up under request.
我有 2 个安全配置,一个仅用于登录端点,使用 AuthenticationFromCredentialsFilter
过滤器进行身份验证,另一个用于其他端点,使用 AuthenticationFromTokenFilter
过滤器进行身份验证.
I have 2 security configurations, one for the login endpoint only, authenticating with the AuthenticationFromCredentialsFilter
filter, and another one for the other endpoints, authenticating with the AuthenticationFromTokenFilter
filter.
我希望调用过滤器的 attemptAuthentication
方法,但它们没有.
I expect the attemptAuthentication
method of the filters to be called but they are not.
更喜欢在过滤器中而不是在登录控制器中验证凭据并创建令牌有什么意义吗?
Is there any point in prefering to authenticate the credentials and create the token, in a filter rather than in the login controller ?
登录控制器目前存在,但它不应该存在,因为它的工作应该由过滤器完成.
The login controller is present for now, but it should not exist as its job should be done by the filter.
我在安全配置中设置了它们:
I set them up each in a security configuration:
@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
状态:
Here is an example of a login request that should be captured by the AuthenticationFromCredentialsFilter
filter in the security configuration, but is not, and thus is allowed to proceed to the controller and give a response with a 201
status:
$ 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
过滤器是否正确?使用过滤器进行身份验证并使用令牌进行响应?并且根本没有调用登录控制器?
Am I right to expect the login request to be firing up the AuthenticationFromCredentialsFilter
filter ? With the filter doing the authentication and responding with a token ? And the login controller not being called at all ?
这里是密码更改请求的另一个示例,它应该由安全配置中的 AuthenticationFromTokenFilter
过滤器捕获,但没有捕获,因此允许继续到控制器并给出响应200
状态:
Here is another example of a password change request that should be captured by the AuthenticationFromTokenFilter
filter in the security configuration, but is not, and thus is allowed to proceed to the controller and give a response with a 200
status:
$ 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 实现 AuthenticationProvider
而不是 AuthenticationFromCredentialsFilter
过滤器?在 Spring Boot 2.0.3
下还有可能吗?
And what about using a CustomAuthenticationProvider implements AuthenticationProvider
instead of a AuthenticationFromCredentialsFilter
filter for the login request ? Is that still possible under Spring Boot 2.0.3
?
我在想:
@Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
使用身份验证提供程序:
With the authentication provider being:
@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;
}
}
更新:我也试过这个配置,但它没有改变任何问题:
UPDATE: I also tried this configuration but it didn't change anything to the issue:
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();
}
推荐答案
我终于可以在 Spring Boot 2
环境下启动过滤器,每个过滤器都有自己的请求.
I could finally have the filters fire up and each for their own requests under a Spring Boot 2
environment.
凭据过滤器会根据对 /users/login
端点的登录请求启动.
The credentials filter fires up upon a login request to the /users/login
endpoint.
令牌过滤器会根据除登录请求之外的任何请求启动.
The token filter fires up upon any request except the login request.
要将 url 模式分配给过滤器,过滤器必须扩展 AbstractAuthenticationProcessingFilter
类.这两个过滤器扩展了该类.该模式在其构造函数中指定.
To assign a url pattern to a filter, the filter had to extend the AbstractAuthenticationProcessingFilter
class. And the two filters extend that class. The pattern is specified in their constructor.
凭据过滤器是:
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);
的调用,因为不需要命中控制器端点,因为响应已经和令牌一起发回了.
Note that on successful authentication, the filter chain is not pursued, that is there is no such call to filterChain.doFilter(httpRequest, httpResponse);
because there is no need to hit a controller endpoint, since the response is already sent back with the token.
使用@Bean
注解显式实例化以指定匹配器模式:
It is explicitly instantiated with a @Bean
annotation so as to specify the matcher pattern:
@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
注解显式实例化以指定匹配器模式:
It is explicitly instantiated with a @Bean
annotation so as to specify the matcher pattern:
@Bean
public AuthenticationFromTokenFilter authenticationFromTokenFilter() throws Exception {
AuthenticationFromTokenFilter authenticationFromTokenFilter = new AuthenticationFromTokenFilter(new NegatedRequestMatcher(new AntPathRequestMatcher("/users/login", RequestMethod.POST.name())));
authenticationFromTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationFromTokenFilter;
}
与之前的凭据过滤器相反,此过滤器需要在配置中明确指定:
Contrary to the previous credentials filter, this filter needs to be explicitly specified in the configuration:
http
.addFilterBefore(authenticationFromTokenFilter, UsernamePasswordAuthenticationFilter.class)
为了避免这个过滤器在过滤器链中被启动两次,一个 bean 指示 Spring Boot 不要自动注入它:
To avoid this filter being fired up twice in the filters chain, a bean instructs Spring Boot not to automatically inject it:
@Bean
FilterRegistrationBean<AuthenticationFromTokenFilter> disableAutoRegistration(final AuthenticationFromTokenFilter filter) {
final FilterRegistrationBean<AuthenticationFromTokenFilter> registration = new FilterRegistrationBean<AuthenticationFromTokenFilter>(filter);
registration.setEnabled(false);
return registration;
}
请注意,在成功认证时,会跟踪过滤器链,即调用 filterChain.doFilter(httpRequest, httpResponse);
因为需要命中控制器端点,之后身份验证成功.
Note that on successful authentication, the filter chain is pursued, that is there is a call to filterChain.doFilter(httpRequest, httpResponse);
because there is a need to hit a controller endpoint, after successful authentication.
完整配置为:
@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 配置中根本没有使用:
Also, note that the is not being used at all in this Spring Boot configuration:
@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));
}
}
我想知道它适用于何处以及它如何替代凭据过滤器...但那是另一回事了.
I wonder where it could fit and how it could replace the credentials filter... but that is another story.
这篇关于Spring 安全过滤器未启动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!