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;
}

到这里整个前后分离微信授权的流程就完了,希望可以帮助到大家!!!

原文地址:https://www.cnblogs.com/itmrzhang/p/10997006.html

时间: 2024-07-31 23:14:28

SPA单页应用前后分离微信授权的相关文章

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

前端 SPA 单页应用数据统计解决方案 (ReactJS / VueJS) 一.百度统计的代码: UV PV 统计方式可能存在问题 在 SPA 的前端项目中 数据统计,往往就是一个比较麻烦的事情,React 和 Vue 也是一样. 在 发现问题之前,我们得来思考下 百度统计的 统计原理 是什么? 1-1: 百度统计代码 var _hmt = _hmt || []; (function () { var hm = document.createElement("script"); hm.

一、单页应用如何调用微信接口和手机端的一些方法?

因为angular 是单页应用,所以在调用一些手机端可微信接口的地方会很麻烦,这里总结一下在手机端调用分享和调用微信接口的时候出现的问题及解决的办法: 出现问题: 手机端的方法如果有回调,H5需要调用手机端某个方法的回调时,不管在哪个页面需要回调该方法都无法回调成功 如果做有关微信公众平台接口,在任何页面也是无法调用该接口 解决办法: 出现这种问题的原因是由于angular是单页应用引起的 解决方案:目前只想到一种解决办法就是:在index.html页面申明一个全局变量,然后将手机端或者微信接口

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

Web API从MVC4开始出现,可以服务于Asp.Net下的任何web应用,本文将介绍Web api在单页应用中的使用.什么是单页应用?Single-Page Application最常用的定义:一个最初内容只包含html和JavaScript,后续操作通过Restful风格的web服务传输json数据来响应异步请求的一个web应用.SPA的优势就是少量带宽,平滑体验,劣势就是只用JavaScript这些平滑的操作较难实现,不像MVC应用,我们可以异步form,partview.不用担心,我们

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

一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会"h5"能够做什么,以及在实际项目中如何去合理的运用达到使用自如,完美驾驭O(∩_∩)O~,好了,废话不多说,直接进入今天的主题, 今天主要讲的是“History API”及在单页应用中的作用,并且会引入一个实际的例子做为讲解的原型范例,先来看看“History API”: 为了提高Web页面的响应

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

一,开篇分析 Hi,大家好!大熊君又和大家见面了,(*^__^*) 嘻嘻……,这系列文章主要是学习Html5相关的知识点,以学习API知识点为入口,由浅入深的引入实例, 让大家一步一步的体会"h5"能够做什么,以及在实际项目中如何去合理的运用达到使用自如,完美驾驭O(∩_∩)O~,好了,废话不多说,直接进入今天的主题, 今天主要讲的是对昨天文章中的代码进行重构,并且相应的美化了一下前台UI界面,如下图所示的效果: 哈哈哈酷吧!继续让咱们做个简单的回顾: 为了提高Web页面的响应速度,越

spa(单页应用)中,使用history模式时,微信长按识别二维码在ios下失效的问题

spa(单页应用,vue)中,使用history模式时,微信长按识别二维码在ios下失效的问题. 触发条件: spa单页应用: 路由模式 history 从其他页面跳转到带有微信二维码识别的页面(不是直接打开该页面) ios版本的微信(实测版本6.5.19) 结果: 二维码长按无法识别,刷新页面后恢复正常,安卓下正常. 解决方案: 1. 进入该页面的方式不使用路由跳转,而改为 <a href="xxx">目标二维码页面</a>的方式: 2. 在beforeCre

快速了解SPA单页面应用

简要 SPA单页网页应用程序这个概念并不算新,早在2003年就已经有在讨论这个概念了,不过,单页应用这个词是到了2005年才有人提出使用,SPA的概念就和它的名字一样显而易懂,就是整个网站不再像传统的HTML网页一样,需要每做一个动作就更新一次网页,而是像传统的电脑软件一样,只变更显示的内容而不需变更整个网页!概念很简单,但是,事实上却有不少的问题要考虑. 理解单页面应用 简单来说SPA的网页只会有一个网页,而这个网页的设计方式要能够回应使用者所使用的各种装置并且复制使用者在电脑上使用软件的经验

单页应用 SPA(Sigle Page Aolication)

单页应用 SPA(Sigle Page Aolication) 优点: 1.具有桌面应用的即时性.网站的可移植性和可访问性. 2.用户体验好.快,内容的改变不需要重新加载整个页面,web应用更具响应性和更令人着迷. 3.基于上面一点,SPA相对对服务器压力小. 4.良好的前后端分离.SPA和RESTful架构一起使用,后端不再负责模板渲染.输出页面工作,web前端和各种移动终端地位对等,后端API通用化. 缺点: 1.不利于SEO. 2.初次加载耗时相对增多. 3.导航不可用,如果一定要导航需要

Vue单页式应用(Hash模式下)实现微信分享

前端微信分享的基本步骤: 一.绑定域名: 先登录微信公众平台进入"公众号设置"的"功能设置"里填写"JS接口安全域名".这个不多说,微信开发的都应该清楚. 二.引入js文件: 在需要调用JS接口的页面引入如下JS文件,(支持https):http://res.wx.qq.com/open/js/jweixin-1.0.0.js.请注意,如果你的页面启用了https,务必引入 https://res.wx.qq.com/open/js/jweixi