java对接企业微信授权
一、需求
从企业微信那边登录,获取到当前企业微信登录的用户ID。
二、思路
先去看官方文档 https://work.weixin.qq.com/api/doc/90000/90135/90664 , 在这里总结一下。
- 获取到企业微信的企业ID、应用ID、应用凭证
- 访问获取code链接
- 获取access_token
- 获取用户信息
三、实现
3.1 获取企业微信的企业ID、应用ID、应用凭证
企业ID如下

应用ID、和凭证 需要创建一个应用,如果以前有可以直接用以前的。


3.2 访问获取code链接
https://open.work.weixin.qq.com/api/doc/90000/90135/91022 官方文档
code 地址
https://open.weixin.qq.com/connect/oauth2/authorize?appid=CORPID&redirect_uri=REDIRECT_URI&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
其中我们要修改的有两个值,appid 和 redirect_uri
appid :应用ID
redirect_uri : 授权后重定向的回调链接地址,需要使用urlencode对链接进行处理。
这里我们使用java来进行重定向,如下代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
@GetMapping("login/qywx") public void loginByQywx(HttpServletResponse response) throws IOException { log.info("使用企业微信登录"); String callbackUrl = myGlobalConfigProperties.getHostUri() + "/auth/login/qywx/callback"; callbackUrl = URLEncoder.encode(callbackUrl,"UTF-8"); String targetUrl = StrUtil.format(qywxProperties.getCodeUrl(),callbackUrl); response.sendRedirect(targetUrl); }
|
3.3 获取access_token
其实按照文档的步骤这里应该是我们在回调的url处理器中接受到code ,然后使用code 获取到用户信息,只不过获取用户信息的接口在调用时需要一个access_token的参数。所以我们需要先获取access_token
https://open.work.weixin.qq.com/api/doc/90000/90135/91039
这里在后台使用http请求获取就行了,需要重点提的一点是这个接口不能频繁的调用,不然企业微信会在后台封禁这个应用的获取access_token的权限。官方的描述如下。
开发者需要缓存access_token,用于后续接口的调用(注意:不能频繁调用gettoken接口,否则会受到频率拦截)。当access_token失效或过期时,需要重新获取。
access_token的有效期通过返回的expires_in来传达,正常情况下为7200秒(2小时),有效期内重复获取返回相同结果,过期后获取会返回新的access_token。
由于企业微信每个应用的access_token是彼此独立的,所以进行缓存时需要区分应用来进行存储。
access_token至少保留512字节的存储空间。
企业微信可能会出于运营需要,提前使access_token失效,开发者应实现access_token失效时重新获取的逻辑。
这里我们将获取到的access_token 存储至Redis 。
3.4 获取用户信息
https://open.work.weixin.qq.com/api/doc/90000/90135/91023
这里通过http请求获取就行。注意的是需要获取的用户的详细信息,还需要去调用另外一个接口(通讯录接口:读取成员。)
四、代码
企业微信配置文件,将企业ID,应用ID , 凭证 和调用接口地址都存放在配置文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration;
@Data @Configuration @ConfigurationProperties(prefix = "qywx") public class QywxProperties {
private String accessTokenUrl;
private String corpId;
private String agentId;
private String secret;
private String userinfoUrl;
private String codeUrl; }
|
示例的配置信息
1 2 3 4 5 6 7 8
| qywx: corp-id: 企业ID agent-id: 应用ID secret: 凭证 access-token-url: https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${qywx.corp-id}&corpsecret=${qywx.secret} userinfo-url: https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token={}&code={} code-url: https://open.weixin.qq.com/connect/oauth2/authorize?appid=${qywx.corp-id}&redirect_uri={}&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect
|
接口的返回值实体,这里抽出来一个 baseResult 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
@Getter @Setter public class QywxBaseResult {
private Integer errcode;
private String errmsg;
public void valid(){ if (!Integer.valueOf(0).equals(errcode)){ throw new MyException("从企业微信获取access_token失败!errcode不为0,errmsg=" + errmsg); } }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
@Getter @Setter public class QywxAccessTokenInfo extends QywxBaseResult {
@JsonProperty("access_token") private String accessToken;
@JsonProperty("expires_in") private Integer expiresIn;
@Override public void valid(){ super.valid(); if (expiresIn == null || expiresIn <= 0){ throw new MyException("从企业微信获取access_token失败!expiresIn = " + expiresIn); } if (accessToken == null){ throw new MyException("从企业微信获取access_token失败!access_token is null"); } }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
@Getter @Setter public class QywxUserinfo extends QywxBaseResult {
@JsonProperty("UserId") private String userId;
@JsonProperty("DeviceId") private String deviceId;
@JsonProperty("OpenId") private String openId;
@JsonProperty("external_userid") private String externalUserId;
@Override public void valid(){ super.valid(); if (userId == null || "".equals(userId)){ throw new MyException("userid is null!"); } }
}
|
核心类QywxApi, 这里将调用http请求企业微信的代码都封装到了这里
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
| import cn.hutool.core.util.StrUtil; import com.qx.yxy.ins.assessment.common.redis.RedisUtils; import com.qx.yxy.ins.assessment.core.exceptions.MyException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.stereotype.Component; import org.springframework.web.client.RestTemplate; import java.util.Optional; import java.util.concurrent.TimeUnit;
@Component public class QywxApi {
private final RestTemplate restTemplate; private final QywxProperties qywxProperties; private final RedisUtils redisUtils;
private static final String ACCESS_TOKEN_REDIS_KEY = "fdykh:qywx_access_token";
public QywxApi(QywxProperties qywxProperties , RedisUtils redisUtils) { this.qywxProperties = qywxProperties; this.redisUtils = redisUtils; SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); factory.setReadTimeout(300 * 1000); factory.setConnectTimeout(300 * 1000); this.restTemplate = new RestTemplate(factory); }
public QywxUserinfo getUserinfo(String code){ String url = StrUtil.format(qywxProperties.getUserinfoUrl(),getAccessToken(),code); ResponseEntity<QywxUserinfo> responseEntity = restTemplate.getForEntity(url, QywxUserinfo.class); if (responseEntity.getStatusCode() != HttpStatus.OK){ throw new MyException("从企业微信获取userinfo失败!http状态码不为200"); } QywxUserinfo body = responseEntity.getBody(); if (body == null){ throw new MyException("QywxUserinfo is null!"); } body.valid(); return body; }
public String getAccessToken(){ Optional<String> tokenOptional = this.getAccessTokenForRedis(); if (tokenOptional.isPresent()){ return tokenOptional.get(); } synchronized (this){ tokenOptional = this.getAccessTokenForRedis(); return tokenOptional.orElseGet(this::getAccessTokenForHttp); } }
private Optional<String> getAccessTokenForRedis(){ Object o = redisUtils.get(ACCESS_TOKEN_REDIS_KEY); return o == null ? Optional.empty() : Optional.of((String) o); }
private String getAccessTokenForHttp(){ String url = qywxProperties.getAccessTokenUrl(); ResponseEntity<QywxAccessTokenInfo> responseEntity = restTemplate.getForEntity(url, QywxAccessTokenInfo.class); if (responseEntity.getStatusCode() != HttpStatus.OK){ throw new MyException("从企业微信获取access_token失败!http状态码不为200"); } QywxAccessTokenInfo tokenInfo = responseEntity.getBody(); if (tokenInfo == null){ throw new MyException("从企业微信获取access_token失败!tokenInfo为null"); } tokenInfo.valid(); redisUtils.set(ACCESS_TOKEN_REDIS_KEY,tokenInfo.getAccessToken(),tokenInfo.getExpiresIn() - 3, TimeUnit.SECONDS); return tokenInfo.getAccessToken(); }
}
|
企业微信登录的开始controller以及回调controller的代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
@GetMapping("login/qywx") public void loginByQywx(HttpServletResponse response) throws IOException { log.info("使用企业微信登录"); String callbackUrl = myGlobalConfigProperties.getHostUri() + "/auth/login/qywx/callback"; callbackUrl = URLEncoder.encode(callbackUrl,"UTF-8"); String targetUrl = StrUtil.format(qywxProperties.getCodeUrl(),callbackUrl); response.sendRedirect(targetUrl); }
@GetMapping("login/qywx/callback") public void loginByQywxCallback(@NotBlank(message = "params code is null") String code , HttpServletResponse response) throws IOException { QywxUserinfo userinfo = qywxApi.getUserinfo(code); if (checkUrlUserId(userinfo.getUserId(),response)){ return; } String token = tokenProvider.createToken(userinfo.getUserId(), AuthTypeEnum.QYWX); response.sendRedirect(myGlobalConfigProperties.phoneHtmlCodeAcceptUri() + "?code=" + tokenCode.saveTokenAndGenerateCode(token)); }
|