SAAS-HRM-短信验证-单点登录

本文详细介绍了短信验证码的发送流程与实现细节,包括选择三方服务、接口封装及调用,以及验证码的有效性检查。同时,深入探讨了单点登录的概念、必要性和实现方案,包括登录拦截、权限校验和用户信息存储。

一 短信验证码

短信验证流程

img

1)方案选择

方案1:对接三大运营商接口

​ 如果量少,三大运营商不屌您。

方案2:三方服务

​ 一些有短信服务商,它们去对接三大运行商,封装为自己的接口。我们对接它们就ok。它们赚取差价。

​ 先使用三方服务,等运营后,量大了,再找三大运营商对接。

  1. 选择三方服务商

阿里大于

腾讯

华为

中国网建 http://www.smschinese.cn/ 5条是免费的

1)中国网建

img
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:自己设计,直接写(知其然亦知其所以然

查看新增的)

img

实现步骤:

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);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值