一 短信验证码
短信验证流程

1)方案选择
方案1:对接三大运营商接口
如果量少,三大运营商不屌您。
方案2:三方服务
一些有短信服务商,它们去对接三大运行商,封装为自己的接口。我们对接它们就ok。它们赚取差价。
先使用三方服务,等运营后,量大了,再找三大运营商对接。
- 选择三方服务商
阿里大于
腾讯
华为
中国网建 http://www.smschinese.cn/ 5条是免费的
1)中国网建

2)注册,提交短信签名
3)浏览器模拟
4)封装接口 Map不能传递
写短信发送接口-commonservice
Feign
@FeignClient(value = "HRM-COMMON", fallbackFactory = SmsClientFallbackFactory.class )//服务提供
@RequestMapping("/sms")
public interface SmsClient {
//feign如果要传递Map list等需要特殊处理
@PostMapping
AjaxResult send(@RequestParam String params);
}
SmsController
public class SmsController {
@Autowired
private ISmsService smsService;
//1 以下两个放到常量类中
//Uid=本站用户名
//Key=接口安全秘钥
//2 以下两个是动态传入
// smsMob=手机号码&smsText=验证码:8888
@PostMapping
AjaxResult send(@RequestParam String params){
System.out.println(params);
Map<String,String> paramsTmp =
JSONObject.parseObject(params, Map.class);
//封装参数
paramsTmp.put("Uid", SmsContants.SMS_UID);
paramsTmp.put("Key", SmsContants.SMS_KEY);
return smsService.sendSmsCode(paramsTmp);
}
}
Smsservice
//参数可变
@Override
public AjaxResult sendSmsCode(Map<String, String> params) {
PostMethod post = null;
try {
HttpClient client = new HttpClient();
post = new PostMethod("http://utf8.api.smschinese.cn");
post.addRequestHeader("Content-Type",
"application/x-www-form-urlencoded;charset=utf-8");//在头文件中设置转码
//动态参数 //map===>list====>array
List<NameValuePair> datas = new ArrayList<>();
for (String key : params.keySet()) {
datas.add( new NameValuePair(key, params.get(key) ));
}
NameValuePair[] data = datas.toArray(new NameValuePair[]{});
post.setRequestBody(data);
client.executeMethod(post);
Header[] headers = post.getResponseHeaders();
int statusCode = post.getStatusCode(); //400 404 200 500
String result = new String(post.getResponseBodyAsString().getBytes("utf-8"));
System.out.println("statusCode:"+statusCode);
System.out.println(result); //打印返回消息状态
if (statusCode==200){
//对各种错误码进行处理
return AjaxResult.me().setMessage(result);
}else{
return AjaxResult.me().setSuccess(false).setMessage(result);
}
}catch (Exception e){
e.printStackTrace();
}
finally {
if (post != null) {
post.releaseConnection();
}
}
return null;
}
调用短信服务
@Override
public AjaxResult sendSmsCode(Map<String, String> params) {
// 一 接收参数
//手机号
String mobile = params.get("mobile");
//图片验证码
String imageCode = params.get("imageCode");
//图片验证码的key
String imageCodeKey = params.get("imageCodeKey");
// 二 做检验
//2.1 图片验证 通过key从redis获取存放验证码和输入的进行对比
String imgCodeforReids = redisClient.get(imageCodeKey);
if (imgCodeforReids==null || !imageCode.equals(imgCodeforReids)){
return AjaxResult.me().setSuccess(false).setMessage("图片验证码不正确!");
}
//2.2 手机号的验证-判null,是否已经注册了
if (mobile==null){
return AjaxResult.me().setSuccess(false).setMessage("请输入正确的手机号码");
}
List<Sso> ssos = ssoMapper
.selectList(new EntityWrapper<Sso>().eq("phone", mobile));
if (ssos!=null && ssos.size()>0){
return AjaxResult.me().setSuccess(false).setMessage("你的手机号已经注册了,可以直接使用!");
}
// 三 发送短信验证码
return sendSmsCode(mobile);
}
private AjaxResult sendSmsCode(String mobile) {
String smsKey = "SMS_CODE:"+mobile;
// 一 先产生一个验证码
String smsCode = StrUtils.getComplexRandomString(4); //面条 饭
//二 如果原来验证码有效,替换生成哪个
String smsCodeForRedis= redisClient.get(smsKey); //smsCode格式 code:time
if (StringUtils.hasLength(smsCodeForRedis)){
//2如果有效,判断是否已经过了重发时间,如果没有过就报错
String[] split = smsCodeForRedis.split(":");
String timeStr = split[1];
long time = System.currentTimeMillis()-Long.valueOf(timeStr);
if (time<1000*60){
return AjaxResult.me().setSuccess(false).setMessage("请不要重复发送短信验证码!");
}
//如果过了,替换原来的验证码
smsCode = split[0];
}
//三 存储到redis
redisClient.addForTime(smsKey,smsCode+":"+System.currentTimeMillis(),60*5);//5分钟过期
//四 都要进行发送
System.out.println("[源码科技]已经发送验证码"+smsCode+"到用户手机:"+mobile);
Map<String,String> params = new HashMap<>();
//smsMob=手机号码&smsText=验证码:8888
params.put("smsMob",mobile);
params.put("smsText","验证码为:"+smsCode+",请在5分钟之内使用!");
smsClient.send(JSONObject.toJSONString(params));
return AjaxResult.me();
}
前台
sendSmsCode(event){
//1.判断手机号不为空
if(!this.formParams.mobile){
alert("手机号不能为空");
return;
}
//2.判断图片验证码不为空
if(!this.formParams.imageCode){
alert("图片验证码不能为空");
return;
}
//3.获取按钮,禁用按钮
var sendBtn = $(event.target);
sendBtn.attr("disabled",true);
var param = {
mobile: this.formParams.mobile,
imageCode:this.formParams.imageCode,
imageCodeKey:localStorage.getItem("imageCodeKey")
};
//4.发送ajax请求
this.$http.post("/user/verifycode/sendSmsCode",param).then(res=>{
var ajaxResult = res.data;
if(ajaxResult.success){
alert("手机验证码已经发送到您的手机,请在5分钟内使用");
//4.1.发送成:倒计时
var time = 60;
var interval = window.setInterval( function () {
//每一条倒计时减一
time = time - 1 ;
//把倒计时时间搞到按钮上
sendBtn.html(time+"秒后重发");
//4.2.倒计时完成恢复按钮
if(time <= 0){
sendBtn.html("重新发送");
sendBtn.attr("disabled",false);
//清除定时器
window.clearInterval(interval);
}
},1000);
}else{
//4.3.发送失败:提示,恢复按钮
sendBtn.attr("disabled",false);
alert("发送失败:"+ajaxResult.message);
}
})
}
二 注册
1)校验
2)保存sso-密码的加密,加盐
String salt = StrUtils.getComplexRandomString(32);
sso.setSalt(salt);
//使用随机验证给密码md5加密,并设置
//输入密码+以后做校验的时候先从数据库查询盐=md5,再和数据库查询出来进行比较
String md5Password = MD5.getMD5(password + salt);
sso.setPassword(md5Password);
3)保存vipBase
前台
submitRegister(){
//4.发送ajax请求
this.$http.post("/user/sso/register",this.formParams).then(res=>{
var ajaxResult = res.data;
if(ajaxResult.success){
alert("注册成功");
window.location.href = "http://127.0.0.1:6003/login.html";
}else{
alert("发送失败:"+ajaxResult.message);
}
})
}
Cotroller
//注册
@PostMapping("/register")
/**
* mobile:'13330964748',
password:'123456',
imageCode:'',
smsCode:''
==============Map接口---不是通过feign,通过zuul转发过来外部请求
*/
public AjaxResult register(@RequestBody Map<String,String> params)
{
return ssoService.register(params);
}
service
@Override
public AjaxResult register(Map<String, String> params) {
String mobile = params.get("mobile");
String password = params.get("password");
String smsCode = params.get("smsCode");
//1 校验
AjaxResult ajaxResult = validateParam(mobile,password,smsCode);
if (!ajaxResult.isSuccess())
return ajaxResult;
// 2 保存sso信息
Sso sso = new Sso();
sso.setPhone(mobile);
//获取随机验证-32位随机字符串
String salt = StrUtils.getComplexRandomString(32);
sso.setSalt(salt);
//使用随机验证给密码md5加密,并设置
//输入密码+以后做校验的时候先从数据库查询盐=md5,再和数据库查询出来进行比较
String md5Password = MD5.getMD5(password + salt);
sso.setPassword(md5Password);
sso.setNickName(mobile);
sso.setSecLevel(0);
sso.setBitState(7L); // 二进制位来表示 2个小时(扩展)
sso.setCreateTime(System.currentTimeMillis());
ssoMapper.insert(sso);
// 3 保存关联对象信息vipbase
VipBase vipBase = new VipBase();
vipBase.setCreateTime(System.currentTimeMillis());
vipBase.setSsoId(sso.getId());
vipBase.setRegChannel(1);
vipBase.setRegTime(System.currentTimeMillis());
vipBaseMapper.insert(vipBase);
return AjaxResult.me();
}
private AjaxResult validateParam(String mobile,String password,String smsCode){
// 手机号密码null校验
if (!StringUtils.hasLength(mobile) || !StringUtils.hasLength(password))
return AjaxResult.me().setSuccess(false).setMessage("请输入用户名或密码!");
// 手机号时候已经注册
List<Sso> ssoList = ssoMapper.selectList(new EntityWrapper<Sso>().eq("phone", mobile));
if (ssoList!=null&&ssoList.size()>0)
return AjaxResult.me().setSuccess(false).setMessage("您已经注册过了!可以直接使用!");
// 短信验证码校验 通过key从redis获取
String smsCodeStr = redisClient.get("SMS_CODE:" + mobile); // code:time
String smsCodeOfRedis = smsCodeStr.split(":")[0];
if (!smsCodeOfRedis.equals(smsCode))
return AjaxResult.me().setSuccess(false).setMessage("请输入正确短信验证码");
return AjaxResult.me();
}
三 单点登录
1)什么是单点登录
在多个应用系统中,一个系统登录了就能访问互相信任的系统.
为什么要用单点登录
我们有多个前端站点,有多个站点是需要登录才能够访问的,不可能所有站点都要写一个登录,需要一个站点登录了其他站点就不需要登录了.
2) 我们的项目需求
众多需要登录的前端站点,也需要做单点登录
3) 方案设计
方案1:依赖于一些权限框架 shiro security
方案2:用一个单点登录框架 cas
方案3:自己设计,直接写(知其然亦知其所以然
查看新增的)

