Document

企业微信三方开发(三):网页授权登录

初识微信开发

企业微信三方开发:注册企业微信服务商

企业微信三方开发(一):回调验证及重要参数获取

企业微信三方开发(二):获取access_token

企业微信三方开发(三):网页授权登录

企业微信三方开发(四):发送消息

企业微信三方开发(五):扫码登录

 

 

前言一

  1. 企业微信登录分两种:网页授权登录和扫码授权登录。
    区别除了字面意思一个需要扫码一个不需要扫码外。还一个重要应用上的区别就是网页授权登录必须在微信客户端完成。
    此文是关于如何实现网页授权登录的。

  2. 登录的整体逻辑分三段,从用户点击登录按钮开始:

    • 点击登录按钮,访问OAuth2网页授权链接获取微信授权code【前端处理】
    • 通过授权code换取用户成员票据user_ticket在内的用户信息【后端处理】
    • 通过user_ticket获取用户敏感信息【后端处理】
  3. 这里OAuth2网页授权链接获取授权code在前端完成。我们前端使用uni-app框架,其好处是搭建方便、支持跨端、语言为现在很热门的vue。

  4. 微信企业三方开发访问授权链接不支持本地调试,官方意见是自己搭建线上调试平台,然后通过企业微信访问调试。这里我就只好用我自己线上的服务器和域名进行开发调试。

技术栈及工具

  • 前端开发框架:uni-app
  • 前端开发工具:HBulider
  • 后端开发框架:spring-boot
  • 后端开发工具: idea

一、OAuth2到底是什么?

在做授权登录时,无论是微信还企业微信,包括许多其它厂的登录。我们都会碰到一个概念——OAuth

通常也写作OAuth2.0,其中2.0是他的版本号

1.1、首先要对授权登录有个正确的概念

通常说到登录,就涉及两方:用户 和 应用

比如我在使用微信,那么我就是用户,微信app就是应用。
登录逻辑也很简单:我只需要注册用户名及密码到微信服务器,即可用用户名和密码登录

而授权登录则涉及三方: 用户,认证服务器 ,应用

比如我想使用CSDN,需要注册登录。如果我直接选择用我的微信账号登录,这就是一个授权登录过程。其中用户就是我,认证服务器就是微信的服务器,应用则是CSDN

其中微信的授权登录就是采用的OAuth 2.0授权码模式

1.2、OAuth2.0的授权码模式

OAuth是一套授权模式的统称,其中用的最多的就是授权码模式。
在这里插入图片描述
上图就是OAuth2.0授权码模式的流程,我们再结合微信授权登录CSDN的情形讲解一下:

  1. 首先我点击CSDN的微信登录按钮,CSDN服务器会构造OAuth2链接(具体链接参看微信文档,参数包括CSDN的身份ID,以及重定向URI,这个URI也就是CSDN登录成功的页面),进入到微信的授权页
  2. 我在微信的授权页点击同意授权,微信会跳转到重定向URI,也就是CSDN登录成功页并附上授权码
  3. 进入登录成功页时,CSDN服务器会拿着授权码以及调用凭证AccessToken(由微信指定接口获取)向微信服务器获取我的基本信息。整个授权过程完成。

可以看到OAuth2.0很明显的优点,CSDN完全不用知道更不需要存储我的微信账号和密码,就能判断我的微信就是我的微信,即我就是我!

二、网页授权

明白了OAuth2.0,下面构建OAuth2.0链接以及其中的参数也就不需要多讲。

2.1、新建uni-app项目

因为正好在用uni-app做小程序,所以这里就选择了这个框架,具体创建方式直接查看官方教程,这里我们已经创建了一个叫easyou-agency的项目。

这里前端用什么框架不重要,关键是需要发送http请求

2.2、 构建网页授权链接

接口文档

在这里插入图片描述
在这里插入图片描述

注意参数 scope,主要用于设置手动授权还是静默授权。 区别就是授权时需不需要点击授权按钮。

