动手实践感受下企业微信的网页授权登录、扫码授权登录

一、网页授权登录:

1.使用内网穿透工具为“本地ip+端口”映射一个公网域名,比如:xxx.xxx.xxx.com 表示我本地的 127.0.0.1:8080。

2.申请一个测试用的企业微信,新建一个应用,比如叫:test_app_0001.

3.新建一个springboot工程:

企业微信的几个工具类:

QyOauthApi.java:

package com.test.qywechat.api;

import com.test.qywechat.httpclient.LocalHttpClient;
import com.test.qywechat.model.QyAuthUser;
import com.test.qywechat.model.QyAuthUserInfo;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class QyOauthApi extends QyWechatBaseApi{

	private static Logger logger = LoggerFactory.getLogger(QyOauthApi.class);
	/**
	 * 生成网页授权 URL  (第三方平台开发)
	 * @param appid appid
	 * @param redirect_uri 自动URLEncoder
	 * @param snsapi_userinfo snsapi_userinfo
	 * @param state 可以为空
	 * @param component_appid 第三方平台开发,可以为空。
	 * 			 服务方的appid,在申请创建公众号服务成功后,可在公众号服务详情页找到
	 * @return url
	 */
	public static String connectOauth2Authorize(String appid,String redirect_uri,boolean snsapi_userinfo,String state,String component_appid){
		try {
			StringBuilder sb = new StringBuilder();
			sb.append(OPEN_URI + "/connect/oauth2/authorize?")
					.append("appid=").append(appid)
					.append("&redirect_uri=").append(URLEncoder.encode(redirect_uri, "utf-8"))
					.append("&response_type=code")
					.append("&scope=").append(snsapi_userinfo?"snsapi_userinfo":"snsapi_base")
					.append("&state=").append(state==null?"":state);
			if(component_appid!=null){
				sb.append("&component_appid=").append(component_appid);
			}
			sb.append("#wechat_redirect");
			return sb.toString();
		} catch (UnsupportedEncodingException e) {
			logger.error("", e);
		}
		return null;
	}

	/**
	 * 获取企业微信当前登录账号
	 * @param accessToken access_token
	 * @param code 通过成员授权获取到的code,最大为512字节。每次成员授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。
	 * @return 登录账号
	 */
	public static QyAuthUser getAuthUserInfo(String accessToken, String code){
		HttpUriRequest httpUriRequest = RequestBuilder.get()
				.setUri(BASE_URI + "/cgi-bin/user/getuserinfo")
				.addParameter(PARAM_ACCESS_TOKEN, accessToken)
				.addParameter("code", code)
				.build();
		return LocalHttpClient.executeJsonResult(httpUriRequest,QyAuthUser.class);
	}

	/**
	 * 根据userId获取通讯录成员用户详情信息
	 * (注:应用须拥有指定成员的查看权限)
	 * @param accessToken access_token
	 * @param userId 登录账号
	 * @return 登录账号详情信息
	 */
	public static QyAuthUserInfo queryAuthUserInfo(String accessToken, String userId){
		HttpUriRequest httpUriRequest = RequestBuilder.get()
				.setUri(BASE_URI + "/cgi-bin/user/get")
				.addParameter(PARAM_ACCESS_TOKEN, accessToken)
				.addParameter("userid", userId)
				.build();
		return LocalHttpClient.executeJsonResult(httpUriRequest, QyAuthUserInfo.class);
	}
}

QyTokenApi.java:

package com.test.qywechat.api;

import com.test.qywechat.httpclient.LocalHttpClient;
import com.test.qywechat.model.QyToken;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;

public class QyTokenApi extends QyWechatBaseApi{

	public static QyToken token(String corpId, String secret){
		HttpUriRequest httpUriRequest = RequestBuilder.get()
				.setUri(BASE_URI + "/cgi-bin/gettoken")
				.addParameter("corpid", corpId)
				.addParameter("corpsecret", secret)
				.build();
		return LocalHttpClient.executeJsonResult(httpUriRequest,QyToken.class);
	}
}

