实现原理
在之前的文章中,我们介绍了普通的帐号密码登录的方式: springboot + spring security 基本使用及个性化登录配置。 但是现在还有一种常见的方式,就是直接通过手机短信验证码登录,这里就需要自己来做一些额外的工作了。
对springsecurity认证流程详解有一定了解的都知道,在帐号密码认证的过程中,涉及到了以下几个类:usernamepasswordauthenticationfilter(用于请求参数获取),usernamepasswordauthenticationtoken(表示用户登录信息),providermanager(进行认证校验),
因为是通过的短信验证码登录,所以我们需要对请求的参数,认证过程,用户登录token信息进行一定的重写。
当然验证码的过程我们应该放在最前面,如果的实现一样。这样的做法的好处是:将验证码认证该过程解耦出来,让其他接口也可以使用到。
基本实现
验证码校验
短信验证码的功能实现,其实和图形验证码的原理是一样的。只不过一个是返回给前端一个图片,一个是给用户发送短消息,这里只需要去调用一下短信服务商的接口就好了。更多的原理可以参考 springboot + springsecurity 实现图形验证码功能
authenticationtoken
在使用帐号密码登录的时候,usernamepasswordauthenticationtoken里面包含了用户的帐号,密码,以及其他的是否可用等状态信息。我们是通过手机短信来做登录,所以就没有密码了,这里我们就直接将usernamepasswordauthenticationtoken的代码copy过来,把密码相关的信息去掉就可以了
public class smscodeauthenticationtoken extends abstractauthenticationtoken { private static final long serialversionuid = springsecuritycoreversion.serial_version_uid; private final object principal; public smscodeauthenticationtoken(string mobile) { super(null); this.principal = mobile; setauthenticated(false); } public smscodeauthenticationtoken(object principal, collection<? extends grantedauthority> authorities) { super(authorities); this.principal = principal; super.setauthenticated(true); // must use super, as we override } 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(); } }
authenticationfilter
在帐户密码登录的流程中,默认使用的是usernamepasswordauthenticationfilter,它的作用是从请求中获取帐户、密码,请求方式校验,生成authenticationtoken。这里我们的参数是有一定改变的,所以还是老方法,copy过来进行简单的修改
public class smscodeauthenticationfilter extends abstractauthenticationprocessingfilter { // 请求参数key private string mobileparameter = securityconstants.default_parameter_name_mobile; // 是否只支持post private boolean postonly = true; public smscodeauthenticationfilter() { // 请求接口的url super(new antpathrequestmatcher(securityconstants.default_login_processing_url_mobile, "post")); } public authentication attemptauthentication(httpservletrequest request, httpservletresponse response) throws authenticationexception { if (postonly && !request.getmethod().equals("post")) { throw new authenticationserviceexception("authentication method not supported: " + request.getmethod()); } // 根据请求参数名,获取请求value string mobile = obtainmobile(request); if (mobile == null) { mobile = ""; } mobile = mobile.trim(); // 生成对应的authenticationtoken smscodeauthenticationtoken authrequest = new smscodeauthenticationtoken(mobile); setdetails(request, authrequest); return this.getauthenticationmanager().authenticate(authrequest); } /** * 获取手机号 */ protected string obtainmobile(httpservletrequest request) { return request.getparameter(mobileparameter); } // 省略不相关代码 }
provider
在帐号密码登录的过程中,密码的正确性以及帐号是否可用是通过daoauthenticationprovider来校验的。我们也应该自己实现一个provier
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; } @override public boolean supports(class<?> authentication) { return smscodeauthenticationtoken.class.isassignablefrom(authentication); } public userdetailsservice getuserdetailsservice() { return userdetailsservice; } public void setuserdetailsservice(userdetailsservice userdetailsservice) { this.userdetailsservice = userdetailsservice; } }
配置
主要的认证流程就是通过以上四个过程实现的, 这里我们再降它们配置一下就可以了
@component public class smscodeauthenticationsecurityconfig extends securityconfigureradapter<defaultsecurityfilterchain, httpsecurity> { @autowired private authenticationsuccesshandler myauthenticationsuccesshandler; @autowired private authenticationfailurehandler myauthenticationfailurehandler; @autowired private userdetailsservice userdetailsservice; @override public void configure(httpsecurity http) throws exception { smscodeauthenticationfilter smscodeauthenticationfilter = new smscodeauthenticationfilter(); smscodeauthenticationfilter.setauthenticationmanager(http.getsharedobject(authenticationmanager.class)); smscodeauthenticationfilter.setauthenticationsuccesshandler(myauthenticationsuccesshandler); smscodeauthenticationfilter.setauthenticationfailurehandler(myauthenticationfailurehandler); smscodeauthenticationprovider smscodeauthenticationprovider = new smscodeauthenticationprovider(); smscodeauthenticationprovider.setuserdetailsservice(userdetailsservice); http.authenticationprovider(smscodeauthenticationprovider) .addfilterafter(smscodeauthenticationfilter, usernamepasswordauthenticationfilter.class); } } // browersecurityconfig.java @override protected void configure(httpsecurity http) throws exception { http.apply(smscodeauthenticationsecurityconfig); }
代码下载
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
荐 Python部分第三方库简要介绍及近期Python入门学习笔记
荐 PCA降维原理及其代码实现(附加 sklearn PCA用法参数详解)
网友评论