SPA单页应用先后分离微信受权

 

项目基于微信公众号开发,业务彻底依赖微信受权,也就是用户进入页面已经完成受权获取到用户的OpenId。前端

须要有一个受权中间页:author.vuevue

基本实现思路:

  • 不管使用哪一个url进入页面都会先触发router.beforeEach钩子。
  • 在router.beforeEach钩子函数中判断用户是否受权。
  • 若未受权则保存用户进入的url并请求后台接口获取微信受权(window.location.href=‘后台接口’)。
  • 后台调用微信接口受权获取用户信息及openId,将openId使用JWT生成一个惟一的token令牌,并将token已参数的形式拼接到url后面,而后重定向到前端author.vue页面。
  • author页面获取url中的token参数,将token参数保存到本地缓存。
  • 获取签名用户保存的url并跳转。

前端代码实现:

路由index.jsjava

// 全局守卫,微信受权
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.vueios

<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以及业务处理?json

基本实现思路:

  • 前端使用axios请求拦截器,判断本地缓存是否存在token,若是存在的话,则为每一个Http请求赋值token。
  • 后端使用拦截器拦截有@PassToken注解之外的方法,获取token值。若是token为null,直接返回错误码以及错误信息。
  • 验证token值是否有效,若有效,则解析openId,并将openId放入request中放行。如无效,直接返回错误码以及错误信息。
  • 拦截器放行,后端可直接经过request.getAttribute("openId")获取。

前端代码实现:

request.jsaxios

// 请求拦截器
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; }

 

到这里整个先后分离微信受权的流程就完了,但愿能够帮助到你们!!!服务器

相关文章
相关标签/搜索