用戶登錄時,原設計是使用工號加密碼進行登錄,只是工號很差記,爲了推廣,設計了企業微信登錄。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
!!!
最後的總結就是多想一想潘老師評論的這句話,有些路總不過去,很大的多是方法錯了。