1. 程式人生 > >Spring Security 整合 微信小程式登入的思路探討

Spring Security 整合 微信小程式登入的思路探討

![](https://img2020.cnblogs.com/other/1739473/202103/1739473-20210305142814994-760395158.png) ## 1. 前言 原本打算把**Spring Security**中**OAuth 2.0**的機制講完後,用小程式登入來實戰一下,發現小程式登入流程和**Spring Security**中**OAuth 2.0**登入的流程有點不一樣,就把寫了半天的東西全部推翻了。但是,但是過了一天之後,突然感覺又可以了。我們來一起試一試。 ## 2. 小程式登入流程分析 小程式的登入流程是這樣的: ![微信小程式登入時序圖](https://img2020.cnblogs.com/other/1739473/202103/1739473-20210305142815188-1393094143.jpg) 而在**Spring Security**中的**OAuth 2.0** `Code`模式是這樣的: ![Spring Security OAuth2.0 Code模式時序圖](https://img2020.cnblogs.com/other/1739473/202103/1739473-20210305142815343-1812433543.png) 從這兩張圖上看最大的差別就是微信小程式中獲取`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`執行向微信伺服器請求獲取結果,處理後返回登入態。時序圖如下: ![小程式登入開發時序圖](https://img2020.cnblogs.com/other/1739473/202103/1739473-20210305142815530-1996002030.png) 對應的虛擬碼實現: ```java 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 必要的校驗自己寫 Multi