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

 

到這裏整個先後分離微信受權的流程就完了,但願能夠幫助到你們!!!服務器

相關文章
相關標籤/搜索