Spring Security整合企業(yè)微信的掃碼登錄舍沙,企微的API震驚到我了

本文代碼: https://gitee.com/felord/spring-security-oauth2-tutorial/tree/wwopen/

現(xiàn)在很多企業(yè)都接入了企業(yè)微信宗兼,作為私域社群工具咆耿,企業(yè)微信開(kāi)放了很多API杆煞,可以打通很多自有的應(yīng)用娩嚼。既然是應(yīng)用蘑险,那肯定需要做登錄。正好企業(yè)微信提供了企業(yè)微信掃碼授權(quán)登錄功能岳悟,而且號(hào)稱使用了OAuth佃迄,正好拿這個(gè)檢驗(yàn)一下Spring Security OAuth2專欄的威力。

正當(dāng)我興致勃勃打開(kāi)文檔學(xué)習(xí)的時(shí)候贵少,臉上笑容逐漸消失呵俏,這確定是OAuth的嗎?

參數(shù)都變了滔灶,跟OAuth(不管是1.0還是2.0)規(guī)定不一樣普碎,然而這還不是最離譜的。按正常OAuth2的要求录平,拿到code之后就可以換access_token了是吧麻车?企業(yè)微信的access_token居然和上面掃碼獲取code這一步完全無(wú)關(guān),甚至獲取access_token才是第一步斗这!

而且這個(gè)access_token接口动猬,你還不能頻繁調(diào)用,要緩存起來(lái)公用表箭。

那費(fèi)了半天勁兒去拿code有啥用呢赁咙?

居然這個(gè)code是拿用戶信息的,不得不說(shuō)免钻,我服了彼水!OAuth2的123流程被整成了213。這也就算了伯襟,命名上能不能走點(diǎn)心猿涨,一會(huì)兒下劃線,一會(huì)兒駝峰:

{
   "errcode": 0,
   "errmsg": "ok",
   "OpenId":"OPENID",
   "DeviceId":"DEVICEID",
   "external_userid":"EXTERNAL_USERID"
}

這個(gè)JSON風(fēng)格姆怪,果然是大廠叛赚,講究澡绩!一個(gè)JSON要三個(gè)人來(lái)寫才體面!反序列化的時(shí)候我還得給你寫一個(gè)兼容俺附,這是要拉滿我的KPI是吧肥卡?算了,忍忍吧事镣,老板就要這個(gè)功能步鉴,它就是一坨翔,做開(kāi)發(fā)的也得含淚吃下去璃哟,干氛琢!

環(huán)境準(zhǔn)備

準(zhǔn)備一個(gè)內(nèi)網(wǎng)穿透

開(kāi)發(fā)微信相關(guān)的應(yīng)用都需要搞一個(gè)內(nèi)網(wǎng)穿透,在我往期的文章都有介紹随闪。搞一個(gè)映射域名出來(lái)阳似,就像下面這樣:

http://invybj.natappfree.cc -> 127.0.0.1:8082 

invybj.natappfree.cc會(huì)映射到我本地的8082端口,也就是我本地要開(kāi)發(fā)應(yīng)用的端口铐伴。

創(chuàng)建應(yīng)用

首先去企業(yè)微信管理后臺(tái)創(chuàng)建一個(gè)應(yīng)用撮奏,如圖:

圖里的參數(shù)AgentIdSecret要記下來(lái)備用。

還有一個(gè)企業(yè)微信的corpid当宴,你可以從下面這個(gè)位置拿到畜吊,也要記下來(lái)備用。

配置內(nèi)網(wǎng)穿透域名

創(chuàng)建應(yīng)用這一頁(yè)往下拉到頁(yè)面底端户矢,你會(huì)看到:

點(diǎn)擊已啟用進(jìn)入下面這個(gè)頁(yè)面:

這里配置你授權(quán)登錄應(yīng)用生產(chǎn)的正式域名或者上面內(nèi)網(wǎng)穿透的域名玲献,注意只配置域名,而且不能使用localhost梯浪。

