准备工作:

<!-- 导入security依赖 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

登录流程:

SecurityContextPersistenceFilter:是整个程序执行的入口,我们可以在这里做登录拦截判断,并验证我们的账户密码

第一步拦截请求,并验证手机验证码

/*** 第一步:拦截请求,验证手机验证码是否正确* 关靠这个无法执行,下一步需要写入口*/
public class SmsCodeCheckFilter extends OncePerRequestFilter {private MyAuthenticationFailureHandler myAuthenticationFailureHandler;public void setMyAuthenticationFailureHandler(MyAuthenticationFailureHandler myAuthenticationFailureHandler) {this.myAuthenticationFailureHandler = myAuthenticationFailureHandler;}@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {// 不是做短信登录,直接放行if(!new AntPathMatcher().match("/smsLogin", httpServletRequest.getRequestURI())) {// 放行filterChain.doFilter(httpServletRequest, httpServletResponse);return;}if (myAuthenticationFailureHandler != null) {// 获取提交的手机号String phone = httpServletRequest.getParameter("phone");// 获取提交的验证码String code = httpServletRequest.getParameter("code");// 判断手机号和验证码if (!StringUtils.hasText(phone)) {myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, new AccountExpiredException("手机号不能为空"));return;}if (code == null) {myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, new AccountExpiredException("请填写验证码"));return;}// 从session中获取手机号String phoneFromSession = (String) httpServletRequest.getSession().getAttribute("phone");// 从session中获取验证码String codeFromSession = (String) httpServletRequest.getSession().getAttribute("code");// 判断验证码if (codeFromSession == null) {myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, new AccountExpiredException("验证码失效,请重新获取"));return;}// 对比手机if (!phone.equalsIgnoreCase(phoneFromSession)) {myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, new AccountExpiredException("验证码错误"));return;}// 比对验证码if (!code.equalsIgnoreCase(codeFromSession)) {myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, new AccountExpiredException("验证码错误"));return;}// 放行filterChain.doFilter(httpServletRequest, httpServletResponse);}}
}

认证失败处理器:

/*** 自定义认证失败处理器*/
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {// 设置响应字符集httpServletResponse.setContentType("text/html;charset=utf8");httpServletResponse.setCharacterEncoding("utf-8");// 将错误信息告诉前端httpServletResponse.getWriter().print(e.getMessage());httpServletResponse.getWriter().flush();httpServletResponse.getWriter().close();}
}

认证成功处理器:

@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {// 设置响应的字符集httpServletResponse.setContentType("text/html;charset=utf8");// 登录成功httpServletResponse.getWriter().println("登录成功");httpServletResponse.getWriter().println("用户" + authentication.getPrincipal()+"拥有权限:");authentication.getAuthorities().forEach(grantedAuthority -> {try {httpServletResponse.getWriter().println(grantedAuthority.getAuthority());} catch (IOException e) {e.printStackTrace();}});httpServletResponse.getWriter().flush();httpServletResponse.getWriter().close();}
}

UsernamePasswordAuthenticationFilter:封装的是用户名和密码,我们需要更换Security的验证规则为我们自己的,也就是说,我们需要重写这个类,实现我们自己的业务逻辑(我们只要封装一个手机号就好)。

第二步,自定义UsernamePasswordAuthenticationFilter

/*** 第二步,入口大门,原本账号密码使用的是UsernamePasswordAuthenticationFilter* 它比较的是账号和密码,而我们的短信登录是没有密码的,我们只要判断手机号是否存在就行、* 因为在SmsCodeCheckFilter里已经验证过验证码了,所以我们这里要做的是拦截请求,获取参数*/
public class SmsAuthenticationFilter extends AbstractAuthenticationProcessingFilter {/** 是否只支持post请求 */private boolean postOnly = true;private MyAuthenticationFailureHandler myAuthenticationFailureHandler;public void setMyAuthenticationFailureHandler(MyAuthenticationFailureHandler myAuthenticationFailureHandler) {this.myAuthenticationFailureHandler = myAuthenticationFailureHandler;}/*** 这里必须要这个构造器*/public SmsAuthenticationFilter() {// 第一个参数是登录的提交路径,第二个参数是请求方式super(new AntPathRequestMatcher("/smsLogin", "POST"));}@Overridepublic Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {// 验证是否只能是post、验证请求方式是否是postif (this.postOnly && !httpServletRequest.getMethod().equals("POST")) {// 认证失败,报一个认证失败myAuthenticationFailureHandler.onAuthenticationFailure(httpServletRequest, httpServletResponse, new InsufficientAuthenticationException("系统异常,请联系客服反馈"));// 结束return null;}// 从请求中获取手机号,验证码已在SmsCodeCheckFilter中进行String phone = httpServletRequest.getParameter("phone");// 判断是否没传过来if (phone == null) {// 根据UsernamePasswordAuthenticationFilter的写法,这里赋值为空字符phone = "";}// 如果不为null,去掉前后空格phone = phone.trim();// 封装成为一个token对象,这里我们不能使用它原有的,因为他是账号和密码进行的校验,我们只有手机号// 所以我们自定义一个自己的,这里转到SmsAuthenticationToken,完成类的设计SmsAuthenticationToken smsAuthenticationToken = new SmsAuthenticationToken(phone);// 设置详细信息this.setDetails(httpServletRequest, smsAuthenticationToken);// 调用下一级,也就是认证管理,他是负责调用认证服务的,我们不用管,我们只需要修改认证服务就可以了return this.getAuthenticationManager().authenticate(smsAuthenticationToken);}/*** 这里对接收的token类进行了修改* 由UsernamePasswordAuthenticationToken改为SmsAuthenticationToken* @param request* @param authRequest*/protected void setDetails(HttpServletRequest request, SmsAuthenticationToken authRequest) {authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));}
}

并且AbstractAuthenticationManager会封装成UsernamePasswordAuthenticationToken(其中包含了用户名和密码),而我们的手机验证码是免密登录是没有密码的,所以这里我们需要自定义我们自己的UsernamePasswordAuthenticationToken。

第三步,自定义UsernamePasswordAuthenticationToken

/*** 第三步,设计一个我们自己需要的类* 这里是根据UsernamePasswordAuthenticationToken来修改的*/
public class SmsAuthenticationToken extends AbstractAuthenticationToken {private static final long serialVersionUID = 500L;private final Object phone;/*** 认证完成之前,因为还没有权限* 没有密码* @param phone*/public SmsAuthenticationToken(Object phone) {// 传了空权限super((Collection)null);// 设置手机号this.phone = phone;// 是否认证,这是一个标志,第一次肯定是没有认证的this.setAuthenticated(false);}/*** 这里是认证后,因为已经获取到权限了* @param phone* @param authorities*/public SmsAuthenticationToken(Object phone, Collection<? extends GrantedAuthority> authorities) {// 权限列表super(authorities);// 设置手机号this.phone = phone;// 这个时候标志为true,已经认证过了super.setAuthenticated(true);}/*** 凭证:这里是密码* @return*/@Overridepublic Object getCredentials() {return this.phone;}/*** 主要的:这里是账号* @return*/@Overridepublic Object getPrincipal() {return this.phone;}
}

AuthenticationManager会调用DaoAuthenticationProvider(AuthenticationProvider)去进行认证,DaoAuthenticationProvider通过调用UserDetailsService从数据库中获取用户信息(UserDetails),之后使用passwordEncoder密码编码器对UsernamePasswordAuthenticationToken(我们已重写,并去除密码)中的密码和数据库中UserDetails(数据库中只有手机号,也没有密码)的密码进行比较。当认证成功后会封装成一个Authentication(包含用户名,密码,权限等)。

第四步,自定义认证比对规则

/*** 第四步,自定义认证服务,它的认证服务调用的是passwordEncoder* 判断的是密码。而我们没有密码,我们无需判断密码,只需要将对象查出来证明有就可以了* 所以我们自己写一个这样的类*/
public class SmsDaoAuthenticationProvider extends DaoAuthenticationProvider {private UserDetailsService userDetailsService;/*** 这个方法必须要实现,如果该类直接实现AbstractUserDetailsAuthenticationProvider* 就不需要写这个方法了,这个方法是用来校验密码的,不过我们不需要,我们没有密码* 这里我把它清空,为了防止不必要的麻烦* @param userDetails* @param authentication* @throws AuthenticationException*/@Overrideprotected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {return;}/*** 核心方法,做认证处理* @param authentication* @return* @throws AuthenticationException*/@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {// 断言判断这个类Assert.isInstanceOf(SmsAuthenticationToken.class, authentication, "只支持的类型为:SmsAuthenticationToken,所传入的类型为:" + authentication.getClass().getSimpleName());// 取出手机号String phone = (String) authentication.getPrincipal();// 断言手机号Assert.hasText(phone, "没有该手机号");// 不从缓存中拿,我们从数据库中获取// 这里是把手机号当作用户名,用我们之前的查询语句UserDetails user = getUserDetailsService().loadUserByUsername(phone);// 原方法为: this.preAuthenticationChecks.check(user); 这里就不调用方法了,直接判断if (!user.isAccountNonLocked()) {throw new LockedException(this.messages.getMessage("AccountStatusUserDetailsChecker.locked", "User account is locked"));} else if (!user.isEnabled()) {throw new DisabledException(this.messages.getMessage("AccountStatusUserDetailsChecker.disabled", "User is disabled"));} else if (!user.isAccountNonExpired()) {throw new AccountExpiredException(this.messages.getMessage("AccountStatusUserDetailsChecker.expired", "User account has expired"));} else if (!user.isCredentialsNonExpired()) {throw new CredentialsExpiredException(this.messages.getMessage("AccountStatusUserDetailsChecker.credentialsExpired", "User credentials have expired"));}// 返回一个用户,意为已经认证过了return this.createSuccessAuthentication(phone, authentication, user);}protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {// 我们只有手机号和权限SmsAuthenticationToken result = new SmsAuthenticationToken(principal, user.getAuthorities());// 设置详细信息result.setDetails(authentication.getDetails());// 返回结果return result;}/*** 这里说的是支持的类,在AbstractUserDetailsAuthenticationProvider中* 它是这样的:* return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);* 意味着他自己支持UsernamePasswordAuthenticationToken,而我们自己定义了一个,叫SmsAuthenticationToken* 所以改为我们自己的* @param authentication* @return*/@Overridepublic boolean supports(Class<?> authentication) {return SmsAuthenticationToken.class.isAssignableFrom(authentication);}
}

当我们做完这些工作之后,Security的认证流程是不知道我们的这些类的,所以我们需要添加一个配置,告诉Security使用的是我们自定义的,而不是原始的类。

最后一步,自定义配置

// security的配置
@Configuration
@EnableWebSecurity // 开启Security
public class SmsAuthConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {@Autowiredprivate IVipUserService vipUserService;@Autowiredprivate MyAuthenticationFailureHandler myAuthenticationFailureHandler;@Autowiredprivate MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;@Overridepublic void configure(HttpSecurity http) throws Exception {// 设置入口大门的认证失败处理// 创建拦截类SmsAuthenticationFilter smsAuthenticationFilter = new SmsAuthenticationFilter();// 成功的处理smsAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);// 失败的处理smsAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);// 设置认证管理类。由于我们自定义,现在指定一个原有的认证管理类smsAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));// 设置dao层的DetailsService为我们自定义的SmsDaoAuthenticationProvider smsDaoAuthenticationProvider = new SmsDaoAuthenticationProvider();smsDaoAuthenticationProvider.setUserDetailsService(vipUserService);// 添加短信认证providerhttp.authenticationProvider(smsDaoAuthenticationProvider);// 添加短信认证filterhttp.addFilterAfter(smsAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}
}


												

Security实现手机验证码注册登录相关推荐

