上一篇实现了自定义认证,但是要想彻底自定义整个认证流程,还差一个环节,即认证过滤器,它是认证的入口。本篇仍然以验证码为例,介绍自定义认证过滤器。
自定义Authentication
第4篇讲过,自定义过滤器的主要作用就是生成未认证Authentication,作为入参给AuthenticationManager认证。而在第5篇,根据AuthenticationProvider的supports方法检查入参Authentication的不同实现类型,从而使AuthenticationManager可以支持不同的认证方式。这里就来自定义一个验证码类型的Authentication。
public class MyAuthentication extends AbstractAuthenticationToken {
private String code; //验证码
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
//未认证Authentication构造方法
public MyAuthentication(String code) {
super(null);
this.code = code;
setAuthenticated(false);
}
//已认证Authentication构造方法
public MyAuthentication(String code,Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.code = code;
setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null; //纯验证码登录,不需要密码
}
@Override
public Object getPrincipal() {
return code; //自己随便定义的Principal
}
认证过滤器
主要作用就是根据用户登录时输入的数据,生成指定类型的Authentication, 交给AuthenticationManager认证。关于具体如何认证上一篇已经讲过,后面也会给出代码。
public class MyFilter extends AbstractAuthenticationProcessingFilter {
protected MyFilter() {
super("/login");//拦截到登录请求时,开始执行以下方法
}
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
if (!request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("只支持POST请求");
}
//有了request,可以拿到任意参数,code是验证码
String code = request.getParameter("code");
//根据不同登录方式,生成不同类型Authentication,如这里的MyAuthentication
MyAuthentication authRequest = new MyAuthentication(code);
//其他参数,可以是一个字符串,也可以任意对象。不需要像上篇配置的那么麻烦了
authRequest.setDetails("其他参数");
//将未认证Authentication交给AuthenticationManager去认证
return getAuthenticationManager().authenticate(authRequest);
}
Security配置
public void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(authenticationProvider());
}
//自定义认证,主要将上篇的UsernamePasswordAuthenticationFilter换成了MyAuthentication
@Bean
public AuthenticationProvider authenticationProvider() {
return new AuthenticationProvider() {
public boolean supports(Class<?> authentication) {
//负责处理MyAuthentication类型登录认证,参考上一篇
return (MyAuthentication.class.isAssignableFrom(authentication));
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//轻松拿到details和principal,都是字符串,你也可以自定义任何类型
String details = authentication.getDetails().toString();
String code = authentication.getPrincipal().toString();
//对code进行认证,认证逻辑随意
if (!code.equals("123")) {
throw new BadCredentialsException("验证码错误");
}
//查询该code拥有的权限
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("ROLE_role1"));
// 认证通过,生成已认证的Authentication,加入请求权限
return new MyAuthentication(code,authorities);
}
};
}
//以下开始过滤器配置
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login/fail").anonymous()//开放登录失败接口
.anyRequest().authenticated().and()
.addFilterAt(myFilter(), UsernamePasswordAuthenticationFilter.class)//配置过滤器
.csrf().disable();
}
@Bean
public MyFilter myFilter() throws Exception {
MyFilter myFilter = new MyFilter();
myFilter.setAuthenticationManager(authenticationManager());//使过滤器关联当前的authenticationManager
myFilter.setFilterProcessesUrl("/login");//登录url,可覆盖构造方法中的url
myFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/login/fail"));//登录失败url
myFilter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/login/success"));//登录成功url
return myFilter;
}
注意最后一个方法,其中配置了三个url,和第1篇formLogin配置的url似曾相识。formLogin的作用其实就是开启了默认的认证过滤器UsernamePasswordAuthenticationFilter,然后对其进行配置。这里自定义了认证过滤器以后,就不再需要formLogin了。没有formLogin,默认的认证过滤器也不再起作用。此时用户请求/login时,将被自定义的认证过滤器拦截。
注册过滤器
spring security中有过滤链的概念,收到请求以后,链中的过滤器会按指定的顺序执行。如果要往链中添加过滤器,必须放在正确的位置,确保按既定顺序执行,否则会引起混乱。可使用以下四种方法
- .addFilterAt(filter,filter.class):将参数1的过滤器放到参数2过滤器相同的位置。这是我上面代码中用到的,将自定义认过滤器放到默认的认证过滤器UsernamePasswordAuthenticationFilter相同的位置顺序。
- .addFilterBefore(filter,filter.class):将参数1放到参数2的前面的位置顺序。
- .addFilterAfter(filter,filter.class):将参数1放到参数2的后面的位置顺序。由于现在UsernamePasswordAuthenticationFilter已经失效,所以不管放到它前面还是后面,效果都是一样的。
- .addFilter(filter):只有一个参数,它必须继承系统中已注册过的过滤器,如UsernamePasswordAuthenticationFilter,以此确定它的位置顺序。