在之前的博客中我们实现了基于验证码的登陆方式。但是我们仅仅实现了通过图片验证码的登录,现在我们基于security 实现一下基于 短信验证码的登录。
基于之前对于 security 的了解,我们知道了要实现一个验证的基本流程,其中最重要的是 AbstractAuthenticationToken(令牌类)、AuthenticationProvider(认证类)、AbstractAuthenticationProcessingFilter (过滤器类)这三个类。
我们可以基于用户密码的登陆方式来实现,对于用户名密码的登录涉及的三个类为:UsernamePasswordAuthenticationToken、UsernamePasswordAuthenticationFilter、而 AuthenticationProvider 在之前我们也是自定义的 ,修改一下就可以用。
1. 编写 SmsCodeAuthenticationToken
/**
* Create with IntelliJ IDEA
* User: Wuzhenzhao
* Date: 2019/3/13
* Time: 18:13
* Description: 参考 UsernamePasswordAuthenticationToken 写
* 封装登录信息
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
// ~ Instance fields
// ================================================================================================
private final Object principal;
//这个在 UsernamePasswordAuthenticationToken 是登录密码,不需要 干掉。
private Object credentials;
// ~ Constructors
// ===================================================================================================
/**
* This constructor can be safely used by any code that wishes to create a
* UsernamePasswordAuthenticationToken
, as the {@link #isAuthenticated()}
* will return false
.
*
*/
public SmsCodeAuthenticationToken(String mobile) {
super(null);
this.principal = mobile;
setAuthenticated(false);
}
/**
* This constructor should only be used by AuthenticationManager
or
* AuthenticationProvider
implementations that are satisfied with
* producing a trusted (i.e. {@link #isAuthenticated()} = true
)
* authentication token.
*
* @param principal
* @param authorities
*/
public SmsCodeAuthenticationToken(Object principal,
Collection extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true); // must use super, as we override
}
// ~ Methods
// ========================================================================================================
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");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
}
2. SmsCodeAuthenticationProvider ,用于短信登陆的验证
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());
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
/*
* (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;
}
}
3. SmsCodeAuthenticationFilter 拦截短信登陆请求
/**
* Create with IntelliJ IDEA
* User: Wuzhenzhao
* Date: 2019/3/13
* Time: 18:13
* Description: 参考 UsernamePasswordAuthenticationFilter
*/
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
// ~ Static fields/initializers
// =====================================================================================
/**
* 发送短信验证码 或 验证短信验证码时,传递手机号的参数的名称
*/
private String mobileParameter = "mobile";
private boolean postOnly = true;
// ~ Constructors
// ===================================================================================================
public SmsCodeAuthenticationFilter() {
/**
* 默认的手机验证码登录请求处理url
* http://localhost:8889/code/sms?mobile=13888888888
*/
super(new AntPathRequestMatcher("/authentication/mobile", "POST"));
}
// ~ Methods
// ========================================================================================================
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
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);
// 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 unsuccessfulAuthentication() method
* will be called as if handling a failed authentication.
*
* Defaults to true but may be overridden by subclasses.
*/
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getMobileParameter() {
return mobileParameter;
}
}
4.在 security 配置类中注入:
@Configuration
@EnableWebSecurity// 开启Security
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启Spring方法级安全
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private MyAuthenticationProvider myAuthenticationProvider;
@Autowired
private MyAuthenctiationFailureHandler myAuthenctiationFailureHandler;
@Autowired
private MyUserDetailService myUserDetailService;
// 自定义认证配置
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(myAuthenticationProvider);
SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
smsCodeAuthenticationProvider.setUserDetailsService(myUserDetailService);
auth.authenticationProvider(smsCodeAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// .......省略部分代码
SmsCodeAuthenticationFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationFilter();
smsCodeAuthenticationFilter.setAuthenticationManager(this.authenticationManager());
smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
smsCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenctiationFailureHandler);
http.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
对于提交登录得时候验证码的校验逻辑在 博客 :https://www.cnblogs.com/wuzhenzhao/p/13169023.html 中提到的 AbstractValidateCodeProcessor 进行验证。这样子先进行一个短信的发送,然后模拟一下登录就可用实现了.