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,输入错误的验证码,结果为
如果您觉得本文不错,欢迎关注,点赞,收藏支持,您的关注是我坚持的动力!
原创不易,转载请注明出处,感谢支持!如果本文对您有用,欢迎转发分享!