QyWechatBaseApi.java:

package com.test.qywechat.api;

public class QyWechatBaseApi {

	protected static final String BASE_URI = "https://qyapi.weixin.qq.com";
	protected static final String OPEN_URI = "https://open.weixin.qq.com";
	protected static final String PARAM_ACCESS_TOKEN = "access_token";

}

QyAuthUser.java:

package com.test.qywechat.model;

import lombok.Data;

@Data
public class QyAuthUser extends QyBaseResult {

	private String userId;

	private String deviceId;

	@Override
	public String toString() {
		return "QyAuthUser [userId=" + userId + ", deviceId=" + deviceId + ", errcode=" + errcode + ", errmsg=" + errmsg + "]";
	}
}


QyAuthUserInfo.java:

package com.test.qywechat.model;

import lombok.Data;

@Data
public class QyAuthUserInfo extends QyBaseResult {

	private String userid;
	private String name;
	private String mobile;
	private Integer[] department;
	private String position;
	//0表示未定义,1表示男性,2表示女性
	private String gender;
	private String email;
	private String status;
	private String main_department;

}


QyBaseResult.java:

package com.test.qywechat.model;

import lombok.Data;

@Data
public class QyBaseResult {

	private static final String SUCCESS_CODE = "0";

	public String errcode;
	public String errmsg;

	public boolean isSuccess() {
		return errcode == null || errcode.isEmpty() || errcode.equals(SUCCESS_CODE);
	}

}


QyToken.java:

package com.test.qywechat.model;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class QyToken extends QyBaseResult {

	private String access_token;
	private int expires_in;
}

HttpClientFactory.java:

package com.test.qywechat.httpclient;

import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.UnknownHostException;
import java.security.*;

/**
 * httpclient 4.3.x
 *
 */
public class HttpClientFactory{
	
	private static final String[] supportedProtocols = new String[]{"TLSv1"};
	
	public static CloseableHttpClient createHttpClient() {
		return createHttpClient(100,10,5000,2);
	}

