![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL4UTM1kzMwYzNtQTO5QTM4IDNxUDMzATMyAjMtMzN0kzM3EzLcNDMxIDMy8CXzcDN5MzNx8CXyVGa092Lc12bj5ycn9Gbi52YuAjMwIzZtl2Lc9CX6MHc0RHaiojIsJye.png)
1. 前言
原本打算把Spring Security中OAuth 2.0的機制講完後,用小程式登入來實戰一下,發現小程式登入流程和Spring Security中OAuth 2.0登入的流程有點不一樣,就把寫了半天的東西全部推翻了。但是,但是過了一天之後,突然感覺又可以了。我們來一起試一試。
2. 小程式登入流程分析
小程式的登入流程是這樣的:
而在Spring Security中的OAuth 2.0
Code
模式是這樣的:
從這兩張圖上看最大的差别就是微信小程式中擷取
code
不需要通過後端伺服器的調用,而Spring Security中需要(第1步,第2步,第3步)。騰訊應該也是借鑒了OAuth 2.0,但是做了一些改動。
讓我放棄的也是這個差别,有沒有人能想到解決方法呢?
假如說小程式已經持有了
code
,它依然需要将
code
傳遞給後端伺服器來執行後面的流程。那麼我們能不能利用圖2中第3個調用
redirectUri
的步驟呢?換個角度來看問題第三方就是小程式反正它也是将一個
code
傳遞給了後端伺服器,隻要傳回登入狀态就行了,反正剩下的登入流程都跟小程式無關。我覺得它是可以的。在Spring Security中我們可以使用
code
通過
tokenUri
來換取
token
。那麼在微信小程式登入流程中,
code
最終換取的隻是登入态,沒有特定的要求。但是後端肯定需要去擷取使用者的一些資訊,比如
openId
,使用者微信資訊之類的。總之要根據微信平台提供的API來實作。通過改造
tokenUri
和
userInfoUri
可以做到這一點。
3. 思路借鑒
所有的猜想都沒有錯,而且我也實作了,但是改造成本過高了,寫了很多相容性的代碼,如果不深入Spring Security,很難實作這,而且也不好了解。
為了簡化實作,我決定借鑒Spring Security中OAuth 2.0的思路。Filter攔截小程式登入URL,然後通過
RestTemplate
執行向微信伺服器請求擷取結果,處理後傳回登入态。時序圖如下:
對應的僞代碼實作:
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 參數 這裡邏輯根據你的情況自行實作
String jsCode = "你自行實作從request中擷取";
//todo 必要的校驗自己寫
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 比如後端存儲、後端授權、角色資源處理、注冊、對session_key的處理等等你需要的業務邏輯
// 最後放入HttpServletResponse中傳回前端傳回
} else {
filterChain.doFilter(request, response);
}
}
}
最後一定别忘了把過濾器配置到
WebSecurityConfigurerAdapter
的
HttpSecurity
中去。
4. 總結
本篇講解了Spring Security和微信小程式登入相結合的思路曆程。本來不需要長篇大論OAuth 2.0,之是以寫出來是讓你明白開發中要善于發現一些相似的東西,通過差異對比來探讨他們結合的可能性,這也是一種自我提升的方法。方法遠比結果重要,形成自己的方法論就能富有創造力。我是:碼農小胖哥,多多關注,分享更多原創程式設計幹貨。
關注公衆号:Felordcn 擷取更多資訊
個人部落格:https://felord.cn
部落客:碼農小胖哥 出處:felord.cn 本文版權歸原作者所有,不可商用,轉載需要聲明出處,否則保留追究法律責任的權利。如果文中有什麼錯誤,歡迎指出。以免更多的人被誤導。 |