/**
*spring security对过滤器没有特别要求,一般继承filter即可。
*但是在spring中一般推荐使用OncePerRequestFilter(确保一次请求只会通过一次该过滤器)
*/
public class VerificationCodeFilter extends OncePerRequestFilter {
// 用来处理校验失败的逻辑
private AuthenticationFailureHandler handler = new MyAuthenticationFailureHandler();
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
// 对登录url不进行验证码校验,直接通过
if(!"/auth/login".equals(request.getRequestURI())){
filterChain.doFilter(request,response);
}else{
try{
// 从session中取出验证码和request中的对比,如果不同则抛出异常
verifyCode(request);
filterChain.doFilter(request,response);
}catch (Exception e){
handler.onAuthenticationFailure(request,response,e);
}
}
}
}
public void configure(HttpSecurity http) throws Exception{
// 将验证码过滤器添加在UsernamePasswordAuthenticationFilter之前
http.addFilterBefore(new VerificationCodeFilter(),UsernamePasswordAuthenticationFilter.class);
}
在Spring Security中,系统中的用户被称为主体,通过一层包装将其定义为一个Authentication
public interface Authentication extends Principal, Serializable {
// 获取主体的权限列表
Collection<? extends GrantedAuthority> getAuthorities();
// 获取主体凭据,一般是用户名和密码
Object getCredentials();
// 获取主体携带的详细信息
Object getDetails();
// 获取主体,一般就是用户名
Object getPrincipal();
// 主体是否被验证成功
boolean isAuthenticated();
void setAuthenticated(boolean var1) throws IllegalArgumentException;
}
对于AuthenticationProvider,被Spring Security定义为一个验证过程
public interface AuthenticationProvider {
// 验证过程,成功返回一个验证完成的Authentication
Authentication authenticate(Authentication var1) throws AuthenticationException;
// 是否支持验证当前的Authentication类型
boolean supports(Class<?> var1);
}
一次验证过程可能包含多个AuthenticationProvider ,这些provider由providerManager统一管理
public class MyAuthenticationProvider extends DaoAuthenticationProvider {
public MyAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder encoder){
this.setUserDetailsService(userDetailsService);
this.setPasswordEncoder(encoder);
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
// 实现图形码验证逻辑
// 调用父类的方法完成密码验证
super.additionalAuthenticationChecks(userDetails, authentication);
}
}
由于验证码一般是存在session里的,并且用户输入的验证码是存在request中的,但是上面additionalAuthenticationChecks方法并没有request参数,所以需要额外添加
在UsernamePasswordAuthenticationFilter中使用的AuthenticationDetailsSource是一个标准的web认证源,携带的是用户的sesionId和IP地址
public class WebAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
public WebAuthenticationDetailsSource() {
}
public WebAuthenticationDetails buildDetails(HttpServletRequest context) {
return new WebAuthenticationDetails(context);
}
}
public class WebAuthenticationDetails implements Serializable {
private static final long serialVersionUID = 520L;
private final String remoteAddress;
private final String sessionId;
public WebAuthenticationDetails(HttpServletRequest request) {
this.remoteAddress = request.getRemoteAddr();
HttpSession session = request.getSession(false);
this.sessionId = session != null ? session.getId() : null;
}
private WebAuthenticationDetails(String remoteAddress, String sessionId) {
this.remoteAddress = remoteAddress;
this.sessionId = sessionId;
}
...
所以我们可以继承WebAuthenticationDetails并扩展需要的信息
public class MyWebAuthenticationDetails extends WebAuthenticationDetails {
// 表示输入的验证码是否正确
private Boolean imageCodeIsRight;
// setter and getter
public MyWebAuthenticationDetails(HttpServletRequest request) {
String imageCode = request.getParameter("imageCode");
String generatedImageCode = (String) request.getSession().getAttribute("generatedImageCode");
/* 判断两个验证码是否相等从而设置imageCodeIsRight的值 略*/
if(相等){
this.imageCodeIsRight = true
}else{
this.imageCodeIsRight = false
}
super(request);
}
}
@Component
public class MyWebAuthenticationDetailsSource implements WebAuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> {
@Override
public WebAuthenticationDetails buildDetails(HttpServletRequest request){
return new MyWebAuthenticationDetails(request);
}
}
经过上面定义,并将MyWebAuthenticationDetailsSource 将入到spring容器中,就可以通过usernamePasswordAuthenticationToken来获取MyWebAuthenticationDetails
public class MyAuthenticationProvider extends DaoAuthenticationProvider {
public MyAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder encoder){
this.setUserDetailsService(userDetailsService);
this.setPasswordEncoder(encoder);
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
// 实现图形码验证逻辑
MyWebAuthenticationDetails details = (MyWebAuthenticationDetails )UsernamePasswordAuthenticationToken.getDetails();
if(!details.getImageCodeIsRight()){
// 输入的验证码错误,抛出异常
}
// 调用父类的方法完成密码验证
super.additionalAuthenticationChecks(userDetails, authentication);
}
}
在WebSecurityConfig中配置AuthenticationProvider和AuthenticationDetailsSource:
1. 在WebSecurityConfig中使用@autowire注解自动注入MyWebAuthenticationDetailsSource(已经使用@component注解加入到了spring中)
a
2.在WebSecurityConfig中使用@autowire注解自动注入AuthenticationProvider
2. protected void configure(AuthenticationManagerBuilder auth) throws Exception{
auth.authenticationProvider(authenticationProvider);
}
3.protected void configure(HttpSecurity http) throws Exception{
http....
.formLogin()
.authenticationDetailsSource(myWebAuthenticationDetailsSource)
}