短信验证码
短信验证码的发送
需要实现对手机的发送验证码,这里只是简单的处理,打印出来
-
定义接口 //发送短信的接口 public interface SmsCodeSender {
void send(String mobile, String code); } //生成短信接口 public interface ValidateCodeGenerator { ValidateCode generate(ServletWebRequest request); }
-
实现接口
//发送短信的接口的实现 public class DefaultSmsCodeSender implements SmsCodeSender { /* (non-Javadoc) * [@see](https://my.oschina.net/weimingwei) com.imooc.security.core.validate.code.sms.SmsCodeSender#send(java.lang.String, java.lang.String) //生成短信接口的实现 [@Override](https://my.oschina.net/u/1162528) public void send(String mobile, String code) { System.out.println("向手机"+mobile+"发送短信验证码"+code); } }
-
生成手机验证码 @Component("smsValidateCodeGenerator") public class SmsCodeGenerator implements ValidateCodeGenerator {
@Autowired private SecurityProperties securityProperties; /* * (non-Javadoc) * * [@see](https://my.oschina.net/weimingwei) * com.imooc.security.core.validate.code.ValidateCodeGenerator#generate(org. * springframework.web.context.request.ServletWebRequest) */ public ValidateCode generate(ServletWebRequest request) { String code = RandomStringUtils.randomNumeric(securityProperties.getCode().getSms().getLegth()); return new ValidateCode(code, securityProperties.getCode().getSms().getExpireIn()); } public SecurityProperties getSecurityProperties() { return securityProperties; } public void setSecurityProperties(SecurityProperties securityProperties) { this.securityProperties = securityProperties; } }
-
发送手机验证码
@GetMapping("/code/sms") public void createSmsCode(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletRequestBindingException { ImageCode imageCode = generate(request); ValidateCode smsCode = imageCodeGenerator.generate(new ServletWebRequest(request)); sessionStrategy.setAttribute(new ServletWebRequest(request), SESSION_KEY, smsCode); String mobile = ServletRequestUtils.getRequiredStringParameter(request,"mobile"); smsCodeSender.send(mobile,smsCode.getCode()); }
上面的部分实现了手机验证码的生成,接下来要做的就是手机验证码的验证。
手机验证码的验证
手机验证码的验证因为不像用户名密码那样可以直接登录,为了实现手机的验证,需要模仿用户名和密码的登录方式,对用户名登录的那一套进行改造,需要创建一个短信验证过滤器SmsAuthenticationFilter,短信验证码的token,SmsAuthenticaitonToken,然后需要一个验证手机号的Provider,SmsAuthenticationProvider,然后将手机号传递给UserDetailService进行相关的处理.
SmsAuthenticationToken的代码如下:
/**
* 封装登录信息
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 420L;
//放认证信息(登录之前放手机号,登录成功之后存放用户的信息)
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);
}
public Object getCredentials() {
return null;
}
public Object getPrincipal() {
return this.principal;
}
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);
}
}
public void eraseCredentials() {
super.eraseCredentials();
}
}
SmsCodeAuthenticationFilter.java
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
// =====================================================================================
//拦截的参数的名称
private String mobileParameter = SecurityConstants.DEFAULT_PARAMETER_NAME_MOBILE;
//表示是否只允许post请求
private boolean postOnly = true;
// ~ Constructors
// ===================================================================================================
/**
* 指定过滤器拦截的请求
*/
public SmsCodeAuthenticationFilter() {
super(new AntPathRequestMatcher(SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE, "POST"));
}
// ~ Methods
// ========================================================================================================
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
//判断是不是post请求
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String mobile = obtainMobile(request);
if (mobile == null) {
mobile = "";
}
mobile = mobile.trim();
SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile);
//认证前将请求的信息放入SmsCodeAuthenticationToken
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
/**
* 获取手机号
*/
protected String obtainMobile(HttpServletRequest request) {
return request.getParameter(mobileParameter);
}
/**
* 把请求的数放到认证请求里面去。
* Provided so that subclasses may configure what is put into the
* authentication request's details property.
*
* @param request
* that an authentication request is being created for
* @param authRequest
* the authentication request object that should have its details
* set
*/
protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
/**
* Sets the parameter name which will be used to obtain the username from
* the login request.
*
* @param usernameParameter
* the parameter name. Defaults to "username".
*/
public void setMobileParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.mobileParameter = usernameParameter;
}
/**
* Defines whether only HTTP POST requests will be allowed by this filter.
* If set to true, and an authentication request is received which is not a
* POST request, an exception will be raised immediately and authentication
* will not be attempted. The <tt>unsuccessfulAuthentication()</tt> method
* will be called as if handling a failed authentication.
* <p>
* Defaults to <tt>true</tt> but may be overridden by subclasses.
*/
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getMobileParameter() {
return mobileParameter;
}
}
SmsCodeAuthenticationProvider.java
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
private UserDetailsService userDetailsService;
/*
* (non-Javadoc)
*
* @see org.springframework.security.authentication.AuthenticationProvider#
* authenticate(org.springframework.security.core.Authentication)
*/
@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());
/*
SmsCodeAuthenticationFilter的
将之前没验证的SmsCodeAuthenticationToken内的请求信息重新放入到新创建的token里面
*/
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
/*
*判断传递进来的东西是不是SmsCodeAuthenticationToken
* (non-Javadoc)
*
* @see org.springframework.security.authentication.AuthenticationProvider#
* supports(java.lang.Class)
*/
@Override
public boolean supports(Class<?> authentication) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
public UserDetailsService getUserDetailsService() {
return userDetailsService;
}
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
}
SmsCodeAuthenticationSecurityConfig.java(短息验证的配置)
/**
* 该模块是要在app,也要在browser里面用
*/
@Component
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain,HttpSecurity>{
@Autowired
private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;
@Autowired
private AuthenticationFailureHandler imoocAuthenticationFailureHandler;
@Autowired
private UserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity builder) throws Exception {
SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
//设置manager
smsCodeAuthenticationFilter.setAuthenticationManager(builder.getSharedObject(AuthenticationManager.class));
smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(imoocAuthenticationSuccessHandler);
smsCodeAuthenticationFilter.setAuthenticationFailureHandler(imoocAuthenticationFailureHandler);
//设置Provider
SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);
//将我们自定义的Provider加入到所有provider的集合里面
builder.authenticationProvider(smsCodeAuthenticationProvider)
.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
/**
* 专门用来做web安全应用的适配器WebSecurityConfigurerAdapter
*/
@Configuration
public class BrowswerSecurityConfig extends AbstractChannelSecurityConfig {
@Autowired
private SecurityProperties securityProperties;
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Autowired
private ValidateCodeSecurityConfig validateCodeSecurityConfig;
/**
* 配置密码编码器
*
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Primary
@Bean(name = "dataSource")
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(){
return DataSourceBuilder.create().build();
}
/**
* @param
* @throws Exception
*/
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
//配置数据源
tokenRepository.setDataSource(dataSource);
//配置在启动的时候创建表
// tokenRepository.setCreateTableOnStartup(true);
return tokenRepository;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
applyPasswordAuthenticationConfig(http);
http.apply(validateCodeSecurityConfig)
.and()
.apply(smsCodeAuthenticationSecurityConfig)
.and()
// .apply(imoocSocialSecurityConfig)
// .and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
.userDetailsService(userDetailsService)
.and()
.authorizeRequests()
.antMatchers(
SecurityConstants.DEFAULT_UNAUTHENTICATION_URL,
SecurityConstants.DEFAULT_LOGIN_PROCESSING_URL_MOBILE,
securityProperties.getBrowser().getLoginPage(),
SecurityConstants.DEFAULT_VALIDATE_CODE_URL_PREFIX+"/*")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
}
之后就可以实现短信的验证了
来源:oschina
链接:https://my.oschina.net/u/3474937/blog/3036383