SpringSecurity之添加过滤器

案例:实现验证码校验

1.使用过滤器实现验证码校验

1.1 创建过滤器

/**
*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);
            }

        }
    }
}

2.将过滤器添加到spring security的过滤器链中

public void configure(HttpSecurity http) throws Exception{
	// 将验证码过滤器添加在UsernamePasswordAuthenticationFilter之前
	http.addFilterBefore(new VerificationCodeFilter(),UsernamePasswordAuthenticationFilter.class);
}

2.自定义实现验证码校验

2.1 AuthenticationProvider介绍

在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统一管理

2.2 自定义AuthenticationProvider

  • 首先,spring security提供了一个抽象的provider:AbstractUserDetailsAuthenticationProvider
  • 在AbstractUserDetailsAuthenticationProvider中提供了基本的认证流程
  • 所以我们可以通过集成这个类,并实现retrieveUser和additionalAuthenticationChecks两个抽象方法即可自定义核心认证过程
  • spring security同样提供了一个DaoAuthenticationProvider继承自AbstractUserDetailsAuthenticationProvider

2.3 实现图形验证码的AuthenticationProvider

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)

  }

你可能感兴趣的:(springSecurity)