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


    初识微信开发

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

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

    企业微信三方开发(二):获取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=" 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返回值。这个是用户在应用内的唯一标识,需存入数据库用作用户登录的凭证。

  • 相关阅读:
    面试题:Spring的理解
    静态块与静态变量
    构造方法与构造块的执行顺序(区别于static)
    子类调用父类的构造函数几种情况
    使用json遇到的问题
    页面加载,使用ajax查询某个类别,并且给它们添加(拼接)连接
    对某个商品的上下架状态进行改变
    SSH后台管理系统,实现查询+分页
    跟上Java8
    java中值传递和引用传递
  • 原文地址:https://www.cnblogs.com/zxtceq/p/13927293.html
Copyright © 2020-2023  润新知