目录

  • 内容介绍
  • 1. 前台登录-账号登录
    • 1.1.前台登录
    • 1.2.axios携带token-common.js
    • 1.3.axios后置处理后台拦截错误-common.js
    • 1.4.前台登录拦截-common.js
    • 1.5.小结
  • 2.三方登录概述
    • 2.1.什么是三方登录
    • 2.2.为什么需要
    • 2.3.优缺点
    • 2.4.使用场景
  • 3.三方登录协议-OAuth2.0
    • 3.1.应用场景
    • 3.2.名词定义
    • 3.3.OAuth的思路
    • 3.4.运行流程
    • 3.5.客户端授权 - 授权码模式
  • 4.三方登录服务商选择-微信三方登录
    • 4.1.为什么选择微信作为三方登录服务提供方
    • 4.2.注册账号
      • 4.2.1.注册
      • 4.2.2.开发者认证
      • 4.2.3.创建网站应用
    • 4.3.网站微信登录原理
    • 4.4.配置回调域名
  • 5.微信三方登录项目实战
    • 5.1.设计
      • 5.1.1.数据库设计
      • 5.1.2.第三方登录流程分析
    • 5.2. 实现准备
      • 5.2.1.工具类的封装
      • 5.2.2.常量封装
      • 5.2.3.检查配置回调域名hosts配置
    • 5.3.核心代码实现
      • 5.3.1.跳转授权界面
      • 5.3.2.回调页面
      • 5.3.3.跳转后台微信登录
      • 5.3.4.绑定页面
      • 5.3.5.绑定实现
      • ~~5.3.6.后台获取登录用户~~
  • 6.课程总结
    • 6.1.重点
    • 6.2.难点
    • 6.3.如何掌握
    • 6.4.排错技巧(技巧)
  • 7.常见问题
  • 8.课后练习
  • 9.面试题
  • 10.扩展知识或课外阅读推荐(可选)
    • 10.1.扩展知识
    • 10.2.课外阅读

第三方登录

内容介绍

1. 用户登录-账号登录;(掌握)
2. 三方登录概述;(理解)
3. 三方登录协议-Oauth2.0;(了解)
4. 三方登录选择-微信三方登录;(掌握)
5. 微信三方登录实战;(掌握)

前台登录:
1)账号登录
2)三方登录
3)验证码登录 自己做
4)扫码登录-做不了

1. 前台登录-账号登录

1)后台登录接口
2)前台登录实现并且保存loginInfo和token到localStorage,登录成功跳转首页,并展示用户名
3)前台通过axios的前置拦截器携带token到后台
4)后台做token的登录拦截器,如果没有回报错给前台
5)前台通过axios后置拦截器对后台登录拦截错误进行跳转到登录页面
6)前台也要做拦截-有的地址是不需要访问后台

前提:login.html中集成axios和vue

<!--引入vue和axios-->
<script src="js/plugins/vue/dist/vue.js"></script>
<script src="js/plugins/axios/dist/axios.js"></script>
<!--全局配置axios-->
<script src="js/common.js"></script>
<script type="text/javascript">new Vue({el:".login-form",data:{},methods:{},mounted(){alert(this.$http)}});
</script>

和后台账号登录差不多
7)后台登录接口
8)前台登录实现并且保存uUser和uToken到localStorage
9)前台通过axios的前置拦截器携带token到后台
10)后台做token的登录拦截器,如果没有回报错给前台
11)前台通过axios后置拦截器对后台登录拦截错误进行跳转到登录页面
12)前台也要做拦截-有的地址是不需要访问后台

1.1.前台登录


暂时不依赖
两个不同的域名的localStorage不能直接互相访问。那么如何在aaa.com中如何调用bbb.com的localStorage?

login(){// alert(this.loginInfo)// console.log(this.loginInfo)this.$http.post("/login/account",this.loginInfo).then(result => {result = result.data; //{success,message,resultobj}console.log(result)if (result.success) {let {token, user} = result.resultObj;localStorage.setItem('uToken', token);alert(localStorage.getItem("token"));//JSON.stringify(user) 把对象转换为json字符串localStorage.setItem('uUser', JSON.stringify(user));//跳转到主页location.href = "/index.html"}else{alert(result.message)}}).catch(result => {alert("系统错误!")})
}

1.2.axios携带token-common.js

