SPA单页应用前后分离微信授权
项目基于微信公众号开发,业务完全依赖微信授权,也就是用户进入页面已经完成授权获取到用户的OpenId。
需要有一个授权中间页:author.vue
基本实现思路:
- 无论使用哪个url进入页面都会先触发router.beforeEach钩子。
- 在router.beforeEach钩子函数中判断用户是否授权。
- 若未授权则保存用户进入的url并请求后台接口获取微信授权(window.location.href=‘后台接口’)。
- 后台调用微信接口授权获取用户信息及openId,将openId使用JWT生成一个唯一的token令牌,并将token已参数的形式拼接到url后面,然后重定向到前端author.vue页面。
- author页面获取url中的token参数,将token参数保存到本地缓存。
- 获取签名用户保存的url并跳转。
前端代码实现:
路由index.js
// 全局守卫,微信授权
router.beforeEach((to, from, next) => {
// 路由发生变化修改页面title
if (to.meta.title) {
document.title = to.meta.title
}
if (process.env.NODE_ENV !== 'development') {
const token = window.localStorage.getItem('token')
if (token) {
if (to.path === '/author') {
next({
path: '/'
})
} else {
next()
}
} else {
if (to.path !== '/author') {
// 保存用户进入的url
window.localStorage.setItem('authUrl', to.fullPath)
// 跳转到微信授权页面
window.location.href = process.env.BASE_URL + '/wx/OAuth2/index'
} else {
next()
}
}
} else {
window.localStorage.setItem('token', 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJvUUFFYndSSU5VVlhPLVZoOWhEcDUzX3RNeEgwIn0.eShRG4fVFFv4w2gHnkyh7QDdVpG1meOHSZXOrbq-psE')
}
next()
})
Author.vue
<template>
<div>授权中</div>
</template> <script>
export default {
name: 'Author',
data () {
return {
user: null
}
},
created () {
// url中获取参数token
const wxToken = this.$route.query.token
// url中获取参数code
const code = this.$route.query.code
// 后端重定向获取参数,判断是否处理成功 200:成功
if (wxToken && Number(code) === 200) {
// 将token放入本地缓存
window.localStorage.setItem('token', wxToken)
// 从本地缓存中获取用户第一次请求页面URL
const historyUrl = window.localStorage.getItem('authUrl')
// 跳转页面
this.$router.push(historyUrl)
} else {
// 没有拿到后台访问微信返回的token
// 清空本地缓存
window.localStorage.removeItem('token')
window.localStorage.removeItem('authUrl')
}
}
}
</script> <style scoped> </style>
后端代码实现:
/**
* 微信授权 --- OATH2 -- 第一种方式(推荐)
* 第一步:前端请求-/wx/oAth2/index
* 第二步:重定向-微信服务器
*/
@PassToken
@GetMapping(value = "/wx/OAuth2/index")
public void OAth2(HttpServletResponse response) throws IOException{
response.sendRedirect(wxMpService.oauth2buildAuthorizationUrl(baseUrl + "/wx/OAuth2/redirect",
WxConsts.OAuth2Scope.SNSAPI_USERINFO, null));
} /**
* 微信授权 -- 微信回调
* 第一步:获取code
* 第二步:通过code获取用户信息
* 第三步:Jwt生成Token令牌
* 第四步:重定向 --> 前端页面
*/
@PassToken
@GetMapping(value = "/wx/OAuth2/redirect")
public void OAth2Return(HttpServletRequest request, HttpServletResponse response) throws IOException,WxErrorException{
String code = request.getParameter("code");
// 获取用户信息
WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(wxMpService.oauth2getAccessToken(code), null);
log.info("[微信授权]--------拉取用户信息详细如下:{}",wxMpUser);
//将微信用户信息入库
wxUserInfoService.insertWxUser(wxMpUser);
//生成token令牌
String token = JWT.create().withAudience(wxMpUser.getOpenId()).sign(Algorithm.HMAC256(jwtSecret));
//重定向地址
String redirectUrl = frontUrl + "/#/author" + "?token=" + token + "&code=200";
response.sendRedirect(redirectUrl);
}
后台验证用户信息
前端获取到token令牌之后,前端每次请求,后端如何获取OpenId以及业务处理?
基本实现思路:
- 前端使用axios请求拦截器,判断本地缓存是否存在token,如果存在的话,则为每个Http请求赋值token。
- 后端使用拦截器拦截有@PassToken注解以外的方法,获取token值。如果token为null,直接返回错误码以及错误信息。
- 验证token值是否有效,如有效,则解析openId,并将openId放入request中放行。如无效,直接返回错误码以及错误信息。
- 拦截器放行,后端可直接通过request.getAttribute("openId")获取。
前端代码实现:
request.js
// 请求拦截器
axios.interceptors.request.use(function (config) {
config.headers['Content-Type'] = 'application/json;charset=UTF-8'
// 判断本地缓存是否存在token,如果存在的话,则每个http header都加上token
if (window.localStorage.getItem('token')) {
config.headers.authorization = window.localStorage.getItem('token')
}
return config
}, function (error) {
return Promise.reject(error)
})
后端代码实现:
JwtInterceptor.java
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
// 从 http 请求头中取出 token
String token = httpServletRequest.getHeader("Authorization");
// 如果不是映射到方法直接通过
if(!(object instanceof HandlerMethod)){
return true;
}
HandlerMethod handlerMethod=(HandlerMethod)object;
Method method=handlerMethod.getMethod();
// OPTIONS请求类型直接返回不处理
if ("OPTIONS".equals(httpServletRequest.getMethod())){
return false;
}
//检查是否有passToken注释,有则跳过认证
if (method.isAnnotationPresent(PassToken.class)) {
PassToken passToken = method.getAnnotation(PassToken.class);
if (passToken.required()) {
return true;
}
}
//校验token,并且将openId放入request中
if (StrUtil.isNotEmpty(token)){
// 验证 token
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(jwtSecret)).build();
try {
jwtVerifier.verify(token);
} catch (JWTVerificationException e) {
logger.info("token校验未通过");
httpServletResponse.getWriter().println(JSONUtil.toJsonStr(Result.need2BLogged()));
return false;
}
// 获取 token 中的 openId
String openId;
try {
openId = JWT.decode(token).getAudience().get(0);
httpServletRequest.setAttribute("openId",openId);
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
}
//检查有没有需要用户权限的注解
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken userLoginToken = method.getAnnotation(UserLoginToken.class);
if (userLoginToken.required()) {
// 执行认证
if (token == null) {
throw new RuntimeException("无token,请重新登录");
}
// 获取 token 中的 openId
String openId;
try {
openId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException j) {
throw new RuntimeException("401");
}
// 通过 openId 查询用户是否绑定手机号
if (objectRedisTemplate.hasKey(userIdKey + openId)) {
logger.info("通过FRDIES用户拦截器");
return true;
} else {
logger.info("REDIS:{Redis has no user information}");
//根据 openId 查询该用户的信息
BaseUserInfo userInfo = baseController.getUserInfo(httpServletRequest, httpServletResponse);
if (userInfo != null && StrUtil.isNotEmpty(userInfo.getPhone())){
logger.info("通过用户拦截器");
return true;
}else{
// 未绑定手机用户返回
httpServletResponse.getWriter().println(JSONUtil.toJsonStr(Result.need2BLogged()));
return false;
}
}
}
}
return true;
}
@PassToken
package com.yhzy.zytx.jwt.annotation; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; /**
* @ClassName PassToken
* @Description 自定义注解(跳过验证Token)
* @Author 天生傲骨、怎能屈服
* @Date 2019/5/22 13:38
* @Version 1.0
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
到这里整个前后分离微信授权的流程就完了,希望可以帮助到大家!!!
SPA单页应用前后分离微信授权的更多相关文章
- 前端 SPA 单页应用数据统计解决方案 (ReactJS / VueJS)
前端 SPA 单页应用数据统计解决方案 (ReactJS / VueJS) 一.百度统计的代码: UV PV 统计方式可能存在问题 在 SPA 的前端项目中 数据统计,往往就是一个比较麻烦的事情,Re ...
- [vue]spa单页开发及vue-router基础
- 了解spa页面跳转方式:(2种) spa: 单页跳转方式 开发(hash模式): https://www.baidu.com/#2313213 生产(h5利于seo): history.pushS ...
- Javascript 与 SPA单页Web富应用
书单推荐 # <单页Web应用:JavaScript从前端到后端> http://download.csdn.net/detail/epubitbook/8720475 # <MVC ...
- 【读书笔记】WebApi 和 SPA(单页应用)--knockout的使用
Web API从MVC4开始出现,可以服务于Asp.Net下的任何web应用,本文将介绍Web api在单页应用中的使用.什么是单页应用?Single-Page Application最常用的定义:一 ...
- 大熊君学习html5系列之------History API(SPA单页应用的必备)
一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会" ...
- 基于VUE的SPA单页应用开发-加载性能篇
1.基于异步数据的vue页面刷新 先看看基于异步数据的vue页面刷新后,都发生了啥- 如图所示: 图1 基于异步数据的vue页面刷新 网络请求图 步骤如下: step1:请求页面: step2:请求页 ...
- 前端学习之路之SPA(单页应用)设计原理
SPA设计 1.设计意义 前后端分离 减轻服务器压力 增强用户体验 Prerender预渲染优化SEO 前后端分离:前端做业务逻辑,后端处理数据和接口,耦合度减少,开发效率提高. 减轻服务器压力:一个 ...
- 大熊君学习html5系列之------History API(SPA单页应用的必备------重构完结版)
一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会" ...
- 3.【nuxt起步】-下面以一个SPA单页程序为例子
spa:single page applcation 1.components目录新建header.vue,footer.vue Header.vue Footer.vue Pages/index.v ...
随机推荐
- em、pt、px和百分比
浏览器默认的字体大小为100%=16px=12pt=1em px像素(Pixel):是固定大小的单元.相对长度单位.像素px是相对于显示器屏幕分辨率而言的.一个像素等于电脑屏幕上的一个点(是你屏幕分辨 ...
- 算法(Algorithms)第4版 练习 1.3.10
主要思路:和1.3.9相似,只不过运算表达式的生成方式不一样 用Dijkstra的双栈算法. 遇到数字则压入数字栈中(String). 遇到运算符则压入运算符栈中(String). 遇到右括号时,从数 ...
- 一篇文章教你如何用R进行数据挖掘
一篇文章教你如何用R进行数据挖掘 引言 R是一种广泛用于数据分析和统计计算的强大语言,于上世纪90年代开始发展起来.得益于全世界众多 爱好者的无尽努力,大家继而开发出了一种基于R但优于R基本文本编辑器 ...
- zabbix 监控 haproxy 记录
1. 当配置好后,遇到 Value should be a JSON object. 该如何是好? sudo 文件增加: zabbix ALL=(ALL) NOPASSWD: ALL haproxy ...
- HDU 4652 Dice:期望dp(成环)【错位相减】
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4652 题意: 给你一个有m个面的骰子. 两种询问: (1)"0 m n": “最后 ...
- 2015 年最热门的国人开发开源软件 TOP 50
开源中国在 2015 年得到了快速的发展,单开源软件收藏量就接近 40000 款,其中不乏优质的国产开源项目.本文从软件的收藏.下载.访问等多角度挑选出了 2015 年最热门的国产开源软件前五十名,让 ...
- Linux_异常_01_CentOS7无法ping 百度
一.原因 vi /etc/sysconfig/network-scripts/ifcfg-ens33 TYPE=Ethernet PROXY_METHOD=none BROWSER_ONLY=no B ...
- spring属性注入DI
spring setter方式注入: 注入对象属性: 前提: 在bean对应实体中有对应的setter方法. 基础代码: 在bean中有另一个bean属性的setter方法. package cn.i ...
- nginx使用ssl模块配置HTTPS支持 <转>
默认情况下ssl模块并未被安装,如果要使用该模块则需要在编译时指定–with-http_ssl_module参数,安装模块依赖于OpenSSL库和一些引用文件,通常这些文件并不在同一个软件包中.通常这 ...
- MAC OS Sierra 10.12.6 下对固态硬盘SSD 开启TRIM功能
这个是对于不是mac原装SSD的情况下才做的操作... 大家都知道,苹果店卖的SSD硬盘那怕就是一个256G的也要1000多人民币,而市场上的也就400-500左右人民币,整整少了一半还要多,可见JS ...