其實(shí)我感覺(jué)改寫hosts文件也能用啊青自,你可以試一試。

到這里環(huán)境就搞定了驱证,接下來(lái)就開(kāi)始寫Spring Security兼容代碼吧。

Spring Security兼容企業(yè)微信掃碼登錄

寫起來(lái)太惡心了恋腕,不過(guò)對(duì)比文檔和OAuth2的流程之后其實(shí)也沒(méi)那么麻煩抹锄。我先放出我調(diào)試好的配置:

spring:
  security:
    oauth2:
      client:
        registration:
          work-wechat-scan:
            # client-id為企業(yè)微信 的企業(yè)ID
            # 下面client-id是假的,你用你自己的企業(yè)ID
            client-id: wwaxxxxxx
            # client-secret企業(yè)微信對(duì)應(yīng)應(yīng)用的secret荠藤,
            # 每個(gè)企業(yè)微信應(yīng)用都有獨(dú)立的secret伙单,不要搞錯(cuò)
            # 下面client-secret假的,你用你自己創(chuàng)建的企業(yè)微信應(yīng)用secret
            client-secret:  nvzGI4Alp3zxxxxxxxKbnfTEets5W8
            authorization-grant-type: authorization_code
            redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
        provider:
          work-wechat-scan:
            authorization-uri: https://open.work.weixin.qq.com/wwopen/sso/qrConnect
            token-uri: https://qyapi.weixin.qq.com/cgi-bin/gettoken
            user-info-uri: https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo

這里client-id使用你企業(yè)微信的企業(yè)ID哈肖,client-secret使用上面創(chuàng)建應(yīng)用的secret值吻育。

這里的work-wechat-scan是客戶端的registrationId

封裝企業(yè)微信拉起二維碼URL

我們期望的是保持Spring Security OAuth2的風(fēng)格,當(dāng)我訪問(wèn):

http://invybj.natappfree.cc/oauth2/authorization/work-wechat-scan

會(huì)重定向到企業(yè)微信掃碼登錄鏈接淤井,格式為:

https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=CORPID&agentid=AGENTID&redirect_uri=REDIRECT_URI&state=STATE

這個(gè)和以前胖哥實(shí)現(xiàn)微信網(wǎng)頁(yè)授權(quán)的原理差不多布疼,都是通過(guò)改造OAuth2AuthorizationRequestResolver接口來(lái)實(shí)現(xiàn)摊趾,只需要實(shí)現(xiàn)一個(gè)Consumer<OAuth2AuthorizationRequest.Builder>就行了。

邏輯是:client_id替換為appid游两,增加一個(gè)agentid參數(shù)砾层,連帶redirect_uristate四個(gè)參數(shù)之外的其它OAuth2參數(shù)全干掉,拼接成上面的URL贱案。

這么寫:

把這個(gè)Consumer配置到DefaultOAuth2AuthorizationRequestResolver就行了肛炮。

適配OAuth2獲取access_token

經(jīng)過(guò)這一步掃碼拿到code就不成問(wèn)題了,按照OAuth2該拿access_token了宝踪,需要自定義一個(gè)函數(shù)式接口:

Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>>

也就是利用OAuth2AuthorizationCodeGrantRequest生成RestTemplate需要的請(qǐng)求對(duì)象RequestEntity<?>侨糟。按照企業(yè)微信獲取access_token的文檔,這樣自定義:

把這個(gè)配置到DefaultAuthorizationCodeTokenResponseClient就行了瘩燥。

access_token的緩存秕重,我放在了下一步進(jìn)行解決。

適配獲取用戶信息

codeaccess_token都拿到了颤芬,最后一步獲取用戶的信息悲幅。這里是比較麻煩的因?yàn)楂@取access_token后并沒(méi)有直接提供將code傳遞給OAuth2UserService的方法。最后發(fā)現(xiàn)OAuth2AccessTokenResponseadditionalParameters屬性可以傳遞到OAuth2UserService站蝠,于是就利用代理模式改造了OAuth2AccessTokenResponseClient來(lái)實(shí)現(xiàn):

