1.官方网站:微信登录功能 / 网页应用授权用户信息变更 (qq.com)
和登陆微信没关系,其实就是微信给我一个id,保证和该用户绑定,不会重复。
2.登陆流程:
是用户的微信号,和我程序的程序编号(微信给的 目前用的老师的),去请求微信的接口,微信返回openId,这个openId在本系统中 只和该用户对应。可以当作用户的唯一标识,我获得了这个openId,就知道是哪个用户了,这就是登录流程。
详细流程:请求到达后端后,携带参数 (1.回调地址 2.应用的id)重定向到微信的扫码界面,用户扫码并且确认后,微信会 携带code(临时票据)访问回调地址,我获得code(临时票据)后,访问微信认证服务器,获取openid。如果该openid没有在本系统中保存,说明之前没登陆过,我需要携带openid访问微信的资源服务器,获取微信名、头像、性别信息,保存该用户信息。
3.具体实现
1》用户在页面点击登陆按钮,前端发送请求给后端
<li><a id="weixin" class="weixin" target="_blank" href="http://localhost:8160/eduuser/usercenter/wx/login"><i class="iconfont icon-weixin"/></a></li>
2》后端接收到请求,重定向到某个微信界面,此时浏览器页面会自动新建一个页签,是微信的二维码登陆界面。
重定向到微信界面需要参数 1.appid(应用的唯一标识)这里使用谷粒学院老师的。
2.回调地址,用户扫码成功后,微信会携带临时票据(code)访问此接口,这个接口需要接收code、携带code请求微信获取该用户openId、判断是否注册该用户、没有注册需要访问微信资源接口获取用户信息并在本系统注册、重定向到前端页面。
其他参数不清楚。
@GetMapping("login")
public String genQrConnect(HttpSession session) {
// 微信开放平台授权baseUrl
String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
"?appid=%s" +
"&redirect_uri=%s" +
"&response_type=code" +
"&scope=snsapi_login" +
"&state=%s" +
"#wechat_redirect";
// 回调地址
String redirectUrl = ConstantWxUtils.REDIRECT_URL; //获取业务服务器重定向地址
try {
redirectUrl = URLEncoder.encode(redirectUrl, "UTF-8"); //url编码
} catch (UnsupportedEncodingException e) {
throw new GuLiExeception(20001, e.getMessage());
}
// 防止csrf攻击(跨站请求伪造攻击)
//String state = UUID.randomUUID().toString().replaceAll("-", "");//一般情况下会使用一个随机数
String state = "imhelen";//为了让大家能够使用我搭建的外网的微信回调跳转服务器,这里填写你在ngrok的前置域名
// 采用redis等进行缓存state 使用sessionId为key 30分钟后过期,可配置
//键:"wechar-open-state-" + httpServletRequest.getSession().getId()
//值:satte
//过期时间:30分钟
//生成qrcodeUrl
String qrcodeUrl = String.format(
baseUrl,
ConstantWxUtils.APP_ID,
redirectUrl,
state);
return "redirect:" + qrcodeUrl;
}
3.上一步配置的回调接口,获取用户信息后,生成token,重定向到前端项目的默认界面,前端项目的默认界面发现有token,会解析token并生成cookie等,保存登录信息。
@GetMapping("callback")
public String callBack(String code, String state) {
//从redis中将state获取出来,和当前传入的state作比较
//如果一致则放行,如果不一致则抛出异常:非法访问
//向认证服务器发送请求换取access_token
String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
"?appid=%s" +
"&secret=%s" +
"&code=%s" +
"&grant_type=authorization_code";
String accessTokenUrl = String.format(baseAccessTokenUrl,
ConstantWxUtils.APP_ID,
ConstantWxUtils.APP_SECRECT,
code);
// 使用httpClient发送请求,得到返回结果
JSONObject accesstokenInfo = HttpClientUtils.httpGet(accessTokenUrl);
String access_token = (String) accesstokenInfo.get("access_token");
String openid = (String) accesstokenInfo.get("openid");
LambdaQueryWrapper<UcenterMember> lambdaQueryWrapper = new LambdaQueryWrapper();
lambdaQueryWrapper.eq(UcenterMember::getOpenid, openid);
UcenterMember checkUser = ucenterMemberService.getOne(lambdaQueryWrapper);
if (checkUser == null) {
//访问微信的资源服务器,获取用户信息
String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
"?access_token=%s" +
"&openid=%s";
String userInfoUrl = String.format(baseUserInfoUrl, access_token, openid);
try {
JSONObject jsonObject = HttpClientUtils.httpGet(userInfoUrl);
String nickName = jsonObject.getString("nickname");
String headUrl = jsonObject.getString("headimgurl");
UcenterMember ucenterMember = new UcenterMember(openid, nickName, headUrl);
ucenterMemberService.save(ucenterMember);
String jwtToken = JwtUtils.getJwtToken(ucenterMember.getId(), nickName);
return "redirect:http://localhost:3000?guli_token=" + jwtToken;
} catch (Exception e) {
throw new GuLiExeception(20001, "用户未注册,获取腾讯用户信息失败");
}
}
String jwtToken = JwtUtils.getJwtToken(checkUser.getId(), checkUser.getNickname());
String temp=JwtUtils.getMemberIdByJwtTokenString(jwtToken);
System.out.println("=======");
System.out.println(temp);
return "redirect:http://localhost:3000?guli_token="+jwtToken;
}
前端代码, 前端发现路径中有token后,获取这个token,并携带这个token访问获取用户信息接口,获取用户信息。这是default.vue,当用户没有输入访问的具体地址时,默认访问此界面。
<template>
<div class="in-wrap">
<!-- 公共头引入 -->
<header id="header">
<section class="container">
<h1 id="logo">
<a href="#" title="谷粒学院">
<img src="~/assets/img/logo.png" width="100%" alt="谷粒学院">
</a>
</h1>
<div class="h-r-nsl">
<ul class="nav">
<router-link to="/" tag="li" active-class="current" exact>
<a>首页</a>
</router-link>
<router-link to="/course" tag="li" active-class="current">
<a>课程</a>
</router-link>
<router-link to="/teacher" tag="li" active-class="current">
<a>名师</a>
</router-link>
<router-link to="/article" tag="li" active-class="current">
<a>文章</a>
</router-link>
<router-link to="/qa" tag="li" active-class="current">
<a>问答</a>
</router-link>
</ul>
<ul class="h-r-login">
<li v-if="!loginInfo.id" id="no-login">
<a href="/login" title="登录">
<em class="icon18 login-icon"> </em>
<span class="vam ml5">登录</span>
</a>
|
<a href="/register" title="注册">
<span class="vam ml5">注册</span>
</a>
</li>
<li v-if="loginInfo.id" id="is-login-one" class="mr10">
<a id="headerMsgCountId" href="#" title="消息">
<em class="icon18 news-icon"> </em>
</a>
<q class="red-point" style="display: none"> </q>
</li>
<li v-if="loginInfo.id" id="is-login-two" class="h-r-user">
<a href="/ucenter" title>
<img
:src="loginInfo.avatar"
width="30"
height="30"
class="vam picImg"
alt
/>
<span id="userName" class="vam disIb">{{
loginInfo.nickname
}}</span>
</a>
<a
href="javascript:void(0);"
title="退出"
@click="logout()"
class="ml5"
>退出</a
>
</li>
<!-- /未登录显示第1 li;登录后显示第2,3 li -->
</ul>
<aside class="h-r-search">
<form action="#" method="post">
<label class="h-r-s-box">
<input
type="text"
placeholder="输入你想学的课程"
name="queryCourse.courseName"
value
/>
<button type="submit" class="s-btn">
<em class="icon18"> </em>
</button>
</label>
</form>
</aside>
</div>
<aside class="mw-nav-btn">
<div class="mw-nav-icon"></div>
</aside>
<div class="clear"></div>
</section>
</header>
<!-- /公共头引入 -->
<nuxt />
<!-- 公共底引入 -->
<footer id="footer">
<section class="container">
<div class>
<h4 class="hLh30">
<span class="fsize18 f-fM c-999">友情链接</span>
</h4>
<ul class="of flink-list">
<li>
<a href="http://www.atguigu.com/" title="尚硅谷" target="_blank"
>尚硅谷</a
>
</li>
</ul>
<div class="clear"></div>
</div>
<div class="b-foot">
<section class="fl col-7">
<section class="mr20">
<section class="b-f-link">
<a href="#" title="关于我们" target="_blank">关于我们</a>|
<a href="#" title="联系我们" target="_blank">联系我们</a>|
<a href="#" title="帮助中心" target="_blank">帮助中心</a>|
<a href="#" title="资源下载" target="_blank">资源下载</a>|
<span>服务热线:010-56253825(北京) 0755-85293825(深圳)</span>
<span>Email:info@atguigu.com</span>
</section>
<section class="b-f-link mt10">
<span>©2018课程版权均归谷粒学院所有 京ICP备17055252号</span>
</section>
</section>
</section>
<aside class="fl col-3 tac mt15">
<section class="gf-tx">
<span>
<img src="~/assets/img/wx-icon.png" alt />
</span>
</section>
<section class="gf-tx">
<span>
<img src="~/assets/img/wb-icon.png" alt />
</span>
</section>
</aside>
<div class="clear"></div>
</div>
</section>
</footer>
<!-- /公共底引入 -->
</div>
</template>
<script>
import "~/assets/css/reset.css";
import "~/assets/css/theme.css";
import "~/assets/css/global.css";
import "~/assets/css/web.css";
import "~/assets/css/base.css";
import "~/assets/css/activity_tab.css";
import "~/assets/css/bottom_rec.css";
import "~/assets/css/nice_select.css";
import "~/assets/css/order.css";
import "~/assets/css/swiper-3.3.1.min.css";
import "~/assets/css/pages-weixinpay.css";
import cookie from "js-cookie";
import loginApi from "@/api/login";
export default {
data() {
return {
guli_token: "",
loginInfo: {
id: "",
age: "",
avatar: "",
mobile: "",
nickname: "",
sex: "",
},
};
},
created() {
// 获取路径中的token中的值
this.guli_token = this.$route.query.guli_token;
if (this.guli_token) {
cookie.set("guli_token", this.guli_token, { domain: "localhost" });
console.log("执行微信登录");
//如果可以取到token,用微信的方式登录
this.wxLogin();
}
this.showInfo();
},
methods: {
//创建方法,从cookie中获取用户信息
showInfo() {
var userStr = cookie.get("guli_ucenter");
if (userStr) {
//把字符串转换为JSON对象
this.loginInfo = JSON.parse(userStr);
}
},
//退出
logout() {
// domain 作用范围
cookie.set("guli_ucenter", "", { domain: "localhost" });
cookie.set("guli_token", "", { domain: "localhost" });
console.log("回首页");
//回到首页面
window.location.href = "/";
},
//微信登陆
wxLogin() {
if (this.guli_token == "") return;
//登录成功根据token获取用户信息
loginApi.getLoginInfo().then((response) => {
this.loginInfo = response.data.data.member;
//将用户信息记录cookie
cookie.set("guli_ucenter", JSON.stringify(this.loginInfo), {
domain: "localhost",
});
});
},
},
};
</script>
后端接收到前端发送的token,根据这个token查询用户信息并返回(就是正常的登录、获取用户信息了)
@GetMapping("/info")
public R geInfo(HttpServletRequest request) {
String headers = "";
Enumeration<String> names = request.getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
String value = request.getHeader(name);
headers += name + "=" + value + "\n";
}
System.out.println(headers + "请求用户信息请求头如上");
String memberId = JwtUtils.getMemberIdByJwtToken(request);
UcenterMember uCenterMember = ucenterMemberService.getById(memberId);
return R.ok().data("member", uCenterMember);
}