最近在做一个项目时,接到一个需求。需要在企业微信中自建应用实现免密登陆到第三方应用,在网上查阅资料一番后,便开始实现,下面是我的大致流程,因是第一次做,所有不免有不准确的地方,还请大家多多指正。
第一步、企业微信自建应用,获取企ID(corpId),AgentId(agentId),Serect(coprsecret)
1.需要以管理员身份登录企业微信管理端后台
链接:企业微信
2.查看企业ID
这个相当于企业微信的唯一识别号,每个企业都有一个唯一的企业ID,在我的企业-企业信息中可以查看,记录下来,后面需要用到。
3.自建应用
应用管理-应用-自建应用(最下面)
填写应用信息
应用创建完之久就可以查看AgentId,Secret
不过Secret需要下发手机端查看
第二步、后端代码实现
1.需要用到的依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.71</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.16</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.14</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
2.发送http请求工具类
import com.alibaba.fastjson.JSONObject;
import com.oz.kerui.web.weixin.model.AccessToken;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import java.io.*;
import java.net.URL;
import java.text.MessageFormat;
/**
* @Description 微信工具使用
* @Author whl
* @Date 2023/12/17 16:20
**/
@Slf4j
public class WeChatUtil {
/**
* 微信的请求url 获取access_token的接口地址(GET) 限200(次/天)
*/
public final static String access_token_url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={0}&corpsecret={1}";
/**
* 1.发起https请求并获取结果
*
* @param requestUrl 请求地址
* @param requestMethod 请求方式(GET、POST)
* @param outputStr 提交的数据
* @return JSONObject(通过JSONObject.get ( key)的方式获取json对象的属性值)
*/
public static JSONObject httpRequest(String requestUrl, String requestMethod, String outputStr) {
JSONObject jsonObject = null;
StringBuffer buffer = new StringBuffer();
try {
// 创建SSLContext对象,并使用我们指定的信任管理器初始化
TrustManager[] tm = {new MyX509TrustManager()};
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(null, tm, new java.security.SecureRandom());
// 从上述SSLContext对象中得到SSLSocketFactory对象
SSLSocketFactory ssf = sslContext.getSocketFactory();
URL url = new URL(requestUrl);
HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();
httpUrlConn.setSSLSocketFactory(ssf);
httpUrlConn.setDoOutput(true);
httpUrlConn.setDoInput(true);
httpUrlConn.setUseCaches(false);
// 设置请求方式(GET/POST)
httpUrlConn.setRequestMethod(requestMethod);
if ("GET".equalsIgnoreCase(requestMethod)) {
httpUrlConn.connect();
}
// 当有数据需要提交时
if (null != outputStr) {
OutputStream outputStream = httpUrlConn.getOutputStream();
// 注意编码格式,防止中文乱码
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 将返回的输入流转换成字符串
InputStream inputStream = httpUrlConn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
bufferedReader.close();
inputStreamReader.close();
// 释放资源
inputStream.close();
httpUrlConn.disconnect();
jsonObject = JSONObject.parseObject(buffer.toString());
return jsonObject;
} catch (Exception e) {
log.error("异常信息", e);
return null;
}
}
/**
* 微信用户登录,登录成功后,返回相应的token 值
*
* @param appid 机构id
* @param appsecret 密码
* @return 微信用户登录,登录成功后,返回相应的token 值
*/
public static AccessToken getAccessToken(String appid, String appsecret) {
AccessToken accessToken = null;
String requestUrl = MessageFormat.format(access_token_url, appid, appsecret);
JSONObject jsonObject = httpRequest(requestUrl, "GET", null);
// 如果请求成功
if (null != jsonObject) {
try {
accessToken = new AccessToken();
accessToken.setToken(jsonObject.getString("access_token"));
accessToken.setExpiresIn(jsonObject.getInteger("expires_in"));
} catch (Exception e) {
accessToken = null;
// 获取token失败
log.error("获取token失败 errcode:{} errmsg:{}",
jsonObject.getInteger("errcode"), jsonObject.getString("errmsg"));
return accessToken;
}
}
return accessToken;
}
/**
* 向指定 URL 发送POST方法的请求
*
* @param url
* 发送请求的 URL
* @param jsonData
* 请求参数,请求参数应该是Json格式字符串的形式。
* @return 所代表远程资源的响应结果
*/
public static String sendJsonPost(String url, String jsonData) {
PrintWriter out = null;
BufferedReader in = null;
String result = "";
try {
HttpClient client = new HttpClient(); // 客户端实例化
PostMethod postMethod = new PostMethod(url); // 请求方法post,可以将请求路径传入构造参数中
postMethod.addRequestHeader("Content-type", "application/json; charset=utf-8");
byte[] requestBytes = jsonData.getBytes("utf-8"); // 将参数转为二进制流
InputStream inputStream = new ByteArrayInputStream(requestBytes, 0,requestBytes.length);
// 请求体
RequestEntity requestEntity = new InputStreamRequestEntity(inputStream,requestBytes.length, "application/json; charset=utf-8");
postMethod.setRequestEntity(requestEntity); // 将参数放入请求体
int i = client.executeMethod(postMethod); // 执行方法
System.out.println("请求状态"+i);
// 这里因该有判断的,根据请求状态判断请求是否成功,然后根据第三方接口返回的数据格式,解析出我们需要的数据
byte[] responseBody = postMethod.getResponseBody(); // 得到相应数据
String s = new String(responseBody);
System.out.println(s);
} catch (Exception e) {
System.out.println("发送 POST 请求出现异常!" + e);
e.printStackTrace();
}
// 使用finally块来关闭输出流、输入流
finally {
try {
if (out != null) {
out.close();
}
if (in != null) {
in.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
return result;
}
}
HttpUtil
import cn.hutool.core.map.MapUtil;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.methods.RequestBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* http请求工具类
*
* @author wangfenglei
*/
public class HttpUtil {
private static Logger log = LoggerFactory.getLogger(HttpUtil.class);
private static PoolingHttpClientConnectionManager connectionManager = null;
private static HttpClientBuilder httpBuilder = null;
private static RequestConfig requestConfig = null;
/**
* 最大连接连接数量
*/
private static int MAX_CONNECTION = 100;
/**
* 最大并发请求数量
*/
private static int DEFAULT_MAX_CONNECTION = 50;
static {
// 设置http的状态参数
requestConfig = RequestConfig.custom().setSocketTimeout(10000).setConnectTimeout(10000)
.setConnectionRequestTimeout(10000).build();
connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(MAX_CONNECTION);
connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTION);
httpBuilder = HttpClients.custom();
httpBuilder.setConnectionManager(connectionManager);
}
/**
* 获取http客户端连接
*
* @return http客户端连接
*/
public static CloseableHttpClient getConnection() {
return httpBuilder.build();
}
/**
* http post请求,利用http请求池
*
* @param url 请求url
* @param paramsMap 请求参数
* @return 请求结果
* @throws Exception 异常
*/
public static String httpPost(String url, Map<String, String> paramsMap) throws Exception {
List<NameValuePair> params = new ArrayList<>();
for (Map.Entry<String, String> e : paramsMap.entrySet()) {
NameValuePair pair = new BasicNameValuePair(e.getKey(), e.getValue());
params.add(pair);
}
HttpUriRequest postMethod = RequestBuilder.post().setUri(url)
.addParameters(params.toArray(new BasicNameValuePair[params.size()])).setConfig(requestConfig).build();
HttpResponse response = getConnection().execute(postMethod);
return EntityUtils.toString(response.getEntity());
}
/**
* http post请求,利用http请求池
*
* @param url 请求url
* @param paramsMap 请求参数
* @param urlParams url请求参数
* @return 请求结果
* @throws Exception 异常
*/
public static String httpPost(String url, Map<String, String> paramsMap, Map<String, Object> urlParams) throws Exception {
List<NameValuePair> params = new ArrayList<>();
for (Map.Entry<String, String> e : paramsMap.entrySet()) {
NameValuePair pair = new BasicNameValuePair(e.getKey(), e.getValue());
params.add(pair);
}
StringBuilder sb = new StringBuilder("?");
for (Map.Entry<String, Object> e : urlParams.entrySet()) {
sb.append(e.getKey()).append("=").append(e.getValue()).append("&");
}
if (urlParams!= null&&urlParams.size()!=0){
url = url + sb.toString();
}
HttpUriRequest postMethod = RequestBuilder.post().setUri(url)
.addParameters(params.toArray(new BasicNameValuePair[params.size()])).setConfig(requestConfig).build();
HttpResponse response = getConnection().execute(postMethod);
return EntityUtils.toString(response.getEntity());
}
/**
* http post请求,利用http请求池
*
* @param url 请求url
* @param jsonStr json字符串
* @return 请求结果
* @throws Exception 异常
*/
public static String httpPostJson(String url, String jsonStr) throws Exception {
HttpUriRequest postMethod = RequestBuilder.post().setUri(url)
.setHeader("Content-Type", "application/json;charset=utf-8").setHeader("Accept", "application/json")
.setEntity(new StringEntity(jsonStr, Charset.forName("UTF-8"))).setConfig(requestConfig).build();
HttpResponse response = getConnection().execute(postMethod);
return EntityUtils.toString(response.getEntity());
}
/**
* http post请求,利用http请求池,设置head内容
*
* @param url 请求url
* @param jsonStr json字符串
* @param head head内容
* @return 请求结果
* @throws Exception 异常
*/
public static String httpPostJson(String url, String jsonStr, Map<String, String> head) throws Exception {
if (MapUtil.isNotEmpty(head)) {
RequestBuilder requestBuilder = RequestBuilder.post().setUri(url)
.setHeader("Content-Type", "application/json;charset=utf-8")
.setHeader("Accept", "application/json");
for (Map.Entry<String, String> entry : head.entrySet()) {
requestBuilder.setHeader(entry.getKey(), entry.getValue());
}
HttpUriRequest postMethod = requestBuilder.setEntity(new StringEntity(jsonStr, Charset.forName("UTF-8")))
.setConfig(requestConfig).build();
HttpResponse response = getConnection().execute(postMethod);
return EntityUtils.toString(response.getEntity());
} else {
return httpPostJson(url, jsonStr);
}
}
/**
* http post请求,利用http请求池,设置head内容
*
* @param url 请求url
* @param jsonStr json字符串
* @param head head内容
* @return 请求结果
* @throws Exception 异常
*/
public static String httpPostJson(String url, String jsonStr, Map<String, String> head, Map<String, Object> urlParams) throws Exception {
if (MapUtil.isNotEmpty(head)) {
StringBuilder sb = new StringBuilder("?");
for (Map.Entry<String, Object> e : urlParams.entrySet()) {
sb.append(e.getKey()).append("=").append(e.getValue()).append("&");
}
if (urlParams!= null&&urlParams.size()!=0){
url = url + sb.toString();
}
RequestBuilder requestBuilder = RequestBuilder.post().setUri(url)
.setHeader("Content-Type", "application/json;charset=utf-8")
.setHeader("Accept", "application/json");
for (Map.Entry<String, String> entry : head.entrySet()) {
requestBuilder.setHeader(entry.getKey(), entry.getValue());
}
HttpUriRequest postMethod = requestBuilder.setEntity(new StringEntity(jsonStr, Charset.forName("UTF-8")))
.setConfig(requestConfig).build();
HttpResponse response = getConnection().execute(postMethod);
return EntityUtils.toString(response.getEntity());
} else {
return httpPostJson(url, jsonStr);
}
}
/**
* http get请求,利用http请求池
*
* @param url 请求url
* @return 请求结果
* @throws Exception 异常
*/
public static String httpGet(String url) throws Exception {
HttpUriRequest getMethod = RequestBuilder.get().setUri(url).setConfig(requestConfig).build();
HttpResponse response = getConnection().execute(getMethod);
return EntityUtils.toString(response.getEntity());
}
/**
* http get请求,利用http请求池
*
* @param url 请求url
* @param paramsMap 请求参数
* @return 请求结果
* @throws Exception 异常
*/
public static String httpGet(String url, Map<String, String> paramsMap) throws Exception {
List<NameValuePair> params = new ArrayList<>();
for (Map.Entry<String, String> e : paramsMap.entrySet()) {
NameValuePair pair = new BasicNameValuePair(e.getKey(), e.getValue());
params.add(pair);
}
HttpUriRequest getMethod = RequestBuilder.get().setUri(url)
.addParameters(params.toArray(new BasicNameValuePair[params.size()])).setConfig(requestConfig).build();
HttpResponse response = getConnection().execute(getMethod);
return EntityUtils.toString(response.getEntity());
}
/**
* http get请求,利用http请求池 设置head内容
*
* @param url 请求url
* @param paramsMap 请求参数
* @param head head内容
* @return 请求结果
* @throws Exception 异常
*/
public static String httpGet(String url, Map<String, String> paramsMap, Map<String, String> head) throws Exception {
List<NameValuePair> params = new ArrayList<>();
for (Map.Entry<String, String> e : paramsMap.entrySet()) {
NameValuePair pair = new BasicNameValuePair(e.getKey(), e.getValue());
params.add(pair);
}
RequestBuilder requestBuilder = RequestBuilder.get().setUri(url);
for (Map.Entry<String, String> entry : head.entrySet()) {
requestBuilder.setHeader(entry.getKey(), entry.getValue());
}
HttpUriRequest getMethod = requestBuilder.addParameters(params.toArray(new BasicNameValuePair[params.size()]))
.setConfig(requestConfig).build();
HttpResponse response = getConnection().execute(getMethod);
return EntityUtils.toString(response.getEntity());
}
/**
* http post 请求,每次创建请求客户端
*
* @param url url
* @param params 请求参数
* @return 请求返回值
*/
public static String httpPostNoPool(String url, Map<String, String> params) {
CloseableHttpClient closeableHttpClient = null;
try {
HttpPost httpPost = new HttpPost(url);
if (params != null) {
List<NameValuePair> form = new ArrayList<>();
for (String name : params.keySet()) {
form.add(new BasicNameValuePair(name, params.get(name)));
}
httpPost.setEntity(new UrlEncodedFormEntity(form, HTTP.UTF_8));
}
closeableHttpClient = HttpClients.createDefault();
CloseableHttpResponse httpResponse = closeableHttpClient.execute(httpPost);
HttpEntity entry = httpResponse.getEntity();
return EntityUtils.toString(entry);
} catch (Exception e) {
log.error("HttpUtil.httpPost failed!", e);
} finally {
if (null != closeableHttpClient) {
try {
closeableHttpClient.close();
} catch (IOException e) {
log.error("closeableHttpClient.close failed!", e);
}
}
}
return null;
}
3.Controller层代码
@RestController
@RequestMapping("/dd")
public class DdLoginController {
@Autowired
private DdLoginService ddLoginService;
/**
* 单点登陆
* @param code
* @param
* @return
*/
@GetMapping("/loginPortal")
public AjaxResult DdLogin( String code) throws Exception {
return ddLoginService.loginPortal(code);
}
}
4.逻辑层代码
实现单点登录,需要用前端传递过来的code获取用户信息。http请求的详细信息如下:
调用这个接口可以获取如下信息:
不过在发起请求之前,需要先获取当前登录企业微信用户的access_token,这个方法在工具类有。如果想进一步获取用户的敏感信息,比如性别、头像、员工个人二维码、手机、邮箱、企业邮箱、地址等,可以调用这个接口获取。
拿到用户信息后,可以用户换取第三方系统的token信息,然后返回给前端,就可以实现在企业微信自建应用里免密登录到第三方系统。以下是我的代码实现:
/**
* 单点登陆
*
* @param code
* @return
*/
@Override
public AjaxResult loginPortal(String code) throws Exception {
Map map = new HashMap();
//获取当前登录企业微信用户token
String token = getWeiXinToken();
//根据code获取用户信息,url地址拼接参照官网地址
// https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
String url = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo?access_token=" + token + "&code=" + code;
JSONObject get = WeChatUtil.httpRequest(url, "GET", null);
//获取当前登录用户的信息
String userId = null;
if (get.getString("errmsg").equals("ok")) {
userId = get.getString("userid");
}
//获取用户详细信息
//https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail?access_token=ACCESS_TOKEN
String userTicket = null;
if (get.getString("errmsg").equals("ok")) {
userTicket = get.getString("user_ticket");
}
String userInfoUrl = "https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail?access_token=" + token;
Map<String,String> param=new HashMap<>();
param.put("user_ticket",userTicket);
String json = HttpUtil.httpPostJson(userInfoUrl, null, param);
JSONObject jsonObject = JSON.parseObject(json);
map.put("username", userId);
String token1 = JWTUtil.createToken(userId);
map.put("token", token1);
return AjaxResult.success(map);
}
public String getWeiXinToken() {
return WeChatUtil.getAccessToken(corpId, coprsecret).getToken();
}
/**
* 解析url字符串获取user_ticket
*
* @return
*/
private String getUserTicket(String json) {
JSONObject err = JSON.parseObject(json);
if (err.getString("errmsg").equals("ok")) {
return err.getString("user_ticket");
}
return null;
}
/**
* 解析url字符串获取userid
*
* @param url
* @return
*/
private String getUserIdByCodeUrl(String url) {
JSONObject err = JSON.parseObject(url);
if (err.getString("errmsg").equals("ok")) {
return err.getString("userid");
}
return null;
}
第三步、自建应用配置可信IP,可信域名,应用主页
1.配置应用主页
配置工作台主页,目的就是但企业微信用户在移动端或是网页端点击自建应用,自建应用将会跳转的目标网页链接地址,可以配置网页网址获是关联小程序,这样当你点击自建应用就可以实现跳转到第三方网页链接。
2.配置可信可信域名
就是客户公司的域名,不过在开始阶段可以先配置成自己公司的域名,这个可以向项目经理要。
3.配置可信ip
客户公司的服务器ip
总结:
最后简单小节一下,要实现自建应用单点登录到第三方系统,需要获取corpId,coprsecret,agentId,发请求需要用到。然后拿前端传递过来的code去换取企业微信用户信息,再拿用户信息去换取第三方系统token信息,因为token里面包含了用户的一些基本信息,这样就可以实现免密登录到第三方系统了。
,