目录
- 前台登录-账号登录
- 前端完成
- 左上角显示用户信息
- 配置前置拦截器、后置拦截器和不受限资源拦截器
- 三方登录-微信登录
- 概述
- 流程图
- 用法
- 代码实现步骤分析:
- 实现准备
- 代码
- 前端
- login.html
- callback.html
- 后端
- LoginController-微信登录
- LoginServiceImpl-微信登录
- 解决回调域名不能跨域
- 绑定页面准备
- 改造发短信验证码接口
- LoginController-绑定微信用户
- LoginServiceImpl-绑定微信用户
前台登录-账号登录
前端完成
1)后台登录接口(和管理员登录接口共用,以type划分)
2)前台登录实现并且保存loginInfo和token到localStorage,登录成功跳转首页,并展示用户名
3)前台通过axios的前置拦截器携带token到后台
4)后台做token的登录拦截器,如果没有回报错给前台(前面已写,共用)
5)前台通过axios后置拦截器对后台登录拦截错误进行跳转到登录页面
6)前台也要做拦截-有的地址是不需要访问后台
login.html中引入axios和vue
标签 - data - 按钮 - methods
localstorage与域名挂钩,同一个域名下的key只能存在一个,不同域名下的key名称可以一样,即前后台的token名称可以一样
login.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.css"/>
<link href="./css/dlstyle.css" rel="stylesheet" type="text/css">
<!--引入vue和axios-->
<script src="js/plugin/vue/dist/vue.min.js"></script>
<script src="js/plugin/axios/dist/axios.js"></script>
<!--全局使用-->
<script src="js/common.js"></script>
</head>
<body>
<div class="login-boxtitle">
<a href="home.html"><img alt="logo" src="./images/logobig.png"/></a>
</div>
<div class="login-banner">
<div class="login-main">
<div class="login-banner-bg"><span></span><img src="./images/big.jpg"/></div>
<div class="login-box">
<h3 class="title">登录商城</h3>
<div class="clear"></div>
<div class="login-form">
<form>
<div class="user-name">
<label for="user"><i class="am-icon-user"></i></label>
<input type="text" name="" v-model="userLoginForm.username" id="user" placeholder="邮箱/手机/用户名">
</div>
<div class="user-pass">
<label for="password"><i class="am-icon-lock"></i></label>
<input type="password" name="" v-model="userLoginForm.password" id="password"
placeholder="请输入密码">
</div>
</form>
</div>
<div class="login-links">
<label for="remember-me"><input id="remember-me" type="checkbox">记住密码</label>
<a href="#" class="am-fr">忘记密码</a>
<a href="register.html" class="zcnext am-fr am-btn-default">注册</a>
<br/>
</div>
<div class="am-cf">
<input type="button" @click="login" name="" value="登 录" class="am-btn am-btn-primary am-btn-sm">
</div>
<div class="partner">
<h3>合作账号</h3>
<div class="am-btn-group">
<li><a href="#"><i class="am-icon-qq am-icon-sm"></i><span>QQ登录</span></a></li>
<li><a href="#"><i class="am-icon-weibo am-icon-sm"></i><span>微博登录</span> </a></li>
<li><a href="#"><i class="am-icon-weixin am-icon-sm"></i><span>微信登录</span> </a></li>
</div>
</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: ".login-main",
data: {
userLoginForm: {
username: "",
password: "",
type: 1
}
},
methods: {
login() {
this.$http.post("/login/account", this.userLoginForm)
.then(result => {
result = result.data;
if (result.success) {
//保存信息到localstorage
var resultObj = result.resultObj;
localStorage.setItem("token", resultObj.token); //不同域名下的key名称可以一样
localStorage.setItem("logininfo", JSON.stringify(resultObj.logininfo));
alert("登录成功!");
//提示登录成功
//跳转到主页
location.href = "/index.html";
} else {
alert(result.message);
}
})
.catch(result => {
alert("系统异常!");
})
}
},
mounted() {
}
})
</script>
</html>
左上角显示用户信息
index.html
引入vue和axios
页面元素 - vue实例 -
<!--引入vue和axios-->
<script src="js/plugin/vue/dist/vue.min.js"></script>
<script src="js/plugin/axios/dist/axios.js"></script>
<!--全局使用-->
<script src="js/common.js"></script>
<div id="myDiv">
<div class="menu-hd" v-if="logininfo">
<a href="#" target="_top" class="h">
欢迎您:<span style="color: red">{{logininfo.username || logininfo.phone || logininfo.email ||''}}</span>
</a>
</div>
<div class="menu-hd" v-else>
<a href="#" target="_top" class="h">亲,请登录</a>
<a href="register.html" target="_blank">免费注册</a>
</div>
<script type="text/javascript">
new Vue({
el:"#myDiv",
data:{
logininfo:null
},
mounted(){
let loginObjStr = localStorage.getItem("logininfo");
if(loginObjStr){
this.logininfo = JSON.parse(loginObjStr);
}
}
})
</script>
配置前置拦截器、后置拦截器和不受限资源拦截器
common.js
//配置axios的全局基本路径
/*axios.defaults.baseURL='/api' 前端解决跨域*/
axios.defaults.baseURL='http://localhost:8080/'
/**
* 使用前提,要发送axios的页面,都应该要引入common.js
* axios前置拦截器:作用:每次发送axios请求,需要携带token给后端
*/
axios.interceptors.request.use(config=>{
//携带token
let uToken = localStorage.getItem("token");
if(uToken){
config.headers['token']=uToken;
}
return config;//一定要返回配置
},error => {
Promise.reject(error);
})
/**
* axios后置拦截:作用:后端ajax请求 报错,在这里处理
*/
axios.interceptors.response.use(result=>{
let data = result.data;
if(!data.success && data.message==="noLogin"){
localStorage.removeItem('token');
localStorage.removeItem('logininfo');
/*router.push({ path: '/login' });*/
location.href = "/login.html";
}
return result;
},error => {
Promise.reject(error);
})
//全局属性配置,在任意组件内可以使用this.$http获取axios对象
Vue.prototype.$http = axios
/*js拦截*/
let uri = location.href;//http://127.0.0.1/index.html
/*不是这两个结尾的请求地址,我应该阻止,并校验是否登录*/
if(uri.indexOf("/login.html") == -1 && uri.indexOf("/register.html")==-1
&& uri.indexOf("/index.html") == -1){
let loginObj = localStorage.getItem('logininfo');
if(!loginObj){
location.href = "/login.html";
}
}
三方登录-微信登录
概述
用户通过微信登录链接访问微信平台
微信返回登录二维码
用户扫码进入微信平台
微信校验用户后返回链接+校验码给本平台 ------- 返回页面信息
本平台通过校验码去微信获取用户信息token ------- 解析页面信息绑定数据,通过校验码去微信获取用户信息token,通过token去微信拿用户信息保存到本平台,设置默认用户密码,下次再扫码校验是微信的和用户是存在的就登录成功
流程图
用法
https://open.weixin.qq.com/:注册为开发者
-> 网站应用开发 - 填写信息 - 邮箱激活 - 完善开发者资料 -
开发者认证 - 人工审核,自己无法申请成功,用源码提供的
APPID:wxd853562a0548a7d0,SECRET:4a5d5615f93f24bdba2ba8534642dbb6,回调域:bugtracker.itsource.cn
目前回调域名没有对外,我们是127.0.0.1,要让外网微信能访问到内网地址,
回调域相当于宠物平台的后端地址
开发阶段通过本地hosts解析将回调域名指向本机127.0.0.1
代码实现步骤分析:
微信扫码登录具体实现步骤:
1.用户点击主站微信登录A标签按钮,平台带上 appid 和 回调页面地址 发送请求到微信平台,
0.微信端会弹出二维码扫码界面给用户
1.用户扫码后,微信平台获得授权生成http://bugtracker.itsource.cn/callback.html?code=xxxxx,回调到平台callback页面
2.平台callback页面页面加载解析地址栏中的code,携带code和binder.html发送微信登录请求到后端,后端通过APPID,SECRET和code到微信平台获取token和授权用户唯一标识openid
再根据openid看用户是否在本地是否存有WxUser信息和绑定用户User_id
如果有,通过绑定的User_id查询到登录信息,生成token和logininfo返回前端 --->免密登录
3.如果没有,返回binderUrl+token+openId给前端,前端跳转到绑定页面,页面加载解析token和openId,填写手机号与短信验证码后带上token和openId发送绑定请求到后端,后端完成验证码验证后携带token和openid发送请求到微信平台拿用户微信信息
然后根据手机号和类型看是否存在此平台登录用户,存在就查出user结合wxUserStr直接保存和绑定进wxUser表,--->然后免密登录
不存在就根据phone和type生成logininfo(密码默认)和拷贝user保存,然后user结合wxUserStr保存和绑定进wxUser表,--->然后免密登录
tips:后面生成logininfo设置默认密码时可以发短信告知用户
项目中该怎么干?
1.用户点击前端微信按钮
2.web发起获取授权请求到微信,带上回调地址callback.html
3.微信展示二维码给用户,让用户扫码
4.用户扫码后,跳转到我们的callBack页面,会加上一个授权码code给到我们
5.那么我们在mounted中可以发起微信登录请求到我们的pethome后台,
传递一个绑定用户信息的地址过来
6.后台接口中直接获取code,然后发起获取token的请求
得到token我们是不是又可以发起获取微信用户信息的请求
7.得到用户后,判断微信用户在我们系统中有没有对应的t_user
如果有,实现免密登录
如果没有,弹出一个绑定用户账号信息的窗口,让他完成信息绑定(相当于给他注册一个账号)
然后在登录成功
实现准备
见文档
代码
前端
login.html
<li><a href="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 ">
<i class="am-icon-weixin am-icon-sm"></i><span>微信登录</span> </a></li>
拷贝callback页面到web根目录,修改vue和axios引入路径,js拦截放行callback页面,common.js下面补充parseUrlParams2Obj方法
callback.html
<html lang="en">
<head>
<meta charset="UTF-8">
<title>回调</title>
<!--引入vue和axios-->
<script src="js/plugin/vue/dist/vue.min.js"></script>
<script src="js/plugin/axios/dist/axios.js"></script>
<!--全局使用-->
<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 param = {"code":paramObj.code,"binderUrl":binderUrl};
//发送微信登录验证 到pethome后端
this.$http.post("/login/wechat",param)
.then(result=>{
result = result.data;
if(result.success){//已经绑定,免密登录成功
alert("登录成功");
//1.将token和登录信息存入localStorage
let {token,loginInfo} = result.resultObj;
localStorage.setItem("token",token);
localStorage.setItem("loginInfo",JSON.stringify(loginInfo));
//2.跳转到首页,并显示登录信息
location.href="index.html";
}else{
let url = result.resultObj;
//没有绑定,跳转到绑定页面
location.href=url;
}
})
.catch(result=>{
alert("系统异常!");
})
}
})
</script>
</body>
</html>
后端
LoginController-微信登录
/**
* 微信扫码登录
* @param params code binderUrl
* @return
*/
@PostMapping("/wechat")
public AjaxResult wechat(@RequestBody Map<String,String> params){
try {
return loginService.wechat(params);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setMessage("系统繁忙,稍后重试!");
}
}
LoginServiceImpl-微信登录
/**
* 空校验
* 通过code去微信平台拿token 同时返回openid
* 校验是否微信用户和我们平台yoghurt绑定
* 如果绑定了, 直接免密登录成功
* 如果没有绑定
* 跳转到绑定用户信息页面?后端能跳转吗?json {success:false,binderUrl:....}
*/
@Override
public AjaxResult wechat(Map<String, String> params) {
//1.获取参数
String code = params.get("code");
String binderUrl = params.get("binderUrl");
if (StringUtils.isEmpty(code) || StringUtils.isEmpty(binderUrl)) {
return AjaxResult.me().setMessage("必要参数为空,登录失败");
}
//2.发送http请求去微信平台拿token
String accessTokenUrl = WxConstants.GET_ACK_URL
.replace("APPID", WxConstants.APPID)
.replace("SECRET", WxConstants.SECRET)
.replace("CODE", code);
String strObj = HttpClientUtils.httpGet(accessTokenUrl);
System.out.println(strObj);
JSONObject object = JSONObject.parseObject(strObj);
String accessToken = (String) object.get("access_token");
String openId = (String) object.get("openid");
//3.判断是否绑定微信用户 通过openId 查询微信用户信息,如果查询到了并且有userid
WxUser wxUser = wxUserMapper.loadByOpenId(openId);
// 3.1 如果查询到值,并且绑定了数据,直接免密登录
if (wxUser != null && wxUser.getUser_id() != null) {//免密登录
// 2.1 如果密码相等 登录成功 存redis,封装返回值
/**
* 这里的redis key就是前端需要存储的token
*/
//通过用户ID查询到登录人
Logininfo logininfo = logininfoMapper.loadbyUserId(wxUser.getUser_id());
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);
Map<String, Object> map = new HashMap<>();
map.put("token", token);
//作为一个高级技师 我们不能将账号和密码传给前端,应该过滤掉
logininfo.setUsername(null);
logininfo.setPassword(null);
map.put("logininfo", logininfo);
return AjaxResult.me().setResultObj(map);//免密登录成功返回
} else {// 3.2 如果没有查询那么应该跳转到绑定页面
String resultUrl = binderUrl + "?accessToken=" + accessToken + "&openId=" + openId;
return AjaxResult.me().setSuccess(false).setResultObj(resultUrl);//免密登录成功返回
}
}
解决回调域名不能跨域
GlobalCorsConfig
config.addAllowedOrigin("http://bugtracker.itsource.cn");
config.addAllowedOrigin("http://bugtracker.itsource.cn:80");
绑定页面准备
拷贝binder.html到web根目录,调整引入路径,调整参数名,js拦截放行
改造发短信验证码接口
见代码
LoginController-绑定微信用户
/**
* 绑定微信用户接口
*/
@PostMapping("/binder/wechat")
public AjaxResult binder(@RequestBody Map<String,String> params){
try {
return loginService.binder(params);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setMessage("系统繁忙,稍后重试!");
}
}
LoginServiceImpl-绑定微信用户
@Override
public AjaxResult binder(Map<String, String> params) {
String phone = params.get("phone");
String accessToken = params.get("accessToken");
String openId = params.get("openId");
String verifyCode = params.get("verifyCode");
//1.空校验
if(StringUtils.isEmpty(phone) || StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openId) || StringUtils.isEmpty(verifyCode)){
return AjaxResult.me().setMessage("必要参数为空,绑定失败");
}
//2.验证码是否过期或正确
Object codeObj = redisTemplate.opsForValue().get(UserConstant.USER_BINDING_CODE+":"+phone);
// 2.1 验证码是否过期
if(codeObj == null){
return AjaxResult.me().setMessage("验证码已经过期,请重新发送验证码!");
}
String code = ((String)codeObj).split(":")[0];
// 2.2 验证码是否正确
if(!verifyCode.equalsIgnoreCase(code)){
return AjaxResult.me().setMessage("请输入正确的验证码!");
}
//3.查询微信平台用户
//发送http请求去微信平台拿微信用户
String userUrl = WxConstants.GET_USER_URL
.replace("ACCESS_TOKEN", accessToken)
.replace("OPENID", openId);
String strObj = HttpClientUtils.httpGet(userUrl);
System.out.println(strObj);
if(strObj == null){//拿不到微信用户了
return AjaxResult.me().setMessage("微信用户不存在!");
}
UserDto dto = new UserDto();
dto.setPhone(phone);
dto.setType(1);
//4.查询平台用户是否存在
Logininfo logininfo = logininfoMapper.loadByDto(dto);
User user = null;
if(logininfo != null){//存在平台用户,那么曾经这个手机号已经注册过,直接绑定就可以了
user = userMapper.loadByloginInfoId(logininfo.getId());
}else{//平台不存在手机相关的用户
logininfo = createLoginInfo(phone);
user = loginInfo2User(logininfo);
logininfoMapper.save(logininfo);
user.setInfo(logininfo);
userMapper.save(user);
}
//5.绑定微信用户
WxUser wxUser = user2WxUser(strObj, user);
wxUserMapper.save(wxUser);
//6.免密登录
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set(token, logininfo, 30, TimeUnit.MINUTES);
Map<String,Object> map = new HashMap<>();
map.put("token", token);
//作为一个高级技师 我们不能将账号和密码传给前端,应该过滤掉
logininfo.setUsername(null);
logininfo.setPassword(null);
map.put("logininfo",logininfo);
return AjaxResult.me().setResultObj(map);//免密登录成功返回
}
private WxUser user2WxUser(String wxUserStr, User user) {
//装换微信用户信息
JSONObject jsonObject = JSONObject.parseObject(wxUserStr);
WxUser wxUser = new WxUser();
wxUser.setUser_id(user.getId()); // 绑定平台用户
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;
}
private User loginInfo2User(Logininfo info) {
User user = new User();
BeanUtils.copyProperties(info,user);//根据同名原则拷贝
return user;
}
/**
* 通过Phone创建info
*/
private Logininfo createLoginInfo(String phone) {
Logininfo info = new Logininfo();
info.setUsername(phone);
info.setPhone(phone);
//设置一个默认密码 todo:默认密码可以发短信告知用户
String salt = StrUtils.getComplexRandomString(32);
String md5Pwd = MD5Utils.encrypByMd5(StrUtils.getComplexRandomString(6) + salt);
info.setType(1);//设置前端type值
info.setSalt(salt);
info.setPassword(md5Pwd);
return info;
}