springboot +spring security4 自定义手机号码+短信验证码登录
spring security 默认登录方式都是用户名+密码登录,项目中使用手机+ 短信验证码登录, 没办法,只能实现修改:
需要修改的地方:
1 、自定义 AuthenticationProvider 配置:
package com.ycmedia.security; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSONObject; import com.ycmedia.constants.Constants; import com.ycmedia.entity.Customer; import com.ycmedia.entity.Role; import com.ycmedia.service.UserService; import com.ycmedia.utils.HttpRequest; /** * @author 自定义验证 * */ @Component public class YcAnthencationProder implements AuthenticationProvider { @Autowired private UserService userService; BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); @Autowired private Environment env; @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // CustomWebAuthenticationDetails details = (CustomWebAuthenticationDetails) authentication // .getDetails(); // 如上面的介绍,这里通过authentication.getDetails()获取详细信息 // 用户名 String username = authentication.getName(); // 验证码 String password = (String) authentication.getCredentials(); Customer user = userService.getUserByname(username); List<SimpleGrantedAuthority> auths = new ArrayList<>(); //游客=》提示用户去注册 if(user==null){ //授权 auths.add(new SimpleGrantedAuthority(Role.ROLE_TOURIST.toString())); auths.add(new SimpleGrantedAuthority(username)); auths.add(new SimpleGrantedAuthority(password)); return new UsernamePasswordAuthenticationToken(new Customer(), password, auths); }else{ //存在此用户,调用登录接口 String data = HttpRequest.sendGet(env.getProperty("login.url"), "mobile=" + username+"&smsCode="+password); JSONObject json = JSONObject.parseObject(data); if(json.getBoolean("success")==true){ //验证码和手机号码正确,返回用户权限 switch(user.getRole()){ case 0:auths.add(new SimpleGrantedAuthority(Role.ROLE_USER.toString())); case 1:auths.add(new SimpleGrantedAuthority(Role.ROLE_CHANNEL.toString())); case 2:auths.add(new SimpleGrantedAuthority(Role.ROLE_ADMIN.toString())); } }else{ //验证消息放到权限里面, 页面提示 auths.add(new SimpleGrantedAuthority(Role.ROLE_WRONGCODE.toString())); auths.add(new SimpleGrantedAuthority(username)); auths.add(new SimpleGrantedAuthority(password)); } } return new UsernamePasswordAuthenticationToken(user, password, auths); } @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }
==================这里把验证码当成password
security 安全中添加:
之前一直跑不通, 是因为在config 中引入我自己的 provider, 结果代码跳不进去, 最后用spring 默认的provier 放进去,才可以。到现在还不明白
============================================================================
因为springsecurity 授权 顺序是 1 :调用AuthenticationProvider 获取Authentication 这个类存了用户名, 密码, 权限, 是否过期 锁定等、然后才会调用 你的 controller 中的login方法,
@SuppressWarnings("unchecked") @RequestMapping(value = { "/login", "/" }) @ResponseBody public ModelAndView login(Model model) { try { Authentication auth = SecurityContextHolder.getContext() .getAuthentication(); List<SimpleGrantedAuthority> auths=(List<SimpleGrantedAuthority>) auth.getAuthorities(); //用户存在。验证码错误,返回登录页面,提示文字 if(auths.get(0).getAuthority().equals(Role.ROLE_WRONGCODE.toString())){ model.addAttribute("msg", "验证码不正确"); model.addAttribute("mobile", auths.get(1).getAuthority()); model.addAttribute("code", auths.get(2).getAuthority()); model.addAttribute("status", 1); return new ModelAndView("login"); } //用户不存在。跳转到登录页面,提示文字 else if(auths.get(0).getAuthority().equals(Role.ROLE_TOURIST.toString())){ model.addAttribute("msg", "该用户不存在"); model.addAttribute("mobile", auths.get(1).getAuthority()); model.addAttribute("code", auths.get(2).getAuthority()); if(auths.get(1).getAuthority()!=null&&auths.get(2).getAuthority()!=null){ model.addAttribute("status", 2); } return new ModelAndView("login"); } if (auth instanceof AnonymousAuthenticationToken) { return new ModelAndView("login"); } else { // 获取用户登录权限详细 Object pinciba = auth.getPrincipal(); if (pinciba instanceof UserDetails) { UserDetails userDetail = ((UserDetails) pinciba); model.addAttribute("username", userDetail.getUsername()); Customer u = userService .getUserByname(userDetail.getUsername()); // 用户角色 model.addAttribute("role", u.getRole()); String today = new DateTime().toString("yyyy-MM-dd"); // 今天收益 Double todayIncome = indexService.getTodayIncome(today); // 今天总充值金额 Double buyInCome = indexService.getBuyIncome(today); // 今天新增用户数 Integer dayCount = indexService.findDayCount(today); // 今天累计用户数 Integer dayAllCount = indexService.findDayAllCount(today); model.addAttribute("todayIncome", todayIncome == null ? 0.00 + "元" : todayIncome + "元"); model.addAttribute("buyInCome", buyInCome == null ? 0.00 + "元" : buyInCome + "元"); model.addAttribute("dayCount", dayCount == null ? 0 + "人" : dayCount + "人"); model.addAttribute("dayAllCount", dayAllCount + "人"); } // 登录成功跳到主页 return new ModelAndView("home"); } } catch (Exception e) { e.printStackTrace(); return new ModelAndView("login"); } }
===========================开发过程中发现一个问题, 就是springsecurity 的异常怎么在页面接受, 百度了很多资料, 说用EL表达式 ${SPRING_SECURITY_LAST_EXCEPTION.message} 获取, 可惜我不能获取
主要是因为登录的时候用form 有两种 提交方式:
1 form提交, 按钮为submit , 这是springsecurity 默认配置, 就会先去调用 AuthenticationProvider 获取权限再 调login方法, 但是回参不好在返回到页面,
2 ajax 提交, 但是不走 AuthenticationProvider ,
最后我把 错误信息放到 Authentication 。 它 会带到我的controller 里面, 然后 返回个新页面, 再 把 参数带过去:
以下 是html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/> <meta name="description" content=""/> <meta name="author" content="ThemeBucket"/> <link rel="shortcut icon" href="#" type="image/png"/> <title>全民发布</title> <link href="/bootstrap/css/loginstyle.css" rel="stylesheet"/> <link href="/bootstrap/css/style-responsive.css" rel="stylesheet"/> <script src="/bootstrap/js/modernizr.min.js"></script> <link rel="stylesheet" href="/bootstrap/css/font-awesome.min.css"/> <script src="/plugins/jQuery/jquery-2.2.3.min.js"></script> <script src="/bootstrap/js/bootstrap.min.js"></script> <link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css"/> <link href="/bootstrap/css/bootstrapValidator.min.css" rel="stylesheet"/> <script src="/bootstrap/js/bootstrapValidator.js"></script> </head> <body class="login-body"> <div class="container"> <form class="form-signin" th:action="@{/login}" method="post" id="loginForm"> <input type="hidden" th:value="${mobile}" id="mobile"/> <input type="hidden" th:value="${msg}" id="msg"/> <input type="hidden" th:value="${code}" id="code"/> <div class="form-signin-heading text-center"> <h1 class="sign-title">登录</h1> <img src="http://apps.ycmedia.cn/qm/img/lable/120x80/00.png?ver=1" alt=""/> </div> <div class="login-wrap"> <input id="username" name="username" type="text" placeholder="手机号码" class="form-control" th:value="${mobile}"/> <span id="telephonenameTip"></span> <div class="row" th:if="${status==2}"> <font color="red" th:text="${msg}" style="margin-left: 17px;"></font> </div> <button type="button" class="btn btn-info" style="margin-left: 82%;margin-bottom: 12px;" id="getcode" >发送</button> <input id="" name="password" type="text" placeholder="验证码" class="form-control" th:value="${code}"/> <div class="row" th:if="${status==1}"> <font color="red" th:text="${msg}" style="margin-left: 17px;"></font> </div> <button class="btn btn-lg btn-login btn-block" type="submit"> <i class="fa fa-check"></i> </button> <div class="registration"> 还不是一个用户? <a class="" th:href="@{/registration}"> 注册 </a> </div> <label class="checkbox"> <input type="checkbox" value="remember-me"/> 记住我 <span class="pull-right"> <a data-toggle="modal" href="#myModal"> 忘记密码?</a> </span> </label> </div> <!-- Modal --> <div aria-hidden="true" aria-labelledby="myModalLabel" role="dialog" tabindex="-1" id="myModal" class="modal fade"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> <h4 class="modal-title">Forgot Password ?</h4> </div> <div class="modal-body"> <p>Enter your e-mail address below to reset your password.</p> <input type="text" name="email" placeholder="Email" autocomplete="off" class="form-control placeholder-no-fix"/> </div> <div class="modal-footer"> <button data-dismiss="modal" class="btn btn-default" type="button">Cancel</button> <button class="btn btn-primary" type="button">Submit</button> </div> </div> </div> </div> <!-- modal --> </form> </div> <script type="text/javascript"> window.onload = function () { $(':input','#loginForm') .not(':button, :submit, :reset, :hidden') .val('') .removeAttr('checked') .removeAttr('selected'); //短信验证码 var InterValObj; //timer变量,控制时间 var count = 60; //间隔函数,1秒执行 var curCount;//当前剩余秒数 var code = ""; //验证码 var codeLength = 6;//验证码长度 $("#getcode").click(function () { //获取输入的手机号码 var phoNum = $("#username").val(); var regrep=/^1[3|4|5|7|8][0-9]{9}$/; if(!regrep.test(phoNum)){ $("#telephonenameTip").html('<font color="red">手机号码格式不正确 </font>'); return; } curCount = count; // 设置按钮显示效果,倒计时 $("#getcode").attr("disabled", "true"); $("#getcode").val("请在" + curCount + "秒内输入验证码"); InterValObj = window.setInterval(SetRemainTime, 1000); // 启动计时器,1秒执行一次 // 向后台发送处理数据 $.ajax({ type: "get", // 用POST方式传输 dataType: "json", // 数据格式:JSON url: "http://testi.xf120.com/qm/sms/send?mobile="+phoNum, // 目标地址 error: function (msg) { $("#telephonenameTip").html('<font color="red">× 短信验证码发送失败,请重新发送 </font>'); alert(msg); }, success: function (data) { $("#telephonenameTip").html('<font color="green">√ 短信验证码已发到您的手机,请查收(15分钟内有效)</font>'); } }); }); //timer处理函数 function SetRemainTime() { if (curCount == 0) { window.clearInterval(InterValObj);// 停止计时器 $("#getcode").removeAttr("disabled");// 启用按钮 $("#getcode").val("重新发送验证码"); code = ""; // 清除验证码。如果不清除,过时间后,输入收到的验证码依然有效 } else { curCount--; $("#getcode").val("请在" + curCount + "秒内输入验证码"); } } } </script> </body>
</html>
===================================================== 大概搞定了=============以下是效果图