//==============axios前置拦截器实现每次携带token======================//
axios.interceptors.request.use(config=>{let token = localStorage.getItem("token");if(token){//携带tokenconfig.headers["token"] = token;}return config;
},error => {Promise.reject(error)
})
//==============axios前置拦截器实现每次携带token======================//

1.3.axios后置处理后台拦截错误-common.js

//==============axios后台拦截器实现后端已经退出登录的跳转登录页面======================//
axios.interceptors.response.use(config=>{let data = config.data;if(!data.success && "noLogin"===data.message){localStorage.removeItem("token");localStorage.removeItem("loginInfo");location.href = "/login.html";}return config;
},error => {Promise.reject(error)
})
//==============axios后台拦截器实现后端已经退出登录的跳转登录页面======================//

1.4.前台登录拦截-common.js

//前端的登录拦截-common.js会被所有的界面导入
// 判断localStorage中是否有logininfo
let url = location.href;
//不是登录且不是注册,就要拦截
if(url.indexOf("login.html")==-1 && url.indexOf("register.html")==-1)
{let loginInfo = localStorage.getItem("loginInfo");if(!loginInfo){location.href = "/login.html"}
}

1.5.小结

2.三方登录概述

2.1.什么是三方登录

用户可以在三方主流平台(微信,qq,支付宝。。)登录,然后自己平台就不需要登录了。基于用户在主流平台上已有的账号和密码来快速完成己方应用的登录或者注册的功能。而这里的主流平台,一般是已经拥有大量用户的平台,国外的比如Facebook,Twitter等,国内的比如微博、微信、QQ等。 第三方登录的目的是使用用户在其他平台上频繁使用的账号,来快速登内录己方产品,也可以实现不注容册就能登录,好处就是登录比较快捷,不用注册。

2.2.为什么需要

用户:少记忆账号了

2.3.优缺点

优点:这些系统有很大的用户群体;有这些大平台背景,用户的接收度和认可度较好;直接登录,避免用户注册和登录的繁琐过程。这样,可以比较好的推广自己的网站和粘主用户。
缺点:要交钱,除了三方登录还要实现其他的。

2.4.使用场景

面向互联网用户一般都需要三方登录。

3.三方登录协议-OAuth2.0

OAUTH协议为用户资源的授权提供了一个安全的、开放而又简易的标准。与以往的授权方式不同之处是OAUTH的授权不会使第三方触及到用户的帐号信息(如用户名与密码),即第三方无需使用用户的用户名与密码就可以申请获得该用户资源的授权,因此OAUTH是安全的。oAuth是Open Authorization的简写,目前的版本是2.0版。

https://oauth.net/2/

3.1.应用场景

为了让大家理解OAuth的适用场合,下面我先举一个栗子。
有一个“云打印”的网站,可以将用户存储在QQ的照片打印出来。用户为了使用该服务,必须让“云打印”读取自己存储在QQ上的照片。

问题是,只有得到用户的授权,QQ才同意“云打印”读取这些照片。那么“云打印”如何才能获得QQ用户的授权呢?有比较传统的方法,用户自己将QQ的用户名和密码告诉“云打印”,后者就可以读取用户的照片了。但是,这么做会有以下几个严重的缺点:

(1)“云打印”为了后续的服务,会保存用户的用户名和密码,这样很不安全。
(2)“云打印”拥有了获取用户在QQ上所有资料的权利,用户没有办法限制“云打印”的访问资       源的权限和有限期。
(3)用户只有修改密码,才能收回“云打印”的权限。但是这么做,会使得其他所有获得用户授权    的第三方应用程序全部失效。
(4)只要有一个第三方应用程序被破解,就会导致用户密码泄露,以及所有被这个密码保护的数据    也会泄露

OAuth就是为了解决上面这些问题而诞生的。 我们平台需要保存三方平台账号和密码

3.2.名词定义

在详细介绍OAuth2.0之前,需要了解几个专有名词,这些名词经常出现在各种应用场合,对于我们理解RCF 6749的内容至关重要。

(1)Third-party application:第三方应用程序,本文中又称"客户端"(client),即栗子中的"云打印"。
(2)HTTP service:HTTP服务提供商,本文中简称"服务提供商",即上一节例子中的QQ。
(3)Resource Owner:资源所有者,本文中又称"用户"(user)。
(4)User Agent:用户代理,本文中就是指浏览器。
(5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器

了解了上面的名词,就不难理解OAuth的作用就是让“客户端”安全可控的获取“用户”的授权,与“服务提供商”进行互动。

3.3.OAuth的思路

OAuth在"客户端"与"服务提供商"之间,设置了一个授权层(authorization layer)。“客户端"不能直接登录"服务提供商”,只能登录授权层,以此将用户与客户端区分开来。"客户端"登录授权层使用的是令牌(token),与用户的密码不同。用户可以在登录的时候,指定令牌的权限范围和有效期。

"客户端"登录授权层以后,"服务提供商"根据令牌的权限范围和有效期,向"客户端"开放用户储存的资料。

3.4.运行流程

OAuth 2.0的运行流程如下图,摘自RCF 6749。

用户授权
获取令牌
访问受限资源

(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。

上述六个步骤中,B是关键,即用户怎样才能给客户端授权。有了这个授权以后,客户端就可以获取令牌,进而凭借令牌获取资源。

3.5.客户端授权 - 授权码模式

客户端必须得到用户的授权,才能获得令牌,OAuth2.0定义了四种授权方式:
授权码模式(authorization code)
简化模式(implicit)
密码模式(resource owner password credentials)
客户端模式(client credentials)

这里,我们主要介绍一下授权码模式:
授权码模式是功能最完整,流程最严密的授权模式。它的特点是通过客户端的后台服务器,与“服务提供商”的认证服务器进行互动,先看下面一张图。

(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI",同时附上一个授权       码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后       台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和     更新令牌(refresh token)。

4.三方登录服务商选择-微信三方登录

4.1.为什么选择微信作为三方登录服务提供方

三方登录产品有很多,比如微信登录,qq登录,支付宝登录,新浪微博登录.但是都是遵循oauth2.0协议,只要学会一个其他就触类旁通.
微信,在做的各位都使用很频繁的社交软件,老少皆宜。今天我们以微信为例进行讲解。
微信开发平台供开发者可以基于微信做很多事情:
https://open.weixin.qq.com/

自己的网站可以接入网站应用开发,为用户提供了微信登录功能,降低了注册门槛,并可在用户授权后,获取用户基本信息,包括头像、昵称、性别、地区。出于安全考虑,网站应用的微信登录,需通过微信扫描二维码来实现。

4.2.注册账号

要想接入微信的登录功能,首先需要在微信开发平台进行用户的注册,同时需要认证为开发者,再创建网站应用,等待微信审批,审批过后,就可以使用相关功能。

4.2.1.注册

打开微信开发平台(https://open.weixin.qq.com/):

使用邮箱进行注册(最好使用企业邮箱,避免使用私人邮箱,后续需要企业的资质认证等,避免你离职后的交接麻烦):

4.2.2.开发者认证

注册完成后,需要进行开发者的认证:

登录后,点击登录名,进入:
大致需要个人身份证等真实信息,还需要企业签字盖章等流程,认证一次300人民币。

4.2.3.创建网站应用

认证成功后,创建网站应用,也需要企业签字盖章,还需要备案的域名,作为微信的回调。


创建完成后,获取到appid和appsecret,配置好回调域名。

4.3.网站微信登录原理

一切参照官方文档:

4.4.配置回调域名

上线:

开发阶段:
路由解析原理


6.4 配置电脑HOST文件
Host文件配置
127.0.0.1 bugtracker.itsource.cn

通过原理分析,要发三个请求
1)授权请求-a标签链接过去就OK
2)获取AccessToken等信息请求-Httpclient
在我们项目中以后这样用代码来发送请求的情况有可能很多,所以抽取一个工具类,以后专门发请求。

5.微信三方登录项目实战

5.1.设计

5.1.1.数据库设计


WxUser crud 自己做

5.1.2.第三方登录流程分析

代码实现步骤分析:微信扫描登录具体实现步骤1.用户点击主站微信登录按钮2.web端发送授权请求到微信平台   带上回调地址  http://bugtracker.itsource.cn/callback.html3.(微信端会弹出二维码扫码界面给用户)4.(用户扫码后,微信平台获得授权,生成code 绑定http://bugtracker.itsource.cn/callback.html?code=xxxxx)5.回调到callback页面,我们需要在页面加载完成,解析地址栏中的code等等信息6.发送微信登录请求到pethome后端  需要携带:code 和 绑定用户信息界面 binder.html 7.后端有controller接收微信登录请求 code binder.html通过code到微信平台获取token在通过token获取微信用户wxUser8.如果微信用户在我们平台有绑定用户,直接免密登录9.如果没有绑定我们平台的用户需要生成t_loginInfo\t_user-->WxUser免密登录微信用户绑定平台用户流程1.前端传phone 和 verifyCode过来  (token、openId)2.service层校验验证码是否过期3.(token、openId)去获取微信用户如果拿不到:微信用户不存在,直接抛错如果拿到:将微信用户绑定平台用户免密登录将微信用户绑定平台用户的流程:1.校验手机号曾经有没有在我们平台注册过注册过了  (直接绑定微信用户(自己初始化===通过上面查询出来的微信用户信息去初始化)就可以了  免密登录)直接查询出平台用户就OK了没有注册过封装LoginInfo封装User2.绑定WxUser和User免密登录

5.2. 实现准备

5.2.1.工具类的封装
package cn.itsource.basic.util;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpMethodParams;
import java.io.IOException;
/*** 使用httpclient组件发送http请求*   get:现在只用到get*   post*/
public class HttpClientUtils {/*** 发送get请求* @param url 请求地址* @return 返回内容 json*/public static String httpGet(String url){// 1 创建发起请求客户端try {HttpClient client = new HttpClient();// 2 创建要发起请求-tetGetMethod getMethod = new GetMethod(url);
//            getMethod.addRequestHeader("Content-Type",
//                    "application/x-www-form-urlencoded;charset=UTF-8");getMethod.getParams().setParameter(HttpMethodParams.HTTP_CONTENT_CHARSET,"utf8");// 3 通过客户端传入请求就可以发起请求,获取响应对象client.executeMethod(getMethod);// 4 提取响应json字符串返回String result = new String(getMethod.getResponseBodyAsString().getBytes("utf8"));return result;} catch (IOException e) {e.printStackTrace();}return null;}
}
5.2.2.常量封装
package cn.itsource.user.constant;//微信登录相关常量
public class WxConstants {public static final String APPID = "wxd853562a0548a7d0";public static final String SECRET = "4a5d5615f93f24bdba2ba8534642dbb6";public static final String GET_ACK_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code";public static final String GET_USER_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID";}
5.2.3.检查配置回调域名hosts配置

5.3.核心代码实现

<!--处理json--><!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.58</version></dependency>
5.3.1.跳转授权界面
Login.html
<script type="text/javascript">//创建vue是需要给他传递一个对象new Vue({el: "#loginMain", //id在hmtl模板中要对应data: { //数据模型loginForm: {username: '',password: ''},wxAuthUrl:' https://open.weixin.qq.com/connect/qrconnect?appid=wxd853562a0548a7d0' +
'&redirect_uri=http://bugtracker.itsource.cn/callback.html&response_type=code&scope=snsapi_login&state=1#wechat_redirect'},

5.3.2.回调页面
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>回调</title><!--集成vue和axios,还要axios全局配置(导入common.js)--><!--script src方式引入vue和axios--><script src="js/plugins/vue/dist/vue.js"></script><script src="js/plugins/axios/dist/axios.js"></script><!--全局配置,以后只要用vue+axios的页面都引入common.js--><script src="js/common.js"></script>
</head>
<body><div id="myDiv"></div><script type="text/javascript">new Vue({el:"#myDiv",mounted(){//解析参数对象let url = location.href;let paramObj = parseUrlParams2Obj(url);//获取发送请求参数let binderUrl = "http://bugtracker.itsource.cn/binder.html"let params = {"code":paramObj.code,"binderUrl":binderUrl};//发起微信登录请求this.$http.post("/login/wechat",params).then(result=>{result = result.data;if(result.success){ //已经关联了//做登录//提示alert("登录成功!")//把token和loginInfo存放到localStoragelet {token,loginInfo} = result.resultObj;localStorage.setItem("token",token);//把对象转换为json字符串存放
localStorage.setItem("loginInfo",JSON.stringify(loginInfo));//跳转主页location.href = "/index.html";}else{ //没有关联跳转关联页面let url = result.resultObj;location.href = url;}}).catch(result=>{alert("系统错误");console.log(result);})}});</script>
</body>
</html>
5.3.3.跳转后台微信登录
LoginInfocontroller 判断是否已经绑定,如果绑定就免密登录,否则返回一个未绑定错误,配合前端跳转到绑定页面
@PostMapping("/wechat")
public AjaxResult loginWechat(@RequestBody Map<String,String> params){try {return  loginInfoService.loginWechat(params);} catch (Exception e) {e.printStackTrace();return AjaxResult.me().setMessage("系统错误!"+e.getMessage());}
}

LoginInfoServiceImpl

 @Override
public AjaxResult loginWechat(Map<String, String> params) {//1 获取codeString code = params.get("code");String binderUrl = params.get("binderUrl");//2 获取accessTokenString getAckUrl = WxConstants.GET_ACK_URL.replace("APPID", WxConstants.APPID).replace("SECRET", WxConstants.SECRET).replace("CODE", code);String jsonStr = HttpClientUtils.httpGet(getAckUrl);JSONObject jsonObject = JSONObject.parseObject(jsonStr);String accessToken = jsonObject.getString("access_token");String openid = jsonObject.getString("openid"); //就相当于微信号//3 判断是否已经关联了WxUser wxUser = wxUserMapper.loadByOpenId(openid);if(wxUser!=null && wxUser.getUser_id()!=null){//查询LogininfoLoginInfo loginInfo =loginInfoMapper.loadByUserId(wxUser.getUser_id());//3.1 如果关联了实现免密登录String token = UUID.randomUUID().toString();redisTemplate.opsForValue().set(token,loginInfo,30, TimeUnit.MINUTES);Map<String,Object> result = new HashMap<>();result.put("token",token);loginInfo.setSalt(null);loginInfo.setPassword(null);result.put("loginInfo",loginInfo);return AjaxResult.me().setResultObj(result);}else{//3.2 否则跳转到绑定页面binderUrl = binderUrl+"?accessToken="+accessToken+"&openId="+openid;return AjaxResult.me().setSuccess(false).setResultObj(binderUrl);}
}
5.3.4.绑定页面
<!DOCTYPE html>
<html><head lang="en"><meta charset="UTF-8"><title>注册</title><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"><meta name="format-detection" content="telephone=no"><meta name="renderer" content="webkit"><meta http-equiv="Cache-Control" content="no-siteapp" /><link rel="stylesheet" href="./AmazeUI-2.4.2/assets/css/amazeui.min.css" /><link href="./css/dlstyle.css" rel="stylesheet" type="text/css"><script src="./AmazeUI-2.4.2/assets/js/jquery.min.js"></script><script src="./AmazeUI-2.4.2/assets/js/amazeui.min.js"></script><!--script src方式引入vue和axios--><script src="js/plugins/vue/dist/vue.js"></script><script src="js/plugins/axios/dist/axios.js"></script><!--全局配置,以后只要用vue+axios的页面都引入common.js--><script src="js/common.js"></script><!--一个一个页面配置,搞一个公共common.js,以后只需要引入它就ok--><!--<script type="text/javascript">--><!--//配置axios的全局基本路径--><!--axios.defaults.baseURL='http://localhost:8080/'--><!--// axios.defaults.baseURL='/api' //前端跨域配置--><!--//全局属性配置,在任意组件内可以使用this.$http获取axios对象--><!--Vue.prototype.$http = axios--><!--</script>--></head><body><div class="login-boxtitle"><a href="home/demo.html"><img alt="" src="./images/logobig.png" /></a></div><div class="res-banner"><div class="res-main"><div class="login-banner-bg"><span></span><img src="./images/big.jpg" /></div><div class="login-box"><div class="am-tabs" id="doc-my-tabs"><div class="am-tabs-bd"><div class="am-tab-panel am-active"id="myDiv" ><form method="post"><div class="user-phone"><label for="phone"><i class="am-icon-mobile-phone am-icon-md"></i></label><input type="tel" name="" id="phone" v-model="phoneUserForm.phone" placeholder="请输入手机号"></div>                                                          <div class="verification"><label for="code"><i class="am-icon-code-fork"></i></label><input type="tel" name="" id="code" v-model="phoneUserForm.verifyCode" placeholder="请输入验证码"><!--<a class="btn" href="javascript:void(0);" οnclick="sendMobileCode();" id="sendMobileCode">--><!--<span id="dyMobileButton">获取</span></a>--><button type="button" @click="sendMobileCode">获取</button></div></form><div class="login-links"><label for="reader-me"><input id="reader-me" type="checkbox"> 点击表示您同意商城《服务协议》</label></div><div class="am-cf"><input type="button" @click="binder" name="" value="绑定授权" class="am-btn am-btn-primary am-btn-sm am-fl"></div><hr></div><script>$(function() {$('#doc-my-tabs').tabs();})</script></div></div></div></div><div class="footer "><div class="footer-hd "><p><a href="# ">恒望科技</a><b>|</b><a href="# ">商城首页</a><b>|</b><a href="# ">支付宝</a><b>|</b><a href="# ">物流</a></p></div><div class="footer-bd "><p><a href="# ">关于恒望</a><a href="# ">合作伙伴</a><a href="# ">联系我们</a><a href="# ">网站地图</a><em>© 2015-2025 Hengwang.com 版权所有. 更多模板 <a href="http://www.cssmoban.com/" target="_blank" title="模板之家">模板之家</a> - Collect from <a href="http://www.cssmoban.com/" title="网页模板" target="_blank">网页模板</a></em></p></div></div></body><script type="text/javascript">new Vue({"el":"#myDiv",data:{phoneUserForm:{phone:"13330964748",verifyCode:"",accessToken:null,openId:null}},methods:{binder(){this.$http.post("/login/binder/wechat",this.phoneUserForm).then(result=>{result = result.data;//提示alert("登录成功!")//把token和loginInfo存放到localStoragelet {token,loginInfo} = result.resultObj;localStorage.setItem("token",token);//把对象转换为json字符串存放localStorage.setItem("loginInfo",JSON.stringify(loginInfo));console.log(result,"fjfjjfjfjfjfjjfjf")//跳转主页location.href = "/index.html";}).catch(result=>{console.log(result,"jjjjj")alert("系统错误!");})},sendMobileCode(){//1.判断手机号不为空if(!this.phoneUserForm.phone){alert("手机号不能为空");return;}//2.获取按钮,禁用按钮  发送时灰化不能使用,发送成功倒计时60才能使用,如果发送失败立即可以发送var sendBtn = $(event.target);sendBtn.attr("disabled",true);this.$http.post('/verifycode/smsCode',{"phone":this.phoneUserForm.phone,"type":"binder"}).then((res) => {console.log(res);var ajaxResult = res.data;if(ajaxResult.success){alert("手机验证码已经发送到您的手机,请在3分钟内使用");//3.1.发送成:倒计时var time = 60;var interval = window.setInterval( function () {//每一条倒计时减一time = time - 1 ;//把倒计时时间搞到按钮上sendBtn.html(time);//3.2.倒计时完成恢复按钮if(time <= 0){sendBtn.html("重发");sendBtn.attr("disabled",false);//清除定时器window.clearInterval(interval);}},1000);}else{//3.3.发送失败:提示,恢复按钮sendBtn.attr("disabled",false);alert("发送失败:"+ajaxResult.message);}});}},mounted(){let paramObj =  parseUrlParams2Obj(location.href);if(paramObj){this.phoneUserForm.accessToken = paramObj.accessToken;this.phoneUserForm.openId = paramObj.openId;}}})</script></html>

改造发送短信验证接口

@RestController
@RequestMapping("/verifycode")
public class VerifyCodeController {@Autowiredprivate IVerifyCodeService verifyCodeService;//一定情况下Map能够代替类使用@PostMapping("/smsCode") //注册验证码public AjaxResult sendSmsCode(@RequestBody Map<String,String> params){String phone = params.get("phone");String type = params.get("type"); //register binder logintry {verifyCodeService.sendSmsCode(params);return AjaxResult.me();}catch (BusinessException e){e.printStackTrace();return AjaxResult.me().setMessage("发送失败!"+e.getMessage());}catch (Exception e) {return AjaxResult.me().setMessage("系统错误!"+e.getMessage());}}
}

VerifyCodeServiceImpl

@Overridepublic void sendSmsCode(Map<String,String> params) {String phone = params.get("phone");String type = params.get("type");//1 校验//1.1 手机号不能为nullif (!StringUtils.hasLength(phone))throw new BusinessException("请输入手机号!");if ("register".equals(type)){ //注册//1.2 不能被注册User user = userMapper.loadByPhone(phone);if (user!=null)throw new BusinessException("用户已经被注册!");String businessKey = UserConstants.REGISTER_CODE_PREFIX + phone;sendSmsCode(businessKey);}else if("binder".equals(type)){ //绑定String businessKey = UserConstants.BINDER_CODE_PREFIX + phone;sendSmsCode(businessKey);}}private void  sendSmsCode(String businessKey){//2 判断原来的是否有效Object codeObj = redisTemplate.opsForValue().get(businessKey); //code:timeString code = "";//2.1 如果有效if (codeObj!=null){String codeStr = (String) codeObj;//2.1.1 判断是否已过重发时间String time = codeStr.split(":")[1]; //114555558888long intervalTime = System.currentTimeMillis() - Long.valueOf(time);if (intervalTime<=1*60*1000){//2.1.1.1 如果没有过提示错误throw new BusinessException("请勿重复发送短信验证码!");}//2.1.1.2 如果过了,就使用原来验证码code = codeStr.split(":")[0];}else{//2.2 如果没有//2.2.1 重新生成验证码code = StrUtils.getComplexRandomString(4);}//3 保存验证码到redisredisTemplate.opsForValue().set(businessKey,code+":"+System.currentTimeMillis(),3, TimeUnit.MINUTES);//4 调用短信接口发送短信
//        SmsUtil.sendMsg(phone,"您的验证码为:"+code+",请在3分钟之内使用!");System.out.println("您的验证码为:"+code+",请在3分钟之内使用!");}
5.3.5.绑定实现
LoginController
@PostMapping("/binder/wechat")
public AjaxResult binderWechat(@RequestBody Map<String,String> params){try {return  loginInfoService.binderWechat(params);} catch (Exception e) {e.printStackTrace();return AjaxResult.me().setMessage("系统错误!"+e.getMessage());}
}

LoginInfoService

//前台输入手机号是否有账号,如果有创建wxUser帮上就ok,如果没有创建账号在绑定
@Override
public AjaxResult binderWechat(Map<String, String> params) {//参数String phone = params.get("phone");String verifyCode = params.get("verifyCode");String accessToken = params.get("accessToken");String openId = params.get("openId");//0 验证码比对Object codeObj = redisTemplate.opsForValue().get(UserConstants.BINDER_CODE_PREFIX + phone);if(codeObj==null){return AjaxResult.me().setMessage("请重新获取验证码后再操作!");}else{String codeStr = (String) codeObj;String code = codeStr.split(":")[0];//code:timeif (!verifyCode.equalsIgnoreCase(code)){return AjaxResult.me().setMessage("请输入正确验证码后再操作!");}}//1 获取微信用户信息String url = WxConstants.GET_USER_URL.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId);String jsonStr = HttpClientUtils.httpGet(url);//2通过电话和type获取用户登录信息LoginDto loginDto = new LoginDto();loginDto.setUsername(phone);loginDto.setLoginType(1);LoginInfo info = loginInfoMapper.loadByPhone(loginDto);//3如果用户登录信息不存在User user = null;if (info==null){user = wxUser2User(phone);info = user2LoginInfo(user);//3.1 创建loginInfologinInfoMapper.save(info);//3.2 创建Useruser.setInfo(info);userMapper.save(user);}else{//4用户存在 查询用户user = userMapper.loadByPhone(phone);}//5把用户和wxUser进行绑定WxUser wxUser = wxUserJsonStr2WxUser(jsonStr);wxUser.setUser_id(user.getId());wxUserMapper.save(wxUser);//6做免密登录//3.1 如果关联了实现免密登录String token = UUID.randomUUID().toString();redisTemplate.opsForValue().set(token,info,30, TimeUnit.MINUTES);Map<String,Object> result = new HashMap<>();result.put("token",token);info.setSalt(null);info.setPassword(null);result.put("loginInfo",info);return AjaxResult.me().setResultObj(result);}private LoginInfo user2LoginInfo(User user) {LoginInfo info = new LoginInfo();BeanUtils.copyProperties(user,info); //按照同名原则拷贝属性return info;
}private User wxUser2User(String phone) {User user = new User();user.setUsername(phone);user.setPhone(phone);user.setEmail(null);//给一个随机密码String salt = UUID.randomUUID().toString();String password = MD5Utils.encrypByMd5(StrUtils.getComplexRandomString(6)+salt);user.setPassword(password);user.setSalt(salt);user.setState(1);user.setCreatetime(new Date());return user;
}private WxUser wxUserJsonStr2WxUser(String jsonStr) {JSONObject jsonObject = JSONObject.parseObject(jsonStr);WxUser wxUser = new WxUser();wxUser.setOpenid(jsonObject.getString("openid"));wxUser.setNickname(jsonObject.getString("nickname"));wxUser.setSex(jsonObject.getInteger("sex"));wxUser.setAddress(null);wxUser.setHeadimgurl(jsonObject.getString("headimgurl"));wxUser.setUnionid(jsonObject.getString("unionid"));return wxUser;
}
5.3.6.后台获取登录用户
package cn.itsource.basic.util;import cn.itsource.user.domain.LoginInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;import javax.servlet.http.HttpServletRequest;/*** 登录的上下文*    登录用户的一些信息放到这里面,以后直接获取就ok了*    1 获取登录用户*    2 获取登录用户的角色或权限*    ....* ==========只是一个工具类不需要交给Spring管理=================** ?? 一个不受spirng管理的bean,要获取受spring管理的bean*/
public class LoginContext {/*** 获取当前登录用户信息* @param request* @return*/public static LoginInfo getLoginInfo(HttpServletRequest request){//从请求头中获取tokenString token = request.getHeader("token");//使用token从redis中获取登录信息if (!StringUtils.isEmpty(token)){//1 获取spring容器WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(request.getServletContext());//2 通过容器获取beanRedisTemplate redisTemplate = (RedisTemplate) context.getBean("redisTemplate");//3 获取登录信息Object loginInfoObj = redisTemplate.opsForValue().get(token);if (loginInfoObj!=null)return (LoginInfo) loginInfoObj;}return null;}
}

6.课程总结

6.1.重点

6.2.难点

6.3.如何掌握

6.4.排错技巧(技巧)

7.常见问题

8.课后练习

9.面试题

10.扩展知识或课外阅读推荐(可选)

10.1.扩展知识

10.2.课外阅读

A072_前台登录_三方登录相关推荐

  1. jwt单点登录_单点登录SSO技术选型

    一些人存在的意义总归是让另一些人成长,然后消失. --刘同<谁的青春不迷茫> 1.单点登录是什么? 单点登录主要用于多系统集成,即在多个系统中,用户只需要到一个中央服务器登录一次即可访问这 ...

  2. 浏览器时间久了重新登录_以登录为例讲解什么是全面的测试设计

    在面试时,面试官往往会出一个简单的场景让大家进行测试点设计来考察大家的测试设计能力,题目看似简单实则蕴藏杀机,测试人员需要根据自己的工作年限做出不同的回答方可过关.如果你工作1-2年,那么你只需要回答 ...

  3. java判断用户是否在某一个区域登录_单点登录实现原理

    一.单点登录概念 什么是单点登录?单点登录全称 Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部 ...

  4. python模拟qq空间登录_模拟登录QQ空间

    # coding=utf-8 from selenium import webdriver # 模拟登录QQ空间 def Start_Login(): # 这个是chormedriver的地址 dri ...

  5. SpringCloud工作笔记072---同一浏览器上不同标签页_tab页或者同一浏览器新开一个浏览器窗口也能实现单点登录_单点登录_localStorage_sessionStorage

    JAVA技术交流QQ群:170933152 注意用localStorage,存的token不会失效,可以在关闭浏览器的时候,清除或者设置一下过期时间,怎么做可以百度 注意:sessionStorage ...

  6. html退出登录_退出登录 0152

    用户只需要向 Spring Security 项目中发送/logout 退出请求即可. 1 退出实现 实现退出非常简单,只要在页面中添加/log <a href="/logout&qu ...

  7. java三层架构实现登录_用户登录——三层架构

    1.MVC开发模式(web开发模式): M:model-------javabean(封装数据) V:view----------jsp(显示数据) C:controller----servlet(获 ...

  8. python模拟qq空间登录_模拟登录系列 | QQ空间模拟登录

    原文链接模拟登录系列 | QQ空间模拟登录​mp.weixin.qq.com 本系列所有代码均在这CharlesPikachu/DecryptLogin​github.com 原理简介 这里,我们简单 ...

  9. python qq空间登录_模拟登录QQ空间

    # coding=utf-8 from selenium import webdriver # 模拟登录QQ空间 def Start_Login(): # 这个是chormedriver的地址 dri ...

最新文章

  1. python能解密java的_实现Java加密,Python解密的RSA非对称加密算法功能
  2. Java并发编程系列之CyclicBarrier详解
  3. 安装深度linux无法引导,安装deepin无法引导,求教!
  4. android flux 与mvp,使用 MVP 时在设计上的考量
  5. PAT_B_1011_Java(15分)
  6. java hashmap读,java – ConcurrentHashmap – 读取和删除
  7. linux内核那些事之mempolicy(1)
  8. 用leangoo怎么做需求管理及规划?(产品Backlog、用户故事)
  9. SQL数据库中日期时间类型,按日期group by 实现
  10. 《机器学习算法竞赛实战》整理 | 六、模型融合
  11. DELL戴尔笔记本电脑找不到或没有DELL触摸板时关闭触摸板方法
  12. Dialog System, QA问答系统
  13. 数据库分库分表可扩展及数据倾斜/热点问题(二)
  14. 蚂蚁金服实习Android岗,面试闯关记。
  15. CentOS 7.9安装bpftrace
  16. nofollow的使用以及作用
  17. HTML+CSS+JavaScript实现放大镜效果
  18. kubernetes入门实战
  19. 前端皮肤功能实现(基于Less)
  20. CND(内容分发网络)前端的使用

热门文章

  1. 国产linux凝思4.2系统多网卡指定路由配置
  2. 心愿作文计算机,电脑的心愿作文
  3. 亮化工程改善城市夜景有什么重要意义
  4. 阿里云建站之模板建站的核心优势有哪些?
  5. ac管理器管理员密码忘记了_人们为什么不使用密码管理器
  6. [系统安全] 二十九.深信服分享之外部威胁防护和勒索病毒对抗
  7. nodejs服务器与服务器之间通讯问题(nodejs服务器端创建客户端)
  8. 免费的几款内网穿透工具
  9. flutter 如何实现上下标效果
  10. 认识程序集:1. 程序集的生成