文章目录
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200215165103849.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Rhb2ppbjEy,size_16,color_FFFFFF,t_70)
SpringSecurity进行用户登录认证时,通过UsernamePasswordAuthenticationFilter获取用户信息,获取一个UsernamePasswordAuthenticationToken,将该token设置给AuthenticationManager进行管理,选择不同的Provider进行认证处理,在Provider中通过UserDetaitlsService获取用户信息进行认证,生成新的认证信息。
在短信验证时,我们只要按照登录验证的步骤进行重新写一个自己的过滤器、token类、provider即可。
1、自己实现一个SmsAuthenticationToken类
/**
* 短信验证码token
*
* 仿照UsernamePasswordAuthenticationToken
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 520L;
/**
* 该属性没登录前放手机号,登录后的放用户信息
*/
private final Object principal;
public SmsCodeAuthenticationToken(String mobile) {
super((Collection)null);
this.principal = mobile;
this.setAuthenticated(false);
}
public SmsCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
} else {
super.setAuthenticated(false);
}
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
2、自己实现一个SmsCodeAuthenticationFilter,验证用户
/**
* 仿照UsernamePasswordAuthenticationFilter
*/
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String IMOOC_FROM_MOBILE_KEY = "mobile";
private String mobileParameter = "username";
private boolean postOnly = true;
public SmsCodeAuthenticationFilter() {
super(new AntPathRequestMatcher("/authentication/mobile", "POST"));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String mobile = this.obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
// 生成token
SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
this.setDetails(request, authRequest);
// 调用 AuthenticationManager
return this.getAuthenticationManager().authenticate(authRequest);
}
}
/**
* 获取手机号
* @param request
* @return
*/
@Nullable
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(this.mobileParameter);
}
protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
public void setUsernameParameter(String mobileParameter) {
Assert.hasText(mobileParameter, "Username parameter must not be empty or null");
this.mobileParameter = mobileParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getMobileParameter() {
return this.mobileParameter;
}
}
3、自己实现一个SmsCodeAuthenticationProvider
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
/**
* 进行身份认证的逻辑
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken)authentication;
UserDetails user = userDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
if (user == null) {
throw new InternalAuthenticationServiceException("无法获取用户信息");
}
SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(user, user.getAuthorities());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
/**
* 根据该方法判断 AuthenticationManager选择哪个 Provider进行认证处理
* @param authentication
* @return
*/
@Override
public boolean supports(Class<?> authentication) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
}
4、验证码验证过滤器
/**
* 继承一个过滤器,实现验证码验证
*/
public class SmsCodeFilter extends OncePerRequestFilter implements InitializingBean {
private AuthenticationFailureHandler authenticationFailureHandler;
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
private Set<String> urls = new HashSet<>();
private SecurityProperties securityProperties;
private AntPathMatcher pathMatcher = new AntPathMatcher();
/**
* 初始化设置
* 将配置文件中的值读取,存在urls中
* @throws ServletException
*/
@Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
String[] configUrls = StringUtils.split(securityProperties.getCode().getSms().getUrl(),",");
// 添加配置的url
for (String configUrl : configUrls) {
urls.add(configUrl);
}
// 添加拦截的url
urls.add("authentication/mobile");
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
/**
* 如果是登录请求,并且是post请求就执行验证
*
*/
boolean action = false;
for (String url : urls) {
if (pathMatcher.match(url, request.getRequestURI())) {
action = true;
}
}
if (action) {
try {
validate(new ServletWebRequest(request));
} catch (ValidateCodeException e) {
authenticationFailureHandler.onAuthenticationFailure(request,response,e);
return ;
}
}
// if (StringUtils.equals("authentication/from", request.getRequestURI())
// && StringUtils.equalsIgnoreCase(request.getMethod(),"post")){
//
// try {
// validate(new ServletWebRequest(request));
// } catch (ValidateCodeException e) {
// authenticationFailureHandler.onAuthenticationFailure(request,response,e);
// return ;
// }
// }
filterChain.doFilter(request, response);
}
private void validate(ServletWebRequest request) throws ServletRequestBindingException {
// 读取session
ValidateCode codeInSession = (ValidateCode) sessionStrategy.getAttribute(request,
ValidateCodeController.SESSION_KEY);
// 获取验证码的值
String codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(),"smsCode");
if (StringUtils.isEmpty(codeInRequest)) {
throw new ValidateCodeException("验证码的值不能为空");
}
if (codeInSession == null) {
throw new ValidateCodeException("验证码不存在");
}
if (codeInSession.isExpried()) {
sessionStrategy.removeAttribute(request, ValidateCodeController.SESSION_KEY);
throw new ValidateCodeException("验证码已过期");
}
if (!StringUtils.equals(codeInSession.getCode(),codeInRequest)) {
throw new ValidateCodeException("验证码不匹配");
}
sessionStrategy.removeAttribute(request,ValidateCodeController.SESSION_KEY);
}
public AuthenticationFailureHandler getAuthenticationFailureHandler() {
return authenticationFailureHandler;
}
public void setAuthenticationFailureHandler(AuthenticationFailureHandler authenticationFailureHandler) {
this.authenticationFailureHandler = authenticationFailureHandler;
}
public SessionStrategy getSessionStrategy() {
return sessionStrategy;
}
public void setSessionStrategy(SessionStrategy sessionStrategy) {
this.sessionStrategy = sessionStrategy;
}
public Set<String> getUrls() {
return urls;
}
public void setUrls(Set<String> urls) {
this.urls = urls;
}
public SecurityProperties getSecurityProperties() {
return securityProperties;
}
public void setSecurityProperties(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
}
5、将SmsCodeAuthenticationFitler和SmsCodeAuthenticationProvider进行配置
将短信验证码登录验证的filter和provider,进行单独配置。一般在项目中,短信验证码会是一个公共项目组件。
/**
* 配置SmsCodeAuthenticationFilter
* 和SmsCodeAuthenticationProvider
*/
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity http) throws Exception {
SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
smsCodeAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
http.authenticationProvider(smsCodeAuthenticationProvider)
.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
6、验证码的验证进行配置
/**
* 覆盖springboot对security的默认配置
*/
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
/**
* 注入登录成功操作类
*/
@Autowired
private AuthenticationSuccessHandler imoocAutheticationSuccessHandler;
/**
* 注入登录失败的处理器
*/
@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailuredHandler;
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailsService userDetailsService;
/**
* 将短信验证的单独配置引入进来,通过HttpSecurity 的apply()方法进行配置
*/
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
/**
* 用户密码加密类配置
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
// 这里可以返回自己实现的加密类
return new BCryptPasswordEncoder();
}
/**
* 记住我的配置
* @return
*/
@Bean
public PersistentTokenRepository persistentTokenRepository () {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
tokenRepository.setDataSource(dataSource);
// 启动的时候创建表
// tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 验证码过滤器配置
ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter();
// 设置失败处理器
validateCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailuredHandler);
validateCodeFilter.setSecurityProperties(securityProperties);
validateCodeFilter.afterPropertiesSet();
// 短信验证码过滤器配置
SmsCodeFilter smsCodeFilter = new SmsCodeFilter();
// 设置失败处理器
smsCodeFilter.setAuthenticationFailureHandler(imoocAuthenticationFailuredHandler);
smsCodeFilter.setSecurityProperties(securityProperties);
smsCodeFilter.afterPropertiesSet();
http.addFilterBefore(smsCodeFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
.formLogin() // 表单登录 http.httpBasic() httpbasic登录
// .loginPage("/imooc-signIn.html") // 配置登录页面
.loginPage("/authentication/require")
.loginProcessingUrl("/authentication/from") // 配置登录请求
.successHandler(imoocAutheticationSuccessHandler) // 登录成功处理器
.failureHandler(imoocAuthenticationFailuredHandler)
.and()
.rememberMe() // 配置记住我
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
.userDetailsService(userDetailsService)
.and()
.authorizeRequests() // 请求验证
// .antMatchers("/imooc-signIn.html").permitAll()// 匹配不需要身份认证的路径
.antMatchers("/authentication/require",
securityProperties.getBrowser().getLoginPage(),
"/code/*").permitAll()
.anyRequest() // 任何请求
.authenticated() // 都需要身份认证
.and()
.csrf().disable()
.apply(smsCodeAuthenticationSecurityConfig); // 关闭伪造跨站请求防护功能
}
}