企业微信的扫码登录官方文档
https://work.weixin.qq.com/api/doc/90000/90135/90988
企业微信扫码登录认证流程
上面是官方的图片,看上去比较复杂。因此我用直白简洁的语言梳理下流程。
1、首先在自己的网站上获取到企业微信登录的二维码(二维码怎么获取稍后再说,这里想象下你已经有了一个二维码)
2、你拿着手机扫描二维码,确认登录之后,页面就会自己进行重定向的跳转,跳转的url如何配置下面再讲,但是注意,这个重定向的url可以是内网地址
3、重定向到指定url时,企业微信会在这个url后加上一个code参数,根据这个code参数在你的程序内部再次请求企业微信的一个接口,就可以获取到扫码用户的企业微信userid
4、获取到userid后就可以去数据库中查询这个userid和自己系统用户的对应关系了,到了这里就和自己网站的登录逻辑对应上了
开发前准备
参数获取
corpid | 每个企业都拥有唯一的corpid,获取此信息可在管理后台“我的企业”-“企业信息”下查看“企业ID” |
agentid | 每个应用都有唯一的agentid。在管理后台->“应用与小程序”->“应用”,点进某个应用,即可看到agentid。secret |
secret | secret是企业应用里面用于保障数据安全的“钥匙”,每一个应用都有一个独立的访问密钥,为了保证数据的安全,secret务必不能泄漏。 |
这三个参数的说明文档见这里
https://work.weixin.qq.com/api/doc/90000/90135/90665
设置授权回调域
https://work.weixin.qq.com/api/doc/90000/90135/91025
请直接参照文档进行配置
正式开始开发
获取二维码
用以下网址获取
https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=CORPID&agentid=AGENTID&redirect_uri=REDIRECT_URI&state=STATE
参数解释如下:
参数 | 必须 | 说明 |
appid | 是 | 企业微信的CorpID,在企业微信管理端查看 |
agentid | 是 | 授权方的网页应用ID,在具体的网页应用中查看 |
redirect_uri | 是 | 重定向地址,需要进行UrlEncode |
state | 否 | 用于保持请求和回调的状态,授权请求后原样带回给企业。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议企业带上该参数,可设置为简单的随机数加session进行校验 |
这种获取二维码的方式是在一个独立的H5页面展示二维码,如下,独占了一个H5页面
如果想要把二维码嵌入到页面的demo中请参照这个官方文档
https://work.weixin.qq.com/api/doc/90000/90135/91019
路由重定向后参数的接收
当我们获取到二维码后,进行扫码登录。
用户允许授权后,将会重定向到redirect_uri的网址上,并且带上code和state参数
redirect_uri?code=CODE&state=STATE
此时我们就可以在后台进行参数的获取了(注意重定向url时为get请求,因此后台请使用get方式接收)。
若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数
redirect_uri?state=STATE
后台判断逻辑分为三步:
1、请求企业微信接口获取得到Access_token
2、请求企业维信接口根据Access_token和code获取得到userid
3、根据userid查询数据库做登录判断逻辑。
代码逻辑如下:
注意,红色框内的为登录逻辑判断,大家根据自己系统的登录逻辑进行具体实现即可
主提逻辑代码(注意用get方式接收):
@RequestMapping(value = "wechatLogin", method = RequestMethod.GET)
public ObjectRestResponse<Map> wechatLogin(@RequestParam("code")String code) throws Exception {
String status = "false";
//获取得到Access_token
String accessToken = getAccessToken();
//根据Access_token及code获取企业微信授权用户的userid
String userId = getWechatUserId(code, accessToken);
Map<String, String> respMap = new HashMap<>();
//根据userid去查询数据库做登录逻辑判断
if (userId!=null&&!"".equals(userId)){
final String token = authService.wechatLogin(userId);
respMap.put("token",token);
status = "true";
}else {
throw new UserInvalidException("企业维信授权失败");
}
respMap.put("status", status);
return new ObjectRestResponse<>().data(respMap);
}
getAccessToken()方法
获取得到accessToken
public String getAccessToken(){
String accessToken = redisStrategy.get("WECHAT_LOGIN_ACCESS_TOKEN");
if (accessToken==null || "".equals(accessToken)){
log.info("缓存中未存储accessToken");
HttpResponse res = HttpRequest.get("https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=xxxxxx&corpsecret=xxxxxxx").execute();
Gson gson = new Gson();
Map map = new HashMap<>();
//将返回结果的String字符串转化为Map,然后获取详细返回参数
map = gson.fromJson(res.body(),map.getClass());
if ("ok".equals(map.get("errmsg"))){
accessToken = (String) map.get("access_token");
redisStrategy.set("WECHAT_LOGIN_ACCESS_TOKEN", accessToken, 3600);
}else {
log.info("请求accessToken失败,失败详细信息为:"+map.get("errmsg"));
}
}
log.info("得到的accessToken为:"+accessToken);
return accessToken;
}
注意,请将accessToken放置在缓存中,因为频繁获取可能会受到企业微信端的拦截,我这里设置的过期时间为1小时,官方说accessToken过期时间为2小时,大家可根据自己情况设置过期时间,只要不超过两小时就好。
getWechatUserId(code, accessToken)
获取得到userid
public String getWechatUserId(String code, String accessToken){
HttpResponse res = HttpRequest.get("https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token="+accessToken+"&code="+code).execute();
Gson gson = new Gson();
Map map = new HashMap<>();
String userid= "";
map = gson.fromJson(res.body(),map.getClass());
if ("ok".equals(map.get("errmsg"))){
userid = (String) map.get("UserId");
}else {
log.info("请求UserId失败,失败详细信息为:"+map.get("errmsg"));
}
log.info("获取到的userid为:"+userid);
return userid;
}
以上代码总共有两个地方需要额外依赖,一个是HTTP请求,一个是String转化为Map。如果大家有自己的实现方式可以自己实现,如果参照我的需要添加如下依赖
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.6.6</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
写在最后
自己做的时候发现很多博客都是把官方文档复制了一遍,具体实现细节也没说清楚,也没有什么参照的代码,对于我这种用复制粘贴写代码的程序员实在是不能接受,所以就把自己的开发过程记录一下,也可以方便下大家。大家如果有什么不懂的或者是我写的文档有遗漏的地方,欢迎随时沟通!