实现步骤:
1)zuul拦截-登录,权限
2)后台提供接口-存放redis返回access-token
3)前台的实现
zuul拦截
package com.zhanglin.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
@Component
public class LoginFilter extends ZuulFilter {
@Override
public String filterType() {
// 登录校验,肯定是在前置拦截
return "pre";
}
@Override
public int filterOrder() {
// 顺序设置为1
return 1;
}
//登录放行
@Override
public boolean shouldFilter() {
// 返回true,代表过滤器生效。
return true;
}
@Override
public Object run() throws ZuulException {
// 登录校验逻辑。
// 1)获取Zuul提供的请求上下文对象
RequestContext ctx = RequestContext.getCurrentContext();
// 2) 从上下文中获取request对象
HttpServletRequest req = ctx.getRequest();
//对登录放行
String requestURI = req.getRequestURI();
if (requestURI.contains("login"))
return null;
if (requestURI.contains("api-docs"))
return null;
// 3) 从请求中获取token
String token = req.getHeader("access-token");
// 4) 判断
if(token == null || "".equals(token.trim())){
// 没有token,登录校验失败,拦截
ctx.setSendZuulResponse(false);
// 返回401状态码。也可以考虑重定向到登录页。
ctx.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
// 校验通过,可以考虑把用户信息放入上下文,继续向后执行
return null;
}
}
SsoController
//登录
@PostMapping("/login")
/**
* phone:'13330964748',
password:'123456',
*/
public AjaxResult login(@RequestBody Sso sso){
return ssoService.login(sso);
}
SsoService
@Override
public AjaxResult login(Sso sso) {
//校验用户是否存在,状态ok,是否已经过期等待
if (!StringUtils.hasLength(sso.getPhone()) || !StringUtils.hasLength(sso.getPassword()))
return AjaxResult.me().setSuccess(false).setMessage("请输入用户名或密码!");
List<Sso> ssoList = ssoMapper.selectList(new EntityWrapper<Sso>()
.eq("phone", sso.getPhone()));
if (ssoList==null || ssoList.size()<1)
return AjaxResult.me().setSuccess(false).setMessage("用户不存在,请注册后再来登录!");
//从数据库查询sso
Sso ssoExsit = ssoList.get(0);
//进行密码比对-输入密码+数据库盐值=md5再和数据库密码比对
System.out.println(sso.getPassword());
System.out.println(ssoExsit.getSalt());
String md5Pwd = MD5.getMD5(sso.getPassword() + ssoExsit.getSalt());
System.out.println(md5Pwd);
if (!ssoExsit.getPassword().equals(md5Pwd)){
return AjaxResult.me().setSuccess(false).setMessage("请输入正确的用户名或密码!");
}
//用户存到redis并且返回token-60*30
String accessToken = UUID.randomUUID().toString();
redisClient.addForTime(accessToken, JSONObject.toJSONString(ssoExsit),30*60);
return AjaxResult.me().setResultObj(accessToken);
}
public static void main(String[] args) {
String md5 = MD5.getMD5("1" + "szYCdrDehOmArvMU49htAIqgKWIVSTkT");
System.out.println(md5);
}
本文详细介绍了短信验证码的发送流程与实现细节,包括选择三方服务、接口封装及调用,以及验证码的有效性检查。同时,深入探讨了单点登录的概念、必要性和实现方案,包括登录拦截、权限校验和用户信息存储。
496

被折叠的 条评论
为什么被折叠?