自定義企業(yè)微信OAuth2UserService

這個(gè)和微信網(wǎng)頁(yè)授權(quán)我封裝的差不多汰具,改下參數(shù)封裝成URI交給RestTemplate請(qǐng)求企業(yè)微信API。惡心的是要反序列化兼容三個(gè)微信研發(fā)工程師寫的一個(gè)JSON

@Data
public class WorkWechatOAuth2User implements OAuth2User {
    private Set<GrantedAuthority> authorities;
    private Integer errcode;
    private String errmsg;
    @JsonAlias("OpenId")
    private String openId;
    @JsonAlias("UserId")
    private String userId;
}

收尾

拿到用戶信息后菱魔,就結(jié)束了留荔,你實(shí)現(xiàn)一個(gè)AuthenticationSuccessHandler來(lái)保證登錄憑證和你平臺(tái)一致,無(wú)論是cookie還是JWT澜倦,最后把它配置到這里:

httpSecurity.oauth2Login()
    .successHandler(AuthenticationSuccessHandler successHandler)

試一下效果

務(wù)必使用域名進(jìn)行訪問(wèn)聚蝶,不要使用localhost或者IP

訪問(wèn)http://invybj.natappfree.cc/login藻治,這里是內(nèi)網(wǎng)穿透域名碘勉,出現(xiàn):

企業(yè)微信掃碼登錄的地址其實(shí)就是http://invybj.natappfree.cc/oauth2/authorization/work-wechat-scan。點(diǎn)擊跳轉(zhuǎn)到掃碼頁(yè)面:

然后用你對(duì)應(yīng)的企業(yè)微信APP掃碼桩卵,企業(yè)和用戶要和申請(qǐng)應(yīng)用的一致验靡。掃碼后:

這個(gè)就是Spring Security 封裝的用戶認(rèn)證信息Authentication對(duì)象,是真正的登錄雏节,這里我沒(méi)有注入權(quán)限胜嗓,你需要在企業(yè)微信的OAuth2UserService實(shí)現(xiàn)中注入權(quán)限和更多的信息。

總結(jié)

沒(méi)有實(shí)現(xiàn)不了的钩乍,只要把原理和流程搞清楚就行辞州。不過(guò)如果上游微信把代碼寫規(guī)范一些,下游何必寫這么多冗余的代碼寥粹。

關(guān)注公眾號(hào):碼農(nóng)小胖哥变过,獲取更多資訊

個(gè)人博客:https://felord.cn

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末埃元,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子牵啦,更是在濱河造成了極大的恐慌亚情,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哈雏,死亡現(xiàn)場(chǎng)離奇詭異楞件,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)裳瘪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門土浸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人彭羹,你說(shuō)我怎么就攤上這事黄伊。” “怎么了派殷?”我有些...
    開(kāi)封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵还最,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我毡惜,道長(zhǎng)拓轻,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任经伙,我火速辦了婚禮扶叉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帕膜。我一直安慰自己枣氧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布垮刹。 她就那樣靜靜地躺著达吞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荒典。 梳的紋絲不亂的頭發(fā)上宗挥,一...
    開(kāi)封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音种蝶,去河邊找鬼。 笑死瞒大,一個(gè)胖子當(dāng)著我的面吹牛螃征,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播透敌,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼盯滚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼踢械!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起魄藕,我...
    開(kāi)封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤内列,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后背率,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體话瞧,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年寝姿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了交排。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡饵筑,死狀恐怖埃篓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情根资,我是刑警寧澤架专,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站玄帕,受9級(jí)特大地震影響部脚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜桨仿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一睛低、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧服傍,春花似錦钱雷、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至灿椅,卻和暖如春套蒂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茫蛹。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工操刀, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人婴洼。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓骨坑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子欢唾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容