如果选择手动授权,需给应用配置权限
在这里插入图片描述

接下来继续写代码,先新建两个页面,index和login
在这里插入图片描述
login页面需要访问构建的URL:

<template>
	<view style="width: 96%;margin: 0 auto;">
		<view>
			 <form @submit="doLogin">
				<button plain="true" class="loginBtn" lang="zh_CN" form-type="submit">登录</button>
			 </form>
		</view>
	</view>
</template>

<script>

	export default {
		data() {
			return {
				usercode: '',
				password: '',
				passwordHidden: true
			}
		},
		onLoad:function(){

		},
		methods: {
			doLogin() {
				let _this = this
				localStorage.setItem("hasLogin",true)
				// 这里放自己的域名
				let redirect_uri = encodeURI("www.xxxx.com")
				let authorUrl = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=ww8273b1801b97c577&redirect_uri='+redirect_uri+'&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect'
				window.location.href = authorUrl
			}
		}
	}
</script>

<style>
	.loginBtn{
		color: #337bd4!important;
		border:1rpx solid #337bd4!important;
		font-size: 30rpx;
		border-radius: 50rpx;
	}
</style>

redirect_uri 用的是我线上的域名,也就是我安装好的应用的主页url。在应用详情里配置:在这里插入图片描述

index页是打开应用时缺省进入的页面,需要做三件事:

  1. 通过storage判断是否是登录状态,不是回到login页
  2. 再判断url参数是否带code
  3. 如果带code说明是网页授权过来的,将code发给后台换取成员票据user_ticket和用户信息

index页如下:

<template>
	<view class="content">
		<image class="logo" src="/static/logo.png"></image>
		<view class="text-area">
			<text class="title">{{title}}</text>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				title: this.$route.query.code
			}
		},
		onLoad() {
			if(localStorage.getItem("hasLogin")=='false'){
				uni.redirectTo({
				    url: '../login/login'
				});
			}
			
			if(this.$route.query.code){
				// 从静模授权进入首页				
				localStorage.setItem("auth_code",this.$route.query.code)
				
				this.getuserinfo3rd()
			} 

		},
		methods: {
		    // 获取敏感信息
			getuserinfo3rd:function(){
				uni.request({
				   url: 'http://tantan.vaiwan.com/login/getuserinfo3rd.do?code='+this.$route.query.code,
				   method:'POST',
				   success: function (res) {
						console.log("res:"+res);
				   }
				})
			}
		}
	}
</script>

<style>
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}

	.logo {
		height: 200rpx;
		width: 200rpx;
		margin-top: 200rpx;
		margin-left: auto;
		margin-right: auto;
		margin-bottom: 50rpx;
	}

	.text-area {
		display: flex;
		justify-content: center;
	}

	.title {
		font-size: 36rpx;
		color: #8f8f94;
	}
</style>

自此前端部分就完成了,生成发行版本并上传至自己服务器,并配置好域名指向index。
在这里插入图片描述

企业微信能够成功访问index,由于storage的登录状态为false,所以跳转到login页面

企业微信网页应用有前端调试插件,安装方法查看官方教程

点击登录按键:
在这里插入图片描述

成功跳转到首页

在这里插入图片描述

请求链接携带code,说明构建网页授权链接成功!

如果是手动授权,则会先跳转到授权页:
在这里插入图片描述

此时就要手动点即授权按钮授权

三、获取用户信息及敏感信息

在1.2中我们在index页中有个getuserinfo3rd函数向后台发送ajax请求,参数携带了网页授权成功的code。此时我们就要响应并通过code获取用户票据user_ticket,再通过user_ticket获取用户敏感信息。

新建一个LoginController:
在这里插入图片描述

package com.tan.cwp.controller;

import com.tan.cwp.util.HttpHelper;
import com.tan.cwp.util.PropertiesUtil;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;


