项目基于微信公众号开发,业务完全依赖微信授权,也就是用户进入页面已经完成授权获取到用户的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单页应用前后分离微信授权的更多相关文章

  1. 前端 SPA 单页应用数据统计解决方案 (ReactJS / VueJS)

    前端 SPA 单页应用数据统计解决方案 (ReactJS / VueJS) 一.百度统计的代码: UV PV 统计方式可能存在问题 在 SPA 的前端项目中 数据统计,往往就是一个比较麻烦的事情,Re ...

  2. [vue]spa单页开发及vue-router基础

    - 了解spa页面跳转方式:(2种) spa: 单页跳转方式 开发(hash模式): https://www.baidu.com/#2313213 生产(h5利于seo): history.pushS ...

  3. Javascript 与 SPA单页Web富应用

    书单推荐 # <单页Web应用:JavaScript从前端到后端> http://download.csdn.net/detail/epubitbook/8720475 # <MVC ...

  4. 【读书笔记】WebApi 和 SPA(单页应用)--knockout的使用

    Web API从MVC4开始出现,可以服务于Asp.Net下的任何web应用,本文将介绍Web api在单页应用中的使用.什么是单页应用?Single-Page Application最常用的定义:一 ...

  5. 大熊君学习html5系列之------History API(SPA单页应用的必备)

    一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会" ...

  6. 基于VUE的SPA单页应用开发-加载性能篇

    1.基于异步数据的vue页面刷新 先看看基于异步数据的vue页面刷新后,都发生了啥- 如图所示: 图1 基于异步数据的vue页面刷新 网络请求图 步骤如下: step1:请求页面: step2:请求页 ...

  7. 前端学习之路之SPA(单页应用)设计原理

    SPA设计 1.设计意义 前后端分离 减轻服务器压力 增强用户体验 Prerender预渲染优化SEO 前后端分离:前端做业务逻辑,后端处理数据和接口,耦合度减少,开发效率提高. 减轻服务器压力:一个 ...

  8. 大熊君学习html5系列之------History API(SPA单页应用的必备------重构完结版)

    一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会" ...

  9. 3.【nuxt起步】-下面以一个SPA单页程序为例子

    spa:single page applcation 1.components目录新建header.vue,footer.vue Header.vue Footer.vue Pages/index.v ...

随机推荐

  1. 按钮滚动到指定位置(JS)

    function intval(v) { v = parseInt(v); return isNaN(v) ? 0 : v; } function getPos(e) { var l = 0; var ...

  2. IoC~MVC3+EF+Autofac实现松耦合的系统架构

    MVC3+EF+Autofac网上这种文章确实没有,呵呵,今天就写一个,代大家分享! 这个系列的文章将带我们进入一种新的开发模式,注入开发模式,或者叫它IOC模式,说起IOC你可以这样去理解它,它为你 ...

  3. listen 69

    Today Is Unlucky for People Who Have Bad Luck Today If you have Paraskevidekatriaphobia, today is no ...

  4. 1111 Online Map (30)(30 分)

    Input our current position and a destination, an online map can recommend several paths. Now your jo ...

  5. BZOJ_3744_Gty的妹子序列

    BZOJ3744: Gty的妹子序列 https://lydsy.com/JudgeOnline/problem.php?id=3744 分析: 预处理出来每一块块首到所有位置的逆序对数. 查询时主席 ...

  6. [HAOI 2011] Problem A

    [题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=2298 [算法] 考虑用总人数 - 最多人说真话 显然 , 对于每个人 , 如果他说的 ...

  7. openjudge 4116:拯救行动

    传送门 总时间限制:  1000ms 内存限制:  65536kB 描述 公主被恶人抓走,被关押在牢房的某个地方.牢房用N*M (N, M <= 200)的矩阵来表示.矩阵中的每项可以代表道路( ...

  8. 原生js实现Canvas实现拖拽式绘图,支持画笔、线条、箭头、三角形和圆形等等图形绘制功能,有实例Demo

    前言 需要用到图形绘制,没有找到完整的图形绘制实现,所以自己实现了一个 - - 演示地址:查看演示DEMO 新版本支持IE5+(你没看错,就是某软的IE浏览器)以上任意浏览器的Canvas绘图:htt ...

  9. 简单两步快速实现shiro的配置和使用,包含登录验证、角色验证、权限验证以及shiro登录注销流程(基于spring的方式,使用maven构建)

    前言: shiro因为其简单.可靠.实现方便而成为现在最常用的安全框架,那么这篇文章除了会用简洁明了的方式讲一下基于spring的shiro详细配置和登录注销功能使用之外,也会根据惯例在文章最后总结一 ...

  10. WPF Background的设置有坑

    今天帮忙同事解决在后台绑定时,动态更改控件(Grid)的Background. 有个陷阱,C#有2个命名空间有Brush和Color, 分别为System.Drawing和System.Window. ...