本文代碼: 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ù)
AgentId
和Secret
要記下來(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_uri
和state
四個(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)行解決。
適配獲取用戶信息
code
和access_token
都拿到了颤芬,最后一步獲取用戶的信息悲幅。這里是比較麻煩的因?yàn)楂@取access_token
后并沒(méi)有直接提供將code
傳遞給OAuth2UserService
的方法。最后發(fā)現(xiàn)OAuth2AccessTokenResponse
的additionalParameters
屬性可以傳遞到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)小胖哥变过,獲取更多資訊