SpringSecurity-7-自定义AuthenticationProvider实现图形验证码
上一章节我们介绍了如何使用过滤器(Filter)实现图形验证,这是属于Servlet层面,比较简单容易理解。那么这次我们介绍SpringSecurity提供的另一种比较高端的实现图形化验证码,这就是AuthenticationProvider自定义认证。
认证流程
我们在
- SpringSecurity认证流程源码解析中介绍了SpringSecurity的认证流程
其中介绍了系统的用户信息,保存在SpringSecurity的主体(Principal)中。主体中包含了所有经过验证用户的权限,详细信息等内容。在SpringSecurity中将其封装放在Authentication中,代码如下
public interface Authentication extends Principal, Serializable { /** * 获取用户权限 * @return */ Collection<? extends GrantedAuthority> getAuthorities(); /** * 获取用于的凭证,用户密码 * @return */ Object getCredentials(); /** * 用户的详细信息 * @return */ Object getDetails(); /** * 用户凭证,一般为用户名 * @return */ Object getPrincipal(); /** * 用户验证是否成功 * @return */ boolean isAuthenticated(); void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }
说明:
Authentication中包含主体权限列表,主体凭据,主体的详细信息,及是否验证成功等。
AuthenticationProvider被SpringSecurity定义为一个验证过程
ProviderManager管理多个AuthenticationProvider
UsernamePasswordAuthenticationFilter
我们查看UsernamePasswordAuthenticationFilter类发现设置用户信息的方法setDetails方法
从源码我们可以看出authenticationDetailsSource是由AbstractAuthenticationProcessingFilter提供的AbstractAuthenticationProcessingFilter部分源码如下
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean implements ApplicationEventPublisherAware, MessageSourceAware { protected ApplicationEventPublisher eventPublisher; protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource(); ... }
WebAuthenticationDetailsSource
在UsernamePasswordAuthenticationFilter中使用的AuthenticationDetailsSource是一个标准的Web认证 源,携带的是用户的sessionId和IP地址。源码如图所示
自定义WebAuthenticationDetails有了HttpServletRequest之后,一切都将变得非常顺畅。基于图形验证码的场景,我们可以继承 WebAuthenticationDetails,并扩展需要的信息。因此我们可以自定义WebAuthenticationDetails存储额外信息。
/** *自定义WebAuthenticationDetails存储额外的图形验证信息 */ public class ImageCodeWebAuthenticationDetails extends WebAuthenticationDetails { /** * 图形信息是否验证成功 */ private boolean imageCodeIsRight; public boolean getImageCodeIsRight(){ return imageCodeIsRight; } public ImageCodeWebAuthenticationDetails(HttpServletRequest request) { super(request); // 先获取seesion中的验证码 HttpSession session = request.getSession(); String sessionCode = (String) session.getAttribute(CaptchaController.SESSION_KEY); // 获取用户输入的验证码 String inpuCode = request.getParameter("code"); if(!StringUtils.isEmpty(inpuCode)){ //清除验证码,不论验证成功还是失败,都需要清除验证码,并且在验证失败的时候需要刷新验证码 session.removeAttribute("code"); if(!StringUtils.isEmpty(sessionCode)&& inpuCode.equalsIgnoreCase(sessionCode) ){ this.imageCodeIsRight=true; } } } }
自定义的AuthenticationDetailsSource。
@Component("imageCodeWebAuthenticationDetailsSource") public class ImageCodeWebAuthenticationDetailsSource implements AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> { @Override public ImageCodeWebAuthenticationDetails buildDetails(HttpServletRequest context) { return new ImageCodeWebAuthenticationDetails(context); } }
自定义AuthenticationProvider。
@Component("imageCodeAuthenticationProvider") public class ImageCodeAuthenticationProvider extends DaoAuthenticationProvider { public ImageCodeAuthenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { this.setUserDetailsService(userDetailsService); this.setPasswordEncoder(passwordEncoder); } @Override protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { //获取详细信息 ImageCodeWebAuthenticationDetails details = (ImageCodeWebAuthenticationDetails)authentication.getDetails(); //如果验证码不正确,抛出异常 if(!details.getImageCodeIsRight()){ throw new ValidateCodeException("验证码输入错误"); } super.additionalAuthenticationChecks(userDetails, authentication); } }
修改配置类
想要应用自定义的 AuthenticationProvider 和 AuthenticationDetailsSource,还需在LearnSrpingSecurity中完成剩余的配置。
/** * 安全配置类 */ @EnableWebSecurity public class LearnSrpingSecurity extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("imageCodeWebAuthenticationDetailsSource") private AuthenticationDetailsSource<HttpServletRequest,WebAuthenticationDetails> imageCodeWebAuthenticationDetailsSource; @Autowired @Qualifier("imageCodeAuthenticationProvider") private AuthenticationProvider imageCodeAuthenticationProvider; /** * 认证管理器 * 1.认证信息提供方式(用户名、密码、当前用户的资源权限) * 2.可采用内存存储方式,也可能采用数据库方式等 * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { //super.configure(auth); auth.authenticationProvider(imageCodeAuthenticationProvider); } /** * 资源权限配置(过滤器链): * 1、被拦截的资源 * 2、资源所对应的角色权限 * 3、定义认证方式:httpBasic 、httpForm * 4、定制登录页面、登录请求地址、错误处理方式 * 5、自定义 spring security 过滤器 * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() //禁用跨站csrf攻击防御,后面的章节会专门讲解 .formLogin() .authenticationDetailsSource(imageCodeWebAuthenticationDetailsSource) .loginPage("/login/page")//一旦用户的请求没有权限就跳转到这个页面 .loginProcessingUrl("/login/form")//登录表单form中action的地址,也就是处理认证请求的路径 .usernameParameter("username")///登录表单form中用户名输入框input的name名,不修改的话默认是username .passwordParameter("password")//form中密码输入框input的name名,不修改的话默认是password //.defaultSuccessUrl("/syslog")//登录认证成功后默认转跳的路径 //.failureHandler(failureHandler) .and() .authorizeRequests() .antMatchers("/login/page","/code/image").permitAll()//不需要通过登录验证就可以被访问的资源路径 .anyRequest().authenticated(); } }
主要修改如图
测试
我们使用浏览器浏览http://localhost:8888,输入错误的验证码,结果为
如果您觉得本文不错,欢迎关注,点赞,收藏支持,您的关注是我坚持的动力!