企業微信登錄

引言

用戶登錄時,原設計是使用工號加密碼進行登錄,只是工號很差記,爲了推廣,設計了企業微信登錄。html

clipboard.png

企業微信中能夠設置自建應用,其實就是內嵌了一個Chrome,點擊左側的自建應用,會在右側瀏覽器顯示相關應用,全部工做都放在企業微信中,需實現當前企業微信帳號自動登錄系統。java

clipboard.png

開發的過程很坎坷。讓微信折騰的一點脾氣都沒有。git

當時不會調試,由於企業微信中的自建應用要求設置成線上地址,寫好了,打包,傳給服務器,而後再測試。redis

五點,以爲還有十分鐘就寫完了,寫完了就去吃飯。typescript

六點、八點,改到九點半,都要改哭了,仍是很差使,最後放棄了。json

後來邢彥年師兄幫我梳理流程,潘老師教我調試方法,才完成這個功能。小程序

感謝邢彥年師兄和潘老師。微信小程序

實現

文檔

找文檔必定要找對地方,兩個API,一個服務端,一個客戶端。api

clipboard.png

最開始我覺得是使用客戶端的API呢?點進去學了學,企業微信小程序可用的API接口,這個用不了,此應用不是小程序。而後JS-SDK不支持登錄受權。瀏覽器

clipboard.png

相關文檔在服務端API中的身份認證一節中。

OAuth 2.0

當咱們用微信登錄某個網站時,會出現相似這樣的受權頁面。

clipboard.png

點擊確認登錄,該應用就能獲取到用戶相關的信息。

用戶經過用戶名和密碼的方式進行微信的使用,第三方應用想獲取微信用戶信息,不是經過用戶名密碼,而是微信提供的令牌,這就是OAuth 2.0,既可讓應用獲取不敏感的信息,又能夠保證帳戶的安全性。

更多內容可學習阮一峯的博客,寫得很是好:OAuth 2.0 的一個簡單解釋 - 阮一峯的網絡日誌

登錄流程

用戶點開應用,其實是訪問當前系統微信受權的入口

clipboard.png

微信網頁受權地址: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_tokencode獲取用戶信息。

GET請求 https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET

這個是獲取access_token的地址,獲取access_token的過程是重點!

上面傳的參數名是corpidcorpsecret,企業id和密鑰。

clipboard.png

clipboard.png

這是企業微信的設計,企業id是一個,標識這個企業,每個功能模塊都有相應的secret

而後企業idsecret配對,獲取到只能訪問這個功能模塊的一個access_token

clipboard.png

clipboard.png

就拿當前Alice系統來舉例,自建應用Alice存在secret,經過此secretcorpid獲取到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_tokencode都有了,終於能夠得到用戶信息了。

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

必定要記住,很是重要!!!

clipboard.png

之後再碰到只能訪問線上地址的狀況,想在本地調試,必定要改Host!!!

總結

最後的總結就是多想一想潘老師評論的這句話,有些路總不過去,很大的多是方法錯了。

clipboard.png

相關文章
相關標籤/搜索