	/**
	 * 
	 * @param maxTotal
	 * @param maxPerRoute
	 * @param timeout
	 * @param retryExecutionCount
	 * @return
	 */
	public static CloseableHttpClient createHttpClient(int maxTotal, int maxPerRoute, int timeout, int retryExecutionCount) {
		try {
			SSLContext sslContext = SSLContexts.custom().useSSL().build();
			SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(sslContext, SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
			PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager();
			poolingHttpClientConnectionManager.setMaxTotal(maxTotal);
			poolingHttpClientConnectionManager.setDefaultMaxPerRoute(maxPerRoute);
			SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(timeout).build();
			poolingHttpClientConnectionManager.setDefaultSocketConfig(socketConfig);
			return HttpClientBuilder.create()
									.setConnectionManager(poolingHttpClientConnectionManager)
									.setSSLSocketFactory(sf)
									.setRetryHandler(new HttpRequestRetryHandlerImpl(retryExecutionCount))
									.build();
		} catch (KeyManagementException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Key store 类型HttpClient
	 * @param keystore
	 * @param keyPassword
	 * @return
	 */
	public static CloseableHttpClient createKeyMaterialHttpClient(KeyStore keystore, String keyPassword, int timeout, int retryExecutionCount) {
		return createKeyMaterialHttpClient(keystore, keyPassword, supportedProtocols,timeout,retryExecutionCount);
	}
	
	/**
	 * Key store 类型HttpClient
	 * @param keystore
	 * @param keyPassword
	 * @param supportedProtocols
	 * @return
	 */
	public static CloseableHttpClient createKeyMaterialHttpClient(KeyStore keystore, String keyPassword, String[] supportedProtocols, int timeout, int retryExecutionCount) {
		try {
			SSLContext sslContext = SSLContexts.custom().useSSL().loadKeyMaterial(keystore, keyPassword.toCharArray()).build();
			SSLConnectionSocketFactory sf = new SSLConnectionSocketFactory(sslContext,supportedProtocols,
	                null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
			SocketConfig socketConfig = SocketConfig.custom().setSoTimeout(timeout).build();
			return HttpClientBuilder.create()
									.setDefaultSocketConfig(socketConfig)
									.setSSLSocketFactory(sf)
									.setRetryHandler(new HttpRequestRetryHandlerImpl(retryExecutionCount))
									.build();
		} catch (KeyManagementException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (UnrecoverableKeyException e) {
			e.printStackTrace();
		} catch (KeyStoreException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * HttpClient  超时重试
	 */
	private static class HttpRequestRetryHandlerImpl implements HttpRequestRetryHandler {
		
		private int retryExecutionCount;
		
		public HttpRequestRetryHandlerImpl(int retryExecutionCount){
			this.retryExecutionCount = retryExecutionCount;
		}
		
		@Override
	    public boolean retryRequest(
	            IOException exception,
	            int executionCount,
	            HttpContext context) {
			if (executionCount > retryExecutionCount) {
	            return false;
	        }
	        if (exception instanceof InterruptedIOException) {
	            return false;
	        }
	        if (exception instanceof UnknownHostException) {
	            return false;
	        }
	        if (exception instanceof ConnectTimeoutException) {
	            return true;
	        }
	        if (exception instanceof SSLException) {
	            return false;
	        }
	        HttpClientContext clientContext = HttpClientContext.adapt(context);
	        HttpRequest request = clientContext.getRequest();
	        boolean idempotent = !(request instanceof HttpEntityEnclosingRequest);
	        if (idempotent) {
	            // Retry if the req is considered idempotent
	            return true;
	        }
	        return false;
	    }

	}
	
}


JsonResponseHandler.java:

package com.test.qywechat.httpclient;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.util.EntityUtils;

import java.util.Map;

public class JsonResponseHandler{

	private static Map<String, ResponseHandler<?>> map = Maps.newHashMap();

	@SuppressWarnings("unchecked")
	public static <T> ResponseHandler<T> createResponseHandler(final Class<T> clazz){

		if(map.containsKey(clazz.getName())){
			return (ResponseHandler<T>)map.get(clazz.getName());
		}else{
			ResponseHandler<T> responseHandler = response -> {
				int status = response.getStatusLine().getStatusCode();
				if (status >= 200 && status < 300) {
					HttpEntity entity = response.getEntity();
					String str = EntityUtils.toString(entity);
					return JSONObject.parseObject(str, clazz);
				} else {
					throw new ClientProtocolException("Unexpected res status: " + status);
				}
			};
			map.put(clazz.getName(), responseHandler);
			return responseHandler;
		}
	}

}


LocalHttpClient.java:
 

package com.test.qywechat.httpclient;

import org.apache.http.HttpEntity;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.Map;

public class LocalHttpClient {
	
	private static final Logger logger = LoggerFactory.getLogger(LocalHttpClient.class);
	
	private static int timeout = 5000;
	
	private static int retryExecutionCount = 2;

	protected static CloseableHttpClient httpClient = HttpClientFactory
			.createHttpClient(100,10,timeout,retryExecutionCount);

	private static Map<String, CloseableHttpClient> httpClient_mchKeyStore = new HashMap<String, CloseableHttpClient>();
	
	/**
	 * @since 2.7.0
	 * @param timeout
	 */
	public static void setTimeout(int timeout) {
		LocalHttpClient.timeout = timeout;
	}

	/**
	 * @since 2.7.0
	 * @param retryExecutionCount
	 */
	public static void setRetryExecutionCount(int retryExecutionCount) {
		LocalHttpClient.retryExecutionCount = retryExecutionCount;
	}

	public static void init(int maxTotal,int maxPerRoute){
		try {
			httpClient.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		httpClient = HttpClientFactory.createHttpClient(maxTotal,maxPerRoute,timeout,retryExecutionCount);
	}

	/**
	 * 初始化   MCH HttpClient KeyStore
	 * @param mch_id
	 * @param keyStoreFilePath
	 */
	public static void initMchKeyStore(String mch_id,String keyStoreFilePath){
		try {
			KeyStore keyStore = KeyStore.getInstance("PKCS12");
			 FileInputStream instream = new FileInputStream(new File(keyStoreFilePath));
			 keyStore.load(instream,mch_id.toCharArray());
			 instream.close();
			 CloseableHttpClient httpClient = HttpClientFactory
					 .createKeyMaterialHttpClient(keyStore, mch_id,timeout,retryExecutionCount);
			 httpClient_mchKeyStore.put(mch_id, httpClient);
		} catch (KeyStoreException e) {
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (CertificateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	public static CloseableHttpResponse execute(HttpUriRequest request){
		loggerCatch(request);
		try {
			return httpClient.execute(request, HttpClientContext.create());
		} catch (Exception e) {
			e.printStackTrace();
			logger.error(e.getMessage());
		}
		return null;
	}

	public static <T> T execute(HttpUriRequest request, ResponseHandler<T> responseHandler){
		loggerCatch(request);
		try {
			return httpClient.execute(request, responseHandler, HttpClientContext.create());
		} catch (Exception e) {
			e.printStackTrace();
			logger.error(e.getMessage());
		}
		return null;
	}

	/**
	 * 数据返回自动JSON对象解析
	 * @param request
	 * @param clazz
	 * @return
	 */
	public static <T> T executeJsonResult(HttpUriRequest request, Class<T> clazz){
		return execute(request,JsonResponseHandler.createResponseHandler(clazz));
	}


	
	/**
	 * 日志记录
	 * @param request
	 */
	private static void loggerCatch(HttpUriRequest request){
		if((logger.isInfoEnabled()||logger.isDebugEnabled())){
			if(request instanceof HttpEntityEnclosingRequestBase){
				HttpEntityEnclosingRequestBase request_base = (HttpEntityEnclosingRequestBase)request;
				HttpEntity entity = request_base.getEntity();
				String content = null;
				//MULTIPART_FORM_DATA 请求类型判断
				if(entity.getContentType().toString().indexOf(ContentType.MULTIPART_FORM_DATA.getMimeType()) == -1){
					try {
						content = EntityUtils.toString(entity);
					} catch (Exception e) {
						e.printStackTrace();
						logger.error(e.getMessage());
					}
				}
				logger.info("URI:{} {} ContentLength:{} Content:{}",
				request.getURI().toString(),
				entity.getContentType(),
				entity.getContentLength(),
				content == null?"multipart_form_data":content);
			}else{
				logger.info("URI:{}",request.getURI().toString());
			}
		}
	}
	
}

TestController.java:

package com.test.controller;

import com.alibaba.fastjson.JSONObject;
import com.test.qywechat.model.QyAuthUser;
import com.test.qywechat.api.QyOauthApi;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

@Slf4j
@RestController
@RequestMapping("/test")
@Api(value = "测试Controller", description = "测试Controller")
public class TestController {

    private static final String corpId = "xxxxx";
    private static final String secret_test_app_0001 = "xxx-xxxx";

    /**
     * 也即时用来根据 code 查询用户信息的
     * @param action
     * @param code
     * @param state
     * @return
     */
    @ApiOperation(value = "企业微信code回调")
    @GetMapping( "/receive/qywx/code" )
    public String receiveQywxCode( @RequestParam( "action" ) String action,
                                   @RequestParam( "code" ) String code,
                                   @RequestParam( "state" ) String state){
        System.out.println( "action = " + action );
        System.out.println( "code = " + code );
        System.out.println( "state = " + state );
        // ps:调用 QyTokenApi.token( code,secret_test_app_0001 ).getAccess_token() 获取的,实际需要缓存起来,防止频繁调用
        String accessToken = "xxx-xxx-xxx-xxx-xxx-xxx";
        QyAuthUser userInfo = QyOauthApi.getAuthUserInfo(accessToken, code);
        // userId 就是企业微信控制台--》通讯录--》成员详情 中的账号字段
        String userId = userInfo.getUserId();
        String deviceId = userInfo.getDeviceId();
        JSONObject result = new JSONObject();
        result.put( "姓名",userId );
        result.put( "设备编码",deviceId );
        return result.toJSONString();
    }

    @ApiOperation(value = "构造网页授权链接")
    @GetMapping( "/oauth2_link" )
    public String oauth2_link( ){
        String redirect_uri = "http://xxx.xxx.xxx.com/test/receive/qywx/code?action=get";
        String click_url = QyOauthApi.connectOauth2Authorize(corpId, redirect_uri, false, null,null);
        System.out.println( "click_url is " + click_url );
        String html = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n" +
                        "        \"http://www.w3.org/TR/html4/loose.dtd\">\n" +
                        "<html>\n" +
                        "<head>\n" +
                        "    <meta charset=\"utf-8\">\n" +
                        "    <meta http-equiv=\"refresh\" content=\"0; URL=" + click_url + "\">\n" +
                        "    <title>个人信息</title>\n" +
                        "</head>\n" +
                        "<body>\n" +
                        "    loading..." +
                        "</body>\n" +
                        "</html>";
        return html;
    }
}

企业微信管理端为应用“test_app_0001”新建一个菜单,菜单内容设置为“跳转到网页”,网址设置为 http://xxx.xxx.xxx.com/test/oauth2_link,其中xxx.xxx.xxx.com也就是为127.0.0.1:8080映射的公网域名,供微信回调使用, /test/oauth2_link为TestController中的接口:

为应用"test_app_0001"设置网页授权可信域名:

在手机端企业微信打开应用“test_app_0001”,点击菜单“个人信息”,就查询出了当前的用户的信息了:

二、在自己的web页面实现企业微信扫码登录:

1.在企业微信管理端对应应用中设置企业微信授权登录授权回调域:

2.TestController 中新增两个接口:

    @ApiOperation(value = "企业微信授权登录二维码")
    @GetMapping( "/show_qr_code" )
    public String showQrCode( ) throws UnsupportedEncodingException {
        System.out.println( "request /test/sso_qr_connect_link" );
        String redirect_uri = "http://" + domain_ngrok_mock + "/test/home?action=get";
        String sso_qr_connect_link = "https://open.work.weixin.qq.com/wwopen/sso/qrConnect?" +
                                        "appid=" + corpId + "&" +
                                        "agentid=" + agentId_test_app_0001 + "&" +
                                        "redirect_uri=" + URLEncoder.encode(redirect_uri, "utf-8") + "&" +
                                        "state=";
        System.out.println( "sso_qr_connect_link is " + sso_qr_connect_link );
        String html = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n" +
                "        \"http://www.w3.org/TR/html4/loose.dtd\">\n" +
                "<html>\n" +
                "<head>\n" +
                "    <meta charset=\"utf-8\">\n" +
                // todo 作用是该页面被打开后立即重定向到 click_url
                "    <meta http-equiv=\"refresh\" content=\"0; URL=" + sso_qr_connect_link + "\">\n" +
                "    <title>个人信息</title>\n" +
                "</head>\n" +
                "<body>\n" +
                "    loading..." +
                "</body>\n" +
                "</html>";
        return html;
    }

    @ApiOperation(value = "测试主页")
    @GetMapping( "/home" )
    public String home( @RequestParam( "code" ) String code,
                        @RequestParam( value = "state",required = false ) String state ){
        System.out.println( "request /test/home" );
        System.out.println( "code = " + code );
        System.out.println( "state = " + state );
        // ps:调用 QyTokenApi.token( code,secret_test_app_0001 ).getAccess_token() 获取的,实际需要缓存起来,防止频繁调用
        String accessToken = "xxx-xxx-xxx-xxx";
        QyAuthUser userInfo = QyOauthApi.getAuthUserInfo(accessToken, code);
        String userId = userInfo.getUserId();
        QyAuthUserInfo userDetailInfo = QyOauthApi.queryAuthUserInfo(accessToken, userId);
        return "welcome you! " + JSONObject.toJSONString( userDetailInfo );
    }

效果:

 

 

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值