  1. springboot整合redis之用户手机验证码注册登录

    目录 1搭建项目开发环境 1.1安装redis 1.1.1下载redis 1.1.2安装redis 1.1.3设置redis为windows服务 安装为windows服务 1.2启动idea 1.3增 ...

  2. php 短信验证登录,短信验证码注册登录的实现,php接入的3种方法(附示例)

    上周,有朋友需要帮忙做一个关于手机短信验证码注册登录的功能,之前没有做过,于是我查查资料,汇总出PHP接入短信验证码的3种方法,现在和大家分享: 1.cURL $curl = curl_init(); ...

  3. 手机验证码+Redis登录验证+token+登录拦截

    手机验证码+Redis登录验证+token+登录拦截 文章目录 手机验证码+Redis登录验证+token+登录拦截 解决方案 思想 以阿里云为例 1.阿里云官网开通短信服务 2.创建签名 3.创建短 ...

  4. 手机验证码注册,登录

    //前端登录页面表单及javascript+ajax传输 <input type="text" name="nickname"  placeholder= ...

  5. Python3版本Django实现免费手机验证码注册

    Demo代码已放上GitHub,实现登录短信校验+极验验证 https://github.com/ChenJhua/TestLogin 打开网站互亿无线注册一个账号,有50条免费短信 登录进去后会有以 ...

