用户登录时,原设计是使用工号加密码进行登录,只是工号很差记,为了推广,设计了企业微信登录。html
企业微信中能够设置自建应用,其实就是内嵌了一个Chrome
,点击左侧的自建应用,会在右侧浏览器显示相关应用,全部工做都放在企业微信中,需实现当前企业微信帐号自动登录系统。java
开发的过程很坎坷。让微信折腾的一点脾气都没有。git
当时不会调试,由于企业微信中的自建应用要求设置成线上地址,写好了,打包,传给服务器,而后再测试。redis
五点,以为还有十分钟就写完了,写完了就去吃饭。typescript
六点、八点,改到九点半,都要改哭了,仍是很差使,最后放弃了。json
后来邢彦年师兄帮我梳理流程,潘老师教我调试方法,才完成这个功能。小程序
感谢邢彦年师兄和潘老师。微信小程序
找文档必定要找对地方,两个API
,一个服务端,一个客户端。api
最开始我觉得是使用客户端的API
呢?点进去学了学,企业微信小程序可用的API
接口,这个用不了,此应用不是小程序。而后JS-SDK
不支持登录受权。浏览器
相关文档在服务端API
中的身份认证一节中。
OAuth 2.0
当咱们用微信登录某个网站时,会出现相似这样的受权页面。
点击确认登录,该应用就能获取到用户相关的信息。
用户经过用户名和密码的方式进行微信的使用,第三方应用想获取微信用户信息,不是经过用户名密码,而是微信提供的令牌,这就是OAuth 2.0
,既可让应用获取不敏感的信息,又能够保证帐户的安全性。
更多内容可学习阮一峰的博客,写得很是好:OAuth 2.0 的一个简单解释 - 阮一峰的网络日志
微信网页受权地址:https://open.weixin.qq.com/connect/oauth2/authorize
参数 | 说明 |
---|---|
appid |
企业的CorpID |
redirect_uri |
受权后的回调地址,需使用urlencode 处理 |
response_type |
回调数据返回类型 |
scope |
应用受权做用域。企业自建应用固定填写:snsapi_base |
state |
回调时额外的参数,非必选 |
#wechat_redirect |
终端使用此参数判断是否须要带上身份信息 |
看这个表格也是在无聊,下面是我配置好的微信受权连接,你们只需将相应参数改写便可。注:回调的url
必定要使用encodeURIComponent
处理!
https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxxxxxxx&redirect_uri=https%3A%2F%2Falice.dgitc.net%2Fsetup%2Fwechat&response_type=code&scope=snsapi_base#wechat_redirect
用户受权成功后,会带着code
参数重定向到回调地址。
相似这样:
https://alice.dgitc.net/setup/wechat?code=xxxxxx
前台的组件就经过路由获取到了code
,而后经过code
去进一步获取用户信息。
const code = this.route.snapshot.queryParamMap.get('code'); this.userService.enterpriseWeChatLogin(code).subscribe(...);
code
找微信后台获取用户信息这个是分红两次获取,先获取access_token
,再经过access_token
和code
获取用户信息。
GET请求 https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET
这个是获取access_token
的地址,获取access_token
的过程是重点!
上面传的参数名是corpid
和corpsecret
,企业id
和密钥。
这是企业微信的设计,企业id
是一个,标识这个企业,每个功能模块都有相应的secret
。
而后企业id
和secret
配对,获取到只能访问这个功能模块的一个access_token
。
就拿当前Alice
系统来举例,自建应用Alice
存在secret
,经过此secret
和corpid
获取到access_token
,即至关于拿到了受保护API
的访问权限。
由于这个access_token
是经过Alice
应用的secret
获取到的,因此再用它访问其余的功能,是不合法的。
access_token
有访问频率限制,因此设计了一套缓存方案。
@Override public String getAccessTokenBySecret(String secret) { logger.debug("从缓存中获取令牌"); String access_token = this.cacheService.get(secret); logger.debug("若是获取到了,直接返回"); if (null != access_token) { return access_token; } logger.debug("不然,发起HTTP请求"); logger.debug("获取企业验证信息"); String url = enterpriseInformation.getAccessTokenUrl(); String corpId = enterpriseInformation.getCorpid(); logger.debug("获取认证令牌"); ResponseEntity<EnterpriseAuth> responseEntity = restTemplate.getForEntity(url + "?corpid=" + corpId + "&corpsecret=" + secret, EnterpriseAuth.class); logger.debug("若是请求成功"); if (responseEntity.getStatusCode().is2xxSuccessful()) { logger.debug("获取响应体"); EnterpriseAuth enterpriseAuth = responseEntity.getBody(); Assert.notNull(enterpriseAuth, "微信令牌为空"); logger.debug("若是微信请求成功"); if (enterpriseAuth.successed()) { logger.debug("存储缓存,返回令牌"); access_token = enterpriseAuth.getAccessToken(); this.cacheService.put(secret, access_token, enterpriseAuth.getExpiresIn(), TimeUnit.SECONDS); return access_token; } } logger.debug("请求失败,返回空令牌"); return ""; }
缓存是经过把Redis
工具类包装了一下实现的,很简单。
@Service public class CacheServiceImpl implements CacheService { /** * Redis操做模版 */ private final StringRedisTemplate redisTemplate; private final ValueOperations<String, String> valueOperations; @Autowired public CacheServiceImpl(StringRedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; this.valueOperations = redisTemplate.opsForValue(); } @Override public void put(String key, String value, Long time, TimeUnit timeUnit) { this.valueOperations.set(key, value, time, timeUnit); } @Override public String get(String key) { return this.valueOperations.get(key); } @Override public void clear(String key) { this.redisTemplate.delete(key); } }
access_token
和code
都有了,终于能够得到用户信息了。
GET请求 https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?access_token=ACCESS_TOKEN&code=CODE
这个简单了,问题出在数据不规范的问题上,大写的json
,若是按这样建,实体就不符合java
规范了。
{ "errcode": 0, "errmsg": "ok", "UserId":"USERID", "DeviceId":"DEVICEID" }
最后在字段添加JsonAlias
注解。
@JsonAlias("UserId") private String userId;
这里获取到的信息是用户的userId
,这个userId
和咱们熟知的openId
是不同的。
userId
就是管理员为用户在企业内设置的惟一标识,企业内惟一。
具体的后台获取userId
的细节:
@Override public Boolean enterpriseWeChatLogin(String code) { logger.debug("构造参数"); String secret = this.enterpriseInformation.getAgentSecret(); String access_token = this.aliceCommonService.getAccessTokenBySecret(secret); String url = this.enterpriseInformation.getUserInfoUrl() + "?code=" + code + "&access_token=" + access_token; logger.debug("请求用户信息"); ResponseEntity<EnterpriseUser> responseEntity = this.restTemplate.getForEntity(url, EnterpriseUser.class); logger.debug("若是请求成功"); if (responseEntity.getStatusCode().is2xxSuccessful()) { logger.debug("获取响应体"); EnterpriseUser enterpriseUser = responseEntity.getBody(); Assert.notNull(enterpriseUser, "企业用户不能为空"); logger.debug("若是企业微信端也成功了"); if (enterpriseUser.successed()) { logger.debug("获取userId"); String wxId = enterpriseUser.getUserId(); logger.debug("若是有userId,说明当前用户存在此企业中"); if (null != wxId) { // 请自行填充登录的具体细节 } } else if (enterpriseUser.tokenInvalided()) { logger.debug("token不合法,清空缓存,从新获取"); this.cacheService.clear(secret); return this.enterpriseWeChatLogin(code); } } logger.debug("其余一概返回false"); return false; }
前台在订阅中添加跳转方法,登录成功后,跳转到首页,登录失败,401
走拦截器,跳转到登录页。
this.userService.enterpriseWeChatLogin(code) .subscribe(() => { this.userService.setCurrentLoginUser(); this.router.navigateByUrl('main'); }, () => { console.log('network error'); });
Host
必定要记住,很是重要!!!
之后再碰到只能访问线上地址的状况,想在本地调试,必定要改Host
!!!
最后的总结就是多想一想潘老师评论的这句话,有些路总不过去,很大的多是方法错了。