本文程式碼: https://gitee.com/felord/spring-security-oauth2-tutorial/tree/wwopen/
現在很多企業都接入了企業微信,作為私域社群工具,企業微信開放了很多API,可以打通很多自有的應用。既然是應用,那肯定需要做登入。正好企業微信提供了企業微信掃碼授權登入功能,而且號稱使用了OAuth,正好拿這個檢驗一下Spring Security OAuth2專欄的威力。
正當我興致勃勃開啟檔案學習的時候,臉上笑容逐漸消失,這確定是OAuth的嗎?
引數都變了,跟OAuth(不管是1.0還是2.0)規定不一樣,然而這還不是最離譜的。按正常OAuth2的要求,拿到code
之後就可以換access_token
了是吧?企業微信的access_token
居然和上面掃碼獲取code
這一步完全無關,甚至獲取access_token
才是第一步!
而且這個
access_token
介面,你還不能頻繁呼叫,要快取起來公用。
那費了半天勁兒去拿code
有啥用呢?
居然這個code
是拿使用者資訊的,不得不說,我服了!OAuth2的123流程被整成了213。這也就算了,命名上能不能走點心,一會兒下劃線,一會兒駝峰:
{
"errcode": 0,
"errmsg": "ok",
"OpenId":"OPENID",
"DeviceId":"DEVICEID",
"external_userid":"EXTERNAL_USERID"
}
這個JSON風格,果然是大廠,講究!一個JSON要三個人來寫才體面!反序列化的時候我還得給你寫一個相容,這是要拉滿我的KPI是吧?算了,忍忍吧,老闆就要這個功能,它就是一坨翔,做開發的也得含淚吃下去,幹!
開發微信相關的應用都需要搞一個內網穿透,在我往期的文章都有介紹。搞一個對映域名出來,就像下面這樣:
http://invybj.natappfree.cc -> 127.0.0.1:8082
invybj.natappfree.cc
會對映到我原生的8082
埠,也就是我本地要開發應用的埠。
首先去企業微信管理後臺建立一個應用,如圖:
圖裡的引數
AgentId
和Secret
要記下來備用。
還有一個企業微信的corpid
,你可以從下面這個位置拿到,也要記下來備用。
在建立應用這一頁往下拉到頁面底端,你會看到:
點選已啟用
進入下面這個頁面:
這裡設定你授權登入應用生產的正式域名或者上面內網穿透的域名,注意只設定域名,而且不能使用localhost
。
其實我感覺改寫
hosts
檔案也能用啊,你可以試一試。
到這裡環境就搞定了,接下來就開始寫Spring Security相容程式碼吧。
寫起來太噁心了,不過對比檔案和OAuth2的流程之後其實也沒那麼麻煩。我先放出我偵錯好的設定:
spring:
security:
oauth2:
client:
registration:
work-wechat-scan:
# client-id為企業微信 的企業ID
# 下面client-id是假的,你用你自己的企業ID
client-id: wwaxxxxxx
# client-secret企業微信對應應用的secret,
# 每個企業微信應用都有獨立的secret,不要搞錯
# 下面client-secret假的,你用你自己建立的企業微信應用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
使用你企業微信的企業ID
,client-secret
使用上面建立應用的secret
值。
這裡的
work-wechat-scan
是使用者端的registrationId
我們期望的是保持Spring Security OAuth2的風格,當我存取:
http://invybj.natappfree.cc/oauth2/authorization/work-wechat-scan
會重定向到企業微信掃碼登入連結,格式為:
https://open.work.weixin.qq.com/wwopen/sso/qrConnect?appid=CORPID&agentid=AGENTID&redirect_uri=REDIRECT_URI&state=STATE
這個和以前胖哥實現微信網頁授權的原理差不多,都是通過改造OAuth2AuthorizationRequestResolver
介面來實現,只需要實現一個Consumer<OAuth2AuthorizationRequest.Builder>
就行了。
邏輯是:把client_id
替換為appid
,增加一個agentid
引數,連帶redirect_uri
和state
四個引數之外的其它OAuth2引數全乾掉,拼接成上面的URL。
這麼寫:
把這個Consumer
設定到DefaultOAuth2AuthorizationRequestResolver
就行了。
經過這一步掃碼拿到code
就不成問題了,按照OAuth2該拿access_token
了,需要自定義一個函數式介面:
Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>>
也就是利用OAuth2AuthorizationCodeGrantRequest
生成RestTemplate
需要的請求物件RequestEntity<?>
。按照企業微信獲取access_token
的檔案,這樣自定義:
把這個設定到DefaultAuthorizationCodeTokenResponseClient
就行了。
access_token
的快取,我放在了下一步進行解決。
code
和access_token
都拿到了,最後一步獲取使用者的資訊。這裡是比較麻煩的因為獲取access_token
後並沒有直接提供將code
傳遞給OAuth2UserService
的方法。最後發現OAuth2AccessTokenResponse
的additionalParameters
屬性可以傳遞到OAuth2UserService
,於是就利用代理模式改造了OAuth2AccessTokenResponseClient
來實現:
這個和微信網頁授權我封裝的差不多,改下引數封裝成URI交給RestTemplate
請求企業微信API。噁心的是要反序列化相容三個微信研發工程師寫的一個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;
}
拿到使用者資訊後,就結束了,你實現一個AuthenticationSuccessHandler
來保證登入憑證和你平臺一致,無論是cookie還是JWT,最後把它設定到這裡:
httpSecurity.oauth2Login()
.successHandler(AuthenticationSuccessHandler successHandler)
務必使用域名進行存取,不要使用
localhost
或者IP。
存取http://invybj.natappfree.cc/login
,這裡是內網穿透域名,出現:
企業微信掃碼登入的地址其實就是http://invybj.natappfree.cc/oauth2/authorization/work-wechat-scan
。點選跳轉到掃碼頁面:
然後用你對應的企業微信APP掃碼,企業和使用者要和申請應用的一致。掃碼後:
這個就是Spring Security 封裝的使用者認證資訊Authentication
物件,是真正的登入,這裡我沒有注入許可權,你需要在企業微信的OAuth2UserService
實現中注入許可權和更多的資訊。
沒有實現不了的,只要把原理和流程搞清楚就行。不過如果上游微信把程式碼寫規範一些,下游何必寫這麼多冗餘的程式碼。
關注公眾號:Felordcn 獲取更多資訊