  6. AXURE手机版注册登录原型(下载+教学)

    今天给大家分享一套APP注册/登录界面模板,其中包括本机登录页面,短信验证登录页面,密码登录页面,人脸登录页面,微博.微信.QQ.支付宝登录页面,注册页面,用户协议和隐私条款.该原型使用简单,交互完善 ...

  7. Day 5-6 阿里云手机验证码及登录代码

    短信服务 我们需要在阿里云官网进行注册登录 阿里云短信服务(Short Message Service)是广大企业客户快速触达手机用户所优选使用的通信能力.调用API或用群发助手,即可发送验证码.通知 ...

  8. 使用阿里云短信验证码API发送短信验证码(配置,获取短信验证码,注册,登录,密码重置)

    获取阿里云短信验证码需要的配置信息. 如果是新用户,可以免费领取3个月,老用户的话就只能购买了,但是也不贵. 申请短信签名 申请短信模板 编写发送短信验证码的工具类 代码中我已经进行了详细的注释,也写 ...

  9. 使用容联云通讯实现手机验证码注册

    添加Pom文件以及下lib下添加jar包 添加main方法测试,发送手机验证码 package javamaildemo;import java.util.HashMap; import java.u ...

最新文章

  1. ‘numpy.float64‘ object is not callable
  2. 用 Python 做数据处理必看:12 个使效率倍增的 Pandas 技巧(下)
  3. 1365 浴火银河星际跳跃 (并查集)
  4. (React 框架)React技术
  5. vue 关于solt得用法
  6. java 编译器重排序_Java编译器重新排序
  7. 湖北工业大学计算机学院王泽建,“指尖年轮,感恩成长”计算机学院2019届毕业生晚会圆满举行...
  8. java博弈,人机博弈小游戏(Java)
  9. 可并堆试水--BZOJ1367: [Baltic2004]sequence
  10. zookeeper资料
  11. 【读书笔记】iOS-访问网络
  12. SPSS22.0简体中文破解版(32位/64位)使用方法
  13. C语言复变函数PPT,C语言中如何应用复变函数
  14. EINT、DINT、ERTM、DRTM和EALLOW、EDIS、ESTOP0解析
  15. 【JAVA SE基础篇】29.初识数组
  16. Navicat怎样导入Excel表格和txt文本的数据
  17. SQL-Server 零基础入门教程[下]
  18. hive 已知日期计算是周几
  19. 笔记本控制台开启热点
  20. java生成AES秘钥

热门文章

  1. Android TV Leanback (八)(引导步骤)
  2. 华为OJ——名字的漂亮度
  3. 史上最煽情的博士论文致谢词
  4. 手把手教你做音乐播放器(八)桌面小工具(下)(完)
  5. 数据库之——sqlite下载及使用
  6. python之字符串替换
  7. 别再问了!考思科认证还是华为认证?看完你就知道了
  8. 新课标下的小学语文教育教学方法初探
  9. 全局前置守卫--路由拦截
  10. sql 语句as用法