@RestController
@RequestMapping("/login")
public class LoginController {
    /**
     * 获取访问用户userid
     *
     * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/service/getuserinfo3rd?suite_access_token=SUITE_ACCESS_TOKEN&code=CODE
     * 请求方式: GET
     * @parm1 SUITE_ACCESS_TOKEN
     * @parm2 oauth2授权成功返回的code
     */
    @RequestMapping(value = "getuserinfo3rd.do" ,method = RequestMethod.POST)
    public static void getUserInfo3rd(HttpServletRequest request,HttpServletResponse response) throws IOException, JSONException {

        String code = request.getParameter("code");

        String suite_access_token = PropertiesUtil.getProperty("suite_access_token");

        String url = "https://qyapi.weixin.qq.com/cgi-bin/service/getuserinfo3rd?suite_access_token="+suite_access_token+"&code="+code;

        JSONObject jsonObj = HttpHelper.doGet(url);

        JSONObject result = getUserDetail3rd((String) jsonObj.get("user_ticket"));

        // 将信息发送至前台
        PrintWriter out = response.getWriter();
        out.print(result);
        out.close();
    }

    /**
     * 获取访问用户敏感信息
     *
     * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/service/getuserdetail3rd?suite_access_token=SUITE_ACCESS_TOKEN
     * 请求方式: POST
     * @parm1 SUITE_ACCESS_TOKEN
     * @parm2 getUserInfo3rd获得的user_ticket
     */
    public static JSONObject getUserDetail3rd(String user_ticket) throws JSONException, IOException {
        String suite_access_token = PropertiesUtil.getProperty("suite_access_token");
        String url = "https://qyapi.weixin.qq.com/cgi-bin/service/getuserdetail3rd?suite_access_token="+suite_access_token;

        JSONObject jsonParms = new JSONObject();
        jsonParms.put("user_ticket", user_ticket);
        JSONObject jsonObj = HttpHelper.doPost(url,jsonParms);

        return jsonObj;
    }
}

通过两个函数先后获取用户普通信息和敏感信息。

保存并重新运行

回到企业微信点击登录按钮发现报错了:
在这里插入图片描述

一个标标准准的跨域错误

这里的调用域名是我线上的域名www.xxxx.com,被调用域名是我本地的 http://tantan.vaiwan.com,所以跨域了。解决跨域通常有两种思路:

  • 从调用方出发:通过http服务器(nginx或apache)将被调用域名反向代理到同一个域名
  • 从被调用方出发:通过过滤器增加请求头,允许指定域名可以跨域

我们选择第二种方法新增过滤器。

新增filter包,加个叫CrosFilter的过滤器:
在这里插入图片描述

package com.tan.cwp.filter;

import javax.servlet.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CrosFilter implements javax.servlet.Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {

        HttpServletResponse res = (HttpServletResponse)servletResponse;

        // 允许所有域名跨域
        res.addHeader("Access-Control-Allow-Origin","*");
        res.addHeader("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS");
        res.addHeader("Access-Control-Allow-Headers","Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

在 CwpApplication 中将过滤器配上:
在这里插入图片描述

package com.tan.cwp;
import com.tan.cwp.filter.CrosFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class CwpApplication {

    public static void main(String[] args) {
        SpringApplication.run(CwpApplication.class, args);
    }

    @Bean
    public FilterRegistrationBean registerFilter() {
        FilterRegistrationBean bean = new FilterRegistrationBean();

        // 过滤所有访问
        bean.addUrlPatterns("/*");
        bean.setFilter(new CrosFilter());
        return bean;
    }
}

重启项目,企业微信点击登录:
在这里插入图片描述

成功获取到用户普通信息和敏感信息!

总结

在获取用户信息中,有个open_userid返回值。这个是用户在应用内的唯一标识,需存入数据库用作用户登录的凭证。

posted @ 2020-11-04 18:20  从未被超越  阅读(9095)  评论(0编辑  收藏  举报