微信公衆号提供了微信支付、微信優惠券、微信H5紅包、微信紅包封面等等促銷工具來幫助我們的應用拉新保活。但是這些福利要想正确地發放到使用者的手裡就必須拿到使用者特定的(微信應用)微信辨別<code>openid</code>甚至是使用者的微信使用者資訊。如果使用者在微信用戶端中通路我們第三方網頁,公衆号可以通過微信網頁授權機制,來擷取使用者基本資訊,進而實作業務邏輯。今天就結合Spring Security來實作一下微信公衆号網頁授權。
在開始之前我們需要準備好微信網頁開發的環境。
請注意,一定是微信公衆号服務号,隻有服務号才提供這樣的能力。像胖哥的這樣公衆号雖然也是認證過的公衆号,但是隻能發發文章并不具備提供服務的能力。但是微信公衆平台提供了沙盒功能來模拟服務号,可以降低開發難度,你可以到微信公衆号測試賬号頁面申請,申請成功後别忘了關注測試公衆号。
微信公衆号服務号隻有企事業機關、政府機關才能開通。
因為微信伺服器需要回調開發者提供的回調接口,為了能夠本地調試,内網穿透工具也是必須的。啟動内網穿透後,需要把内網穿透工具提供的虛拟域名配置到微信測試帳号的回調配置中

打開後隻需要填寫域名,不要帶協定頭。例如回調是<code>https://felord.cn/wechat/callback</code>,隻能填寫成這樣:
然後我們就可以開發了。
基于 Spring Security 5.x
微信網頁授權的文檔在網頁授權,這裡不再贅述。我們隻聊聊如何結合Spring Security的事。微信網頁授權是通過OAuth2.0機制實作的,在使用者授權給公衆号後,公衆号可以擷取到一個網頁授權特有的接口調用憑證(網頁授權<code>access_token</code>),通過網頁授權獲得的<code>access_token</code>可以進行授權後接口調用,如擷取使用者的基本資訊。
我們需要引入Spring Security提供的OAuth2.0相關的子產品:
由于我們需要擷取使用者的微信資訊,是以要用到<code>OAuth2.0 Login</code>;如果你用不到使用者資訊可以選擇<code>OAuth2.0 Client</code>。
接着按照微信提供的流程來結合Spring Security。
微信網頁授權使用的是OAuth2.0的授權碼模式。我們先來看如何擷取授權碼。
這是微信擷取<code>code</code>的OAuth2.0端點模闆,這不是一個純粹的OAuth2.0協定。微信做了一些參數上的變動。這裡原生的<code>client_id</code>被替換成了<code>appid</code>,而且末尾還要加<code>#wechat_redirect</code>。這無疑增加了內建的難度。
這裡先放一放,我們目标轉向Spring Security的<code>code</code>擷取流程。
Spring Security會提供一個模版連結:
當使用該連結請求OAuth2.0用戶端時會被<code>OAuth2AuthorizationRequestRedirectFilter</code>攔截。機制這裡不講了,在我個人部落格<code>felord.cn</code>中的<code>Spring Security 實戰幹貨:用戶端OAuth2授權請求的入口</code>一文中有詳細闡述。
攔截之後會根據配置組裝擷取授權碼的請求URL,由于微信的不一樣是以我們針對性的定制,也就是改造<code>OAuth2AuthorizationRequestRedirectFilter</code>中的<code>OAuth2AuthorizationRequestResolver</code>。
因為Spring Security會根據模闆連結去組裝一個連結而不是我們填參數就行了,是以需要我們對建構URL的處理器進行自定義。
把上面個性化改造的邏輯配置到<code>OAuth2AuthorizationRequestResolver</code>:
适配好的<code>OAuth2AuthorizationRequestResolver</code>配置到<code>HttpSecurity</code>,僞代碼:
接下來第二步是用<code>code</code>去換<code>token</code>。
這是微信網頁授權擷取<code>access_token</code>的模闆:
其中前半段<code>https://api.weixin.qq.com/sns/oauth2/refresh_token</code>可以通過配置OAuth2.0的<code>token-uri</code>來指定;後半段參數需要我們針對微信進行定制。Spring Security中定制<code>token-uri</code>的工具由<code>OAuth2AuthorizationCodeGrantRequestEntityConverter</code>這個轉換器負責,這裡需要來改造一下。
我們先拼接參數:
然後生成<code>RestTemplate</code>的請求對象<code>RequestEntity</code>:
這樣相容性就改造好了。
微信公衆号授權<code>token-uri</code>的傳回值雖然文檔說是個<code>json</code>,可它喵的<code>Content-Type</code>是<code>text-plain</code>。如果是<code>application/json</code>,Spring Security就直接接收了。你說微信坑不坑?我們隻能再寫個适配來正确的反序列化微信接口的傳回值。
Spring Security 中對<code>token-uri</code>的傳回值的解析轉換同樣由<code>OAuth2AccessTokenResponseClient</code>中的<code>OAuth2AccessTokenResponseHttpMessageConverter</code>負責。
首先增加<code>Content-Type</code>為<code>text-plain</code>的适配;其次因為Spring Security接收<code>token</code>傳回的對象要求必須顯式聲明<code>tokenType</code>,而微信傳回的響應體中沒有,我們一律指定為<code>OAuth2AccessToken.TokenType.BEARER</code>即可相容。代碼比較簡單就不放了,有興趣可以去看我給的DEMO。
先配置好我們上面兩個步驟的請求用戶端:
再把請求用戶端配置到<code>HttpSecurity</code>:
微信公衆号網頁授權擷取使用者資訊需要<code>scope</code>包含<code>snsapi_userinfo</code>。
Spring Security中定義了一個OAuth2.0擷取使用者資訊的抽象接口:
是以我們針對性的實作即可,需要實作三個相關概念。
<code>OAuth2UserRequest</code>是請求<code>user-info-uri</code>的入參實體,包含了三大塊屬性:
<code>ClientRegistration</code> 微信OAuth2.0用戶端配置
<code>OAuth2AccessToken</code> 從<code>token-uri</code>擷取的<code>access_token</code>的抽象實體
<code>additionalParameters</code> 一些<code>token-uri</code>傳回的額外參數,比如<code>openid</code>就可以從這裡面取得
根據微信擷取使用者資訊的端點API這個能滿足需要,不過需要注意的是。如果使用的是 OAuth2.0 Client 就無法從<code>additionalParameters</code>擷取<code>openid</code>等額外參數。
這個用來封裝微信使用者資訊,細節看下面的注釋:
注意: <code>getName()</code>一定不能傳回<code>null</code>。
參數<code>OAuth2UserRequest</code>和傳回值<code>OAuth2User</code>都準備好了,就剩下去請求微信伺服器了。借鑒請求<code>token-uri</code>的實作,還是一個<code>RestTemplate</code>調用,核心就這幾行:
這裡補充一下,寫一個授權成功後跳轉的接口并配置為授權登入成功後的跳轉的url。
在這個接口裡可以通過<code>@RegisteredOAuth2AuthorizedClient</code>和<code>@AuthenticationPrincipal</code>分别拿到認證用戶端的資訊和使用者資訊。
到此微信公衆号授權就內建到Spring Security中了。
<code>application.yaml</code>相關的配置:
<code>關注公衆号:Felordcn 擷取更多資訊</code>
個人部落格:https://felord.cn
部落客:碼農小胖哥
出處:felord.cn
本文版權歸原作者所有,不可商用,轉載需要聲明出處,否則保留追究法律責任的權利。如果文中有什麼錯誤,歡迎指出。以免更多的人被誤導。