微信小程序登錄與Spring Security結(jié)合的思路
1. 前言
原本打算把Spring Security中OAuth2.0的機(jī)制講完后,用小程序登錄來實(shí)戰(zhàn)一下,發(fā)現(xiàn)小程序登錄流程和Spring Security中OAuth 2.0登錄的流程有點(diǎn)不一樣,就把寫了半天的東西全部推翻了。但是,但是過了一天之后,突然感覺又可以了。我們來一起試一試。
2. 小程序登錄流程分析
小程序的登錄流程是這樣的:
微信小程序登錄時序圖
而在Spring Security中的OAuth 2.0 Code模式是這樣的:
Spring Security OAuth2.0 Code模式時序圖
從這兩張圖上看最大的差別就是微信小程序中獲取code不需要通過后端服務(wù)器的調(diào)用,而Spring Security中需要(第1步,第2步,第3步)。騰訊應(yīng)該也是借鑒了OAuth 2.0,但是做了一些改動。
讓我放棄的也是這個差別,有沒有人能想到解決方法呢?
假如說小程序已經(jīng)持有了code,它依然需要將code傳遞給后端服務(wù)器來執(zhí)行后面的流程。那么我們能不能利用圖2中第3個調(diào)用redirectUri的步驟呢?換個角度來看問題第三方就是小程序反正它也是將一個code傳遞給了后端服務(wù)器,只要返回登錄狀態(tài)就行了,反正剩下的登錄流程都跟小程序無關(guān)。我覺得它是可以的。在Spring Security中我們可以使用code通過tokenUri來換取token。那么在微信小程序登錄流程中,code最終換取的只是登錄態(tài),沒有特定的要求。但是后端肯定需要去獲取用戶的一些信息,比如openId,用戶微信信息之類的??傊鶕?jù)微信平臺提供的API來實(shí)現(xiàn)。通過改造tokenUri和userInfoUri可以做到這一點(diǎn)。
3. 思路借鑒
所有的猜想都沒有錯,而且我也實(shí)現(xiàn)了,但是改造成本過高了,寫了很多兼容性的代碼,如果不深入Spring Security,很難實(shí)現(xiàn)這一點(diǎn),而且也不好理解。
為了簡化實(shí)現(xiàn),我決定借鑒Spring Security中OAuth 2.0的思路。Filter攔截小程序登錄URL,然后通過RestTemplate執(zhí)行向微信服務(wù)器請求獲取結(jié)果,處理后返回登錄態(tài)。時序圖如下:
小程序登錄開發(fā)時序圖
對應(yīng)的偽代碼實(shí)現(xiàn):
- package cn.felord.spring.security.filter;
- import org.springframework.http.ResponseEntity;
- import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
- import org.springframework.security.web.util.matcher.RequestMatcher;
- import org.springframework.util.Assert;
- import org.springframework.util.LinkedMultiValueMap;
- import org.springframework.util.MultiValueMap;
- import org.springframework.web.client.RestTemplate;
- import org.springframework.web.filter.OncePerRequestFilter;
- import org.springframework.web.util.UriComponentsBuilder;
- import javax.servlet.FilterChain;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.net.URI;
- /**
- * 小程序登錄過濾器
- *
- * @author felord.cn
- * @since 1.0.4.RELEASE
- */
- public class WeChatAppLoginFilter extends OncePerRequestFilter {
- private final RequestMatcher requiresAuthenticationRequestMatcher;
- private final RestTemplate restTemplate;
- private String appId;
- private String secret;
- private static final String WX_URL = "https://api.weixin.qq.com/sns/jscode2session";
- public WeChatAppLoginFilter(String loginProcessingUrl, String appId, String secret) {
- this.appId = appId;
- this.secret = secret;
- Assert.notNull(loginProcessingUrl, "loginProcessingUrl must not be null");
- this.requiresAuthenticationRequestMatcher = new AntPathRequestMatcher(loginProcessingUrl, "POST");
- this.restTemplate = new RestTemplate();
- }
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
- // 攔截微信登錄
- if (requiresAuthenticationRequestMatcher.matches(request)) {
- //todo 從request中獲取 code 參數(shù) 這里邏輯根據(jù)你的情況自行實(shí)現(xiàn)
- String jsCode = "你自行實(shí)現(xiàn)從request中獲取";
- //todo 必要的校驗(yàn)自己寫
- MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
- queryParams.add("appid", this.appId);
- queryParams.add("secret", this.secret);
- queryParams.add("js_code", jsCode);
- queryParams.add("grant_type", "authorization_code");
- URI uri = UriComponentsBuilder.fromHttpUrl(WX_URL)
- .queryParams(queryParams)
- .build()
- .toUri();
- //todo 這里 Object 自行封裝為具體對象
- ResponseEntity<Object> result = this.restTemplate.getForEntity(uri, Object.class);
- //todo 處理 result 比如后端存儲、后端授權(quán)、角色資源處理、注冊、對session_key的處理等等你需要的業(yè)務(wù)邏輯
- // 最后放入HttpServletResponse中返回前端返回
- } else {
- filterChain.doFilter(request, response);
- }
- }
- }
最后一定別忘了把過濾器配置到WebSecurityConfigurerAdapter的HttpSecurity中去。
4. 總結(jié)
本篇講解了Spring Security和微信小程序登錄相結(jié)合的思路歷程。本來不需要長篇大論OAuth 2.0,之所以寫出來是讓你明白開發(fā)中要善于發(fā)現(xiàn)一些相似的東西,通過差異對比來探討他們結(jié)合的可能性,這也是一種自我提升的方法。方法遠(yuǎn)比結(jié)果重要,形成自己的方法論就能富有創(chuàng)造力。
本文轉(zhuǎn)載自微信公眾號「碼農(nóng)小胖哥」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系碼農(nóng)小胖哥公眾號。