欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringSecurityOAuth2實現(xiàn)微信授權登錄

 更新時間:2023年09月25日 10:02:21   作者:Lucas小毛驢  
微信的登錄功能是用戶注冊和使用微信的必經(jīng)之路之一,而微信授權登錄更是方便了用戶的登錄操作,本文主要介紹了SpringSecurityOAuth2實現(xiàn)微信授權登錄,感興趣的可以了解一下

繼上一篇走了下登錄的流程后,已經(jīng)熟悉了不少,這一篇就來嘗試下微信的授權登錄實現(xiàn),之所以學下微信,是因為微信在當前的地位還是無人可及的,而且也是因為微信的OAuth2比較不同,挺多需要自定義的,因此來搞下微信授權登錄,會了這個,相信別的第三方都可以輕松應對。

一. 準備工作

  • 工程建立與之前一樣

  • 配置OAuth應用
    對比之前的Github和Gitee,咱們都在他們那創(chuàng)建了自己的OAuth應用,那么對于微信來說,也是需要的,只是微信有些特殊,微信平臺限制只有微信公眾號服務號才能使用授權登錄。那我們這種普通使用者是不是沒法搞了?
    實際上,微信還是提供了一個測試平臺來供我們模擬服務號進行功能測試,我們可以到微信公眾平臺接口申請測試賬號
    通過掃碼登錄后,會顯示如下頁面:

    在這里插入圖片描述

微信的不是叫ClientID,而是appid

你以為這樣就OK啦?當然不是!看到了那個接口配置信息了沒,微信需要我們配置一個接口,然后在提交時他會去請求我們的接口,做一次校驗,我們需要在自己的服務器提供這樣的接口,并且按微信的要求正確返回,他才認為我們的服務器是正常的。

具體的要求可以看他的文檔:消息接口使用指南其中最關鍵的就是這個:

在這里插入圖片描述

其實這個也好辦,咱們寫個程序就可以了,但是這里又會有另一問題需要解決,我們自己在電腦寫的應用,電腦的網(wǎng)絡大概率是內(nèi)網(wǎng)(除非你在有公網(wǎng)的服務器開發(fā)),那微信的服務器要怎么請求到我們內(nèi)網(wǎng)的電腦?

這就需要我們?nèi)ジ阋粋€內(nèi)網(wǎng)穿透了。

  • 內(nèi)網(wǎng)穿透配置
    推薦一款免費的工具:cpolar

要注意的是好像24h還是多長時間,這個域名會自動刷新的,所以也僅僅是適合我們測試用用

這里我配置了幾個隧道,分別映射本地的80端口和8844端口

在這里插入圖片描述

80端口是為了給微信服務器能用http請求我們接口
8844是應用程序開啟的端口

  • 回到第二步配置接口url和Token
    搞定內(nèi)網(wǎng)穿透后,將80端口對應的http的接口填入微信配置中:

    在這里插入圖片描述

token可以隨便填,但需要和接口代碼中的token保持一樣。

這里點擊提交顯示配置失敗,是因為我們的接口還沒寫,微信服務器請求不到正確響應導致。這里我用golang來快速的提供下這個接口:

package main
import (
	"crypto/sha1"
	"encoding/hex"
	"net/http"
	"sort"
	"github.com/gin-gonic/gin"
)
type ByAlphabet []string
func (a ByAlphabet) Len() int {
	return len(a)
}
func (a ByAlphabet) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}
func (a ByAlphabet) Less(i, j int) bool {
	return a[i] < a[j]
}
func SHA1(s string) string {
	hash := sha1.New()
	hash.Write([]byte(s))
	return hex.EncodeToString(hash.Sum(nil))
}
func main() {
	engine := gin.Default()
	engine.GET("/", func(ctx *gin.Context) {
		signature := ctx.Query("signature")
		timestamp := ctx.Query("timestamp")
		nonce := ctx.Query("nonce")
		echostr := ctx.Query("echostr")
		token := "lucas"
		tmpSlice := []string{nonce, timestamp, token}
		// 1.按字典序排序
		sort.Sort(ByAlphabet(tmpSlice))
		// 2.三個字段拼接為str
		str := tmpSlice[0] + tmpSlice[1] + tmpSlice[2]
		// 3. 計算str的sha1加密的字符串
		sha1Str := SHA1(str)
		// 4.比較sha1Str和signature,相同則返回echostr
		if sha1Str == signature {
			ctx.String(http.StatusOK, echostr)
			return
		} else {
			ctx.String(http.StatusOK, "")
			return
		}
	})
	engine.Run(":80")
}

啟動應用,然后再在網(wǎng)頁上提交,就可以成功了。

在這里插入圖片描述

  • 到了這一步,離成功也不遠了
    在上面這些操作,實際就是類似之前在gitee中新建一個OAuth app,但是不知道是否還記得,當時我們需要填寫一個授權成功后的回調(diào)url的,接著我們就來微信這配置。
    還是微信公眾平臺測試號管理這個頁面,往下拉,會看到一個體驗接口權限表,沒錯,我們需要獲取用戶信息,就在這個里面:

    在這里插入圖片描述

點擊修改,會展示如下:

在這里插入圖片描述

在這里填入我們的域名,注意不需要協(xié)議頭,只要域名即可,也就是內(nèi)網(wǎng)穿透給我們的那個:7453dd4b.r15.cpolar.top

注意這里不需要配置端口,只需要域名即可

好了,到了這一步,環(huán)境準備就完成了。

二. 開始編碼

  • 閱讀官方文檔
    首先,先看看微信的官方接口文檔說明在這文檔里,我們可以了解到各個接口的請求路徑以及參數(shù),這在接下來配置中需要用到。
    另外,我們也可以看到,微信使用appid而不是clientid,這也是我們需要自定義的地方。

  • 配置文件
    根據(jù)文檔,將相關的配置項寫入

spring:
  security:
    oauth2:
      client:
        registration:
          github:
            clientId: xxxx # 填入自己應用的clientId
            clientSecret: xxxxx # 填入自己應用的clientSecret
            redirectUri: http://localhost:8844/login/oauth2/code/github
          gitee:
            clientId: xxxx # 填入自己應用的clientId
            clientSecret: xxxx # 填入自己應用的clientSecret
            redirectUri: http://localhost:8844/login/oauth2/code/gitee
            authorizationGrantType: authorization_code
          wechat:
            clientId: xxxx # 填入自己應用的appID
            clientSecret: xxxx # 填入自己應用的appsecret
            redirectUri: http://347b2d93.r8.cpolar.top/login/oauth2/code/wechat
            authorizationGrantType: authorization_code
            scope:
            - snsapi_userinfo
            clientName: tencent-wechat
        provider:
          gitee:
            authorizationUri: https://gitee.com/oauth/authorize
            tokenUri: https://gitee.com/oauth/token
            userInfoUri: https://gitee.com/api/v5/user
            userNameAttribute: name
          wechat:
            authorizationUri: https://open.weixin.qq.com/connect/oauth2/authorize
            tokenUri: https://api.weixin.qq.com/sns/oauth2/access_token
            userInfoUri: https://api.weixin.qq.com/sns/userinfo
            userNameAttribute: nickname
  • 自定義配置
    關于自定義配置這塊,我們按照oauth2授權碼的流程,結(jié)合官方文檔接口,一步步看哪些是需要自定義配置的,然后給他定制上。

    在這里插入圖片描述

  • 第一步是去申請授權碼

    在這里插入圖片描述

可以看到這里就需要自定義了,因為參數(shù)變?yōu)榱?code>appid以及需要加一個錨點#wechat_redirect

  • 回顧下之前走過的登錄流程分析,我們已經(jīng)配置好了微信的Provider,在訪問受限制的接口時會跳轉(zhuǎn)到登錄頁面,點擊wechat,就會被OAuth2AuthorizationRequestRedirectFilter過濾器過濾處理,因此我們要自定義參數(shù),需要到這個過濾器中去查找可自定義的地方。
  • 之前也分析過,在默認的實現(xiàn)類DefaultOAuth2AuthorizationRequestResolver解析請求時預留了一個this.authorizationRequestCustomizer.accept(builder),而這個builder就是構(gòu)建請求的
  • 因此我們可以實現(xiàn)這個authorizationRequestCustomizer,再將它set進去:
private final static String WECHAT_APPID = "appid";
private final static String WECHAT_SECRET = "secret";
private final static String WECHAT_FRAGMENT = "wechat_redirect";
/**
 * 1. 自定義微信獲取授權碼的uri
 * https://open.weixin.qq.com/connect/oauth2/authorize?
 * appid=wx807d86fb6b3d4fd2
 * &redirect_uri=http%3A%2F%2Fdevelopers.weixin.qq.com
 * &response_type=code
 * &scope=snsapi_userinfo
 * &state=STATE  非必須
 * #wechat_redirect
 * 微信比較特殊,比如不是clientid,而是appid,還強制需要一個錨點#wechat+redirect
 * @return
 */
public OAuth2AuthorizationRequestResolver customOAuth2AuthorizationRequestResolver(ClientRegistrationRepository clientRegistrationRepository) {
    // 定義一個默認的oauth2請求解析器
    DefaultOAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI);
    // 進行自定義
    Consumer<OAuth2AuthorizationRequest.Builder> authorizationRequestCustomizer = (builder) -> {
        builder.attributes(attributeConsumer -> {
            // 判斷registrationId是否為wechat
            String registrationId = (String) attributeConsumer.get(OAuth2ParameterNames.REGISTRATION_ID);
            if ("wechat".equals(registrationId)) {
                // 替換參數(shù)名稱
                builder.parameters(this::replaceWechatUriParamter);
                // 增加錨點,需要在uri構(gòu)建中添加
                builder.authorizationRequestUri((uriBuilder) -> {
                    uriBuilder.fragment(WECHAT_FRAGMENT);
                    return uriBuilder.build();
                });
            }
        });
    };
    // 設置authorizationRequestCustomizer
    oAuth2AuthorizationRequestResolver.setAuthorizationRequestCustomizer(authorizationRequestCustomizer);
    return oAuth2AuthorizationRequestResolver;
}
/**
 * 替換Uri參數(shù),parameterMap是保存的請求的各個參數(shù)
 * @param parameterMap
 */
private void replaceWechatUriParamter(Map<String, Object> parameterMap) {
    Map<String, Object> linkedHashMap = new LinkedHashMap<>();
    // 遍歷所有參數(shù),有序的,替換掉clientId為appid
    parameterMap.forEach((k, v) -> {
        if (OAuth2ParameterNames.CLIENT_ID.equals(k)) {
            linkedHashMap.put(WECHAT_APPID, v);
        } else {
            linkedHashMap.put(k, v);
        }
    });
    // 清空原始的paramterMap
    parameterMap.clear();
    // 將新的linkedHashMap存入paramterMap
    parameterMap.putAll(linkedHashMap);
}
  • 至于這內(nèi)部替換參數(shù)的做法,可以先看看builder的實現(xiàn),它在構(gòu)建時已經(jīng)創(chuàng)建了所有默認的參數(shù),并且在attributes中放入了registration_id,因此可以先拿到registration_id,再將參數(shù)全部拿出來,再進行遍歷
    DefaultOAuth2AuthorizationRequestResolver中:

    在這里插入圖片描述

而參數(shù)部分,在構(gòu)建uri時已經(jīng)getParameters()將參數(shù)全部拿出來,并且設置到了this.parametersConsumer:

在這里插入圖片描述

調(diào)用builder.parameters的用途就是重新處理參數(shù):

在這里插入圖片描述

這一塊可能比較亂,我只是想告訴你們怎么寫出那個自定義的代碼的,結(jié)合這些應該是可以理解的。

  • 第二步是通過code獲取access_token

    在這里插入圖片描述

可以看到這里的請求參數(shù)也是需要做下變更的。

  • 按照流程,這一步會被OAuth2LoginAuthenticationFilter過濾處理,然后會交給AuthenticationManager,最終會委托給ProviderManager處理,再找到合適的Provider處理,這里是OAuth2LoginAuthenticationProvider,它又讓OAuth2AuthorizationCodeAuthenticationProvider幫忙處理了。

  • 直接來到OAuth2AuthorizationCodeAuthenticationProvider的authenticate()方法,它是交給了accessTokenResponseClient去請求獲取access_token的:

    在這里插入圖片描述

  • 找到OAuth2AccessTokenResponseClient的實現(xiàn)類:DefaultAuthorizationCodeTokenResponseClient,看到他的getTokenResponse方法,存在一個requestEntityConverter,請求實體轉(zhuǎn)換器,并且提供了set方法,這就是說明我們可以自定義替換默認實現(xiàn)

    在這里插入圖片描述

  • 接著進去它的實現(xiàn)類看看做了什么:

    在這里插入圖片描述

一眼看穿,實際就是在構(gòu)造請求參數(shù),那么我們只需要來實現(xiàn)自己的requestEntityConverter就可以在請求參數(shù)上為所欲為了。

5. 參考代碼如下:

private final static String WECHAT_APPID = "appid";
private final static String WECHAT_SECRET = "secret";
private final static String WECHAT_FRAGMENT = "wechat_redirect";
/**
    * 2. 自定義請求access_token時的請求體轉(zhuǎn)換器
    * 獲取access_token
    * https://api.weixin.qq.com/sns/oauth2/access_token?
    * appid=APPID
    * &secret=SECRET
    * &code=CODE 從上一個請求響應中獲取
    * &grant_type=authorization_code  框架幫忙填寫了
    */
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> customOAuth2AccessTokenResponseClient() {
    // 定義默認的Token響應客戶端
    DefaultAuthorizationCodeTokenResponseClient oAuth2AccessTokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
    // 定義默認的轉(zhuǎn)換器
    OAuth2AuthorizationCodeGrantRequestEntityConverter oAuth2AuthorizationCodeGrantRequestEntityConverter = new OAuth2AuthorizationCodeGrantRequestEntityConverter();
    // 自定義參數(shù)轉(zhuǎn)換器
    Converter<OAuth2AuthorizationCodeGrantRequest, MultiValueMap<String, String>> customParameterConverter = (authorizationCodeGrantRequest) -> {
        ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
        OAuth2AuthorizationExchange authorizationExchange = authorizationCodeGrantRequest.getAuthorizationExchange();
        MultiValueMap<String, String> parameters = new LinkedMultiValueMap();
        parameters.add("grant_type", authorizationCodeGrantRequest.getGrantType().getValue());
        parameters.add("code", authorizationExchange.getAuthorizationResponse().getCode());
        String redirectUri = authorizationExchange.getAuthorizationRequest().getRedirectUri();
        String codeVerifier = (String)authorizationExchange.getAuthorizationRequest().getAttribute("code_verifier");
        if (redirectUri != null) {
            parameters.add("redirect_uri", redirectUri);
        }
        parameters.add(WECHAT_APPID, clientRegistration.getClientId());
        parameters.add(WECHAT_SECRET, clientRegistration.getClientSecret());
        if (codeVerifier != null) {
            parameters.add("code_verifier", codeVerifier);
        }
        return parameters;
    };
    // 設置自定義參數(shù)轉(zhuǎn)換器
    oAuth2AuthorizationCodeGrantRequestEntityConverter.setParametersConverter(customParameterConverter);
    // 自定義RestTemplate處理響應content-type為“text/plain”
    OAuth2AccessTokenResponseHttpMessageConverter oAuth2AccessTokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
    oAuth2AccessTokenResponseHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON));
    // 處理TOKEN_TYPE為null的問題,自定義accessTokenResponseParametersConverter,給TOKEN_TYPE賦值
    // 因為已經(jīng)有默認的處理了,只是需要給token_type賦值
    Converter<Map<String, Object>, OAuth2AccessTokenResponse> setAccessTokenResponseConverter = (paramMap) -> {
        DefaultMapOAuth2AccessTokenResponseConverter defaultMapOAuth2AccessTokenResponseConverter = new DefaultMapOAuth2AccessTokenResponseConverter();
        paramMap.put(OAuth2ParameterNames.TOKEN_TYPE, OAuth2AccessToken.TokenType.BEARER.getValue());
        return defaultMapOAuth2AccessTokenResponseConverter.convert(paramMap);
    };
    // 設置這個轉(zhuǎn)換器
    oAuth2AccessTokenResponseHttpMessageConverter.setAccessTokenResponseConverter(setAccessTokenResponseConverter);
    RestTemplate restTemplate = new RestTemplate(Arrays.asList(new FormHttpMessageConverter(), oAuth2AccessTokenResponseHttpMessageConverter));
    restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
    // 設置自定義轉(zhuǎn)換器
    oAuth2AccessTokenResponseClient.setRequestEntityConverter(oAuth2AuthorizationCodeGrantRequestEntityConverter);
    // 設置自定義RestTemplate
    oAuth2AccessTokenResponseClient.setRestOperations(restTemplate);
    return oAuth2AccessTokenResponseClient;
}
  • 注意看上面代碼,除了參數(shù)轉(zhuǎn)換這一部分的自定義外,還多做了一些處理響應的操作,主要原因是微信接口返回的是json字符串,但他的content-type卻不是application/json,而是text/plain!!!,因此在這里會踩坑,沒有做處理的話,可能你會遇到這樣的報錯:

    在這里插入圖片描述

  • 既然返回的是text/plain,那我們也只能做處理去兼容,注意下DefaultAuthorizationCodeTokenResponseClient類不止是提供了我們自定義請求實體轉(zhuǎn)換,他發(fā)起請求的RestOperations也提供了set方法,也就是我們也可以自定義RestOperations來將text/plain給支持進去。

  • 我們可以先看看官方中是怎么設置這個RestOperations,他在構(gòu)造方法中初始化:

    在這里插入圖片描述

在初始化RestTemplate(RestOperations的實現(xiàn)類)時傳入了轉(zhuǎn)換器OAuth2AccessTokenResponseHttpMessageConverter,進去看看:

在這里插入圖片描述

這就是官方自己定義的一個轉(zhuǎn)換器,用來處理請求access_token響應的消息轉(zhuǎn)換器,其實我們自定義就可以照貓畫瓢,照抄這個轉(zhuǎn)換器,再改改適配我們需要的。
但是看到這個轉(zhuǎn)換器也提供了一些自定義的接口:accessTokenResponseConverteraccessTokenResponseParametersConverter,那我們也可以直接就自定義這部分。

  • 接著看看這個OAuth2AccessTokenResponseHttpMessageConverter繼承了AbstractHttpMessageConverter<T> implements HttpMessageConverter<T>,該父類內(nèi)有一個方法可以設置MediaType:

    在這里插入圖片描述

因此我們要想支持text/plain,那我們可以直接調(diào)用這個方法,進行設置,因此有了以下代碼:

OAuth2AccessTokenResponseHttpMessageConverter oAuth2AccessTokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
oAuth2AccessTokenResponseHttpMessageConverter.setSupportedMediaTypes(Arrays.asList(MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON));
  • 到這里,好像都沒問題了,但是一運行起來,又會報錯,這次的坑是:springsecurity默認token響應對象OAuth2AccessTokenResponse中的OAuth2AccessToken對象在構(gòu)造時必須有TokenType這個屬性,否則會報錯

    在這里插入圖片描述

但是我們請求接口時響應數(shù)據(jù)里沒有TokenType,因此我們這里需要再處理下,給他填個值,這里就要用到OAuth2AccessTokenResponseHttpMessageConverter提供的自定義接口accessTokenResponseConverter了,在將參數(shù)轉(zhuǎn)為OAuth2AccessTokenResponse對象時給他的OAuth2AccessToken設置一個TokenType:

// 處理TOKEN_TYPE為null的問題,自定義accessTokenResponseParametersConverter,給TOKEN_TYPE賦值
// 因為已經(jīng)有默認的處理了,只是需要給token_type賦值
Converter<Map<String, Object>, OAuth2AccessTokenResponse> setAccessTokenResponseConverter = (paramMap) -> {
    DefaultMapOAuth2AccessTokenResponseConverter defaultMapOAuth2AccessTokenResponseConverter = new DefaultMapOAuth2AccessTokenResponseConverter();
    paramMap.put(OAuth2ParameterNames.TOKEN_TYPE, OAuth2AccessToken.TokenType.BEARER.getValue());
    return defaultMapOAuth2AccessTokenResponseConverter.convert(paramMap);
};
  • 到這里,第二步的自定義才結(jié)束,這里挺繁瑣的,有兩個坑需要埋,因此嘮叨比較長。
  • 第三步通過access_token獲取用戶信息

    在這里插入圖片描述

在這里依然是需要自定義一些操作,首先就是請求了,然后響應也是需要處理,因為微信響應的用戶信息的實體是不同的,自然也是需要自定義了。

  • 根據(jù)之前的流程分析,我們回到OAuth2LoginAuthenticationProviderauthenticate方法中,在獲取到access_token后,緊接著就是獲取用戶信息了:

    在這里插入圖片描述

這里調(diào)用了一個userService的loadUser方法,并且返回了一個OAuth2User,這個OAuth2User是一個接口,因此我們自定義的用戶實體只要實現(xiàn)它即可作為返回值返回了,在這里先定義出來:

@Data
public class WeChatEntity implements OAuth2User {
    // 用戶的唯一標識
    private String openid;
    // 用戶昵稱
    private String nickname;
    // 用戶的性別,值為1表示男,值為2表示女,值為0表示未知
    private Integer sex;
    // 用戶個人資料填寫的省份
    private String province;
    // 普通用戶個人資料填寫的城市
    private String city;
    // 國家,如中國為CN
    private String country;
    // 用戶頭像,最后一個數(shù)值代表正方形頭像大?。ㄓ?、46、64、96、132數(shù)值可選,0代表640*640正方形頭像),
    // 用戶沒有頭像時該項為空。若用戶更換頭像,原有頭像URL將失效。
    private String headimgurl;
    // 用戶特權信息
    private List<String> privilege;
    // 只有在用戶將公眾號綁定到微信開放平臺帳號后,才會出現(xiàn)該字段。
    private String unionid;
    @Override
    public Map<String, Object> getAttributes() {
        return null;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }
    /**
        不可以返回null,在構(gòu)建實體時會有斷言
    **/
    @Override
    public String getName() {
        return nickname;
    }
}

這里需要注意的就是getName()方法不返回null,因為在OAuth2AuthorizedClient構(gòu)造中斷言它不為空

  • 接著便是要看看這個loadUser做了什么了,找到默認的實現(xiàn)類DefaultOAuth2UserService:

    在這里插入圖片描述

雖然這里也是提供了自定義接口,但是微信獲取用戶信息的接口參數(shù)是query參數(shù),需要拼接在請求url上,獲取的類型也是我們自定義的實體,因此這里不采用直接實現(xiàn)提供的自定義接口的方式,而是直接實現(xiàn)一個我們自己的UserService

3. 實現(xiàn)代碼

  • 首先我們要實現(xiàn)自己的UserService,最好的方法就是直接參考默認實現(xiàn)的,先整個復制,再改成適合我們自己的
  • 第一個要改的地方就是getResponse方法,我們需要自己構(gòu)造請求url:
private ResponseEntity<WeChatEntity> getResponse(OAuth2UserRequest userRequest) {
    OAuth2Error oauth2Error;
    try {
        // 發(fā)起Get請求,請求參數(shù)是query參數(shù),需要自己拼接
        MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>();
        queryParams.add("access_token", userRequest.getAccessToken().getTokenValue());
        // 獲取access token時,其他參數(shù)被存儲在了userRequest中,從里面把openid拿出來
        queryParams.add("openid", (String) userRequest.getAdditionalParameters().get("openid"));
        queryParams.add("lang", "zh_CN");
        URI uri = UriComponentsBuilder.fromUriString(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri()).queryParams(queryParams).build().toUri();
        ResponseEntity<WeChatEntity> retData = this.restOperations.exchange(uri, HttpMethod.GET, null, PARAMETERIZED_RESPONSE_TYPE);
        return retData;
    } catch (OAuth2AuthorizationException var6) {
        oauth2Error = var6.getError();
        StringBuilder errorDetails = new StringBuilder();
        errorDetails.append("Error details: [");
        errorDetails.append("UserInfo Uri: ").append(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri());
        errorDetails.append(", Error Code: ").append(oauth2Error.getErrorCode());
        if (oauth2Error.getDescription() != null) {
            errorDetails.append(", Error Description: ").append(oauth2Error.getDescription());
        }
        errorDetails.append("]");
        oauth2Error = new OAuth2Error("invalid_user_info_response", "An error occurred while attempting to retrieve the UserInfo Resource: " + errorDetails.toString(), (String)null);
        throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), var6);
    } catch (UnknownContentTypeException var7) {
        String errorMessage = "An error occurred while attempting to retrieve the UserInfo Resource from '" + userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri() + "': response contains invalid content type '" + var7.getContentType().toString() + "'. The UserInfo Response should return a JSON object (content type 'application/json') that contains a collection of name and value pairs of the claims about the authenticated End-User. Please ensure the UserInfo Uri in UserInfoEndpoint for Client Registration '" + userRequest.getClientRegistration().getRegistrationId() + "' conforms to the UserInfo Endpoint, as defined in OpenID Connect 1.0: 'https://openid.net/specs/openid-connect-core-1_0.html#UserInfo'";
        oauth2Error = new OAuth2Error("invalid_user_info_response", errorMessage, (String)null);
        throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), var7);
    } catch (RestClientException var8) {
        oauth2Error = new OAuth2Error("invalid_user_info_response", "An error occurred while attempting to retrieve the UserInfo Resource: " + var8.getMessage(), (String)null);
        throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), var8);
    }
}
  • 因為我們的參數(shù)是自己拼接的,因此這個requestEntityConverter轉(zhuǎn)換器就不需要了,可以直接刪除
  • 然后就是loadUser處調(diào)用getResponse:
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
    Assert.notNull(userRequest, "userRequest cannot be null");
    if (!StringUtils.hasText(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) {
        OAuth2Error oauth2Error = new OAuth2Error("missing_user_info_uri", "Missing required UserInfo Uri in UserInfoEndpoint for Client Registration: " + userRequest.getClientRegistration().getRegistrationId(), (String)null);
        throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
    } else {
        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
        if (!StringUtils.hasText(userNameAttributeName)) {
            OAuth2Error oauth2Error = new OAuth2Error("missing_user_name_attribute", "Missing required \"user name\" attribute name in UserInfoEndpoint for Client Registration: " + userRequest.getClientRegistration().getRegistrationId(), (String)null);
            throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
        } else {
            ResponseEntity<WeChatEntity> response = this.getResponse(userRequest);
            // 直接返回最終的實體
            WeChatEntity userAttributes = (WeChatEntity)response.getBody();
            return userAttributes;
        }
    }
}
  • 最后還要處理響應體的轉(zhuǎn)換,將我們獲取到的數(shù)據(jù)轉(zhuǎn)換為WeChatEntity,這就需要Spring的HttpMessageConverter了,而且在微信獲取用戶信息中返回的還是JSON字符串,text/plain,因此我們還需要再處理這些問題,有了上面的處理經(jīng)驗,我們知道是從RestTemplate入手,我們可以參考SpringSecurity官方實現(xiàn)的這個OAuth2AccessTokenResponseHttpMessageConverter,還是照抄,再改寫:
public class WeChatUserHttpMessageConverter extends AbstractHttpMessageConverter<WeChatEntity> {
    private static final ParameterizedTypeReference<WeChatEntity> STRING_OBJECT_MAP;
    private static final Charset DEFAULT_CHARSET;
    private GenericHttpMessageConverter<Object> jsonMessageConverter = HttpMessageConverters.getJsonMessageConverter();
    static {
        DEFAULT_CHARSET = StandardCharsets.UTF_8;
        STRING_OBJECT_MAP = new ParameterizedTypeReference<WeChatEntity>() {
        };
    }
    public WeChatUserHttpMessageConverter() {
        super(DEFAULT_CHARSET, MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
    }
    @Override
    protected boolean supports(Class<?> clazz) {
        return  WeChatEntity.class.isAssignableFrom(clazz);
    }
    @Override
    protected WeChatEntity readInternal(Class<? extends WeChatEntity> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        try {
            WeChatEntity weChatEntity = (WeChatEntity)this.jsonMessageConverter.read(STRING_OBJECT_MAP.getType(), (Class)null, inputMessage);
            return weChatEntity;
        } catch (Exception var5) {
            throw new HttpMessageNotReadableException("An error occurred reading the OAuth 2.0 Access Token Response: " + var5.getMessage(), var5, inputMessage);
        }
    }
    @Override
    protected void writeInternal(WeChatEntity weChatEntity, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    }
}
  • 最后的最后,配置SpringSecurity,以上的自定義,沒有配置到SpringSecurity的filterChain中,是不可能生效的。
@Bean
public SecurityFilterChain filterChain(HttpSecurity http,  ClientRegistrationRepository clientRegistrationRepository) throws Exception {
    http
            .authorizeHttpRequests()
            .anyRequest()
            .authenticated().and()
            .oauth2Login(oauth2LoginCustomizer -> {
                // 授權端點配置
                oauth2LoginCustomizer.authorizationEndpoint().authorizationRequestResolver(customOAuth2AuthorizationRequestResolver(clientRegistrationRepository));
                // 獲取token端點配置
                oauth2LoginCustomizer.tokenEndpoint().accessTokenResponseClient(customOAuth2AccessTokenResponseClient());
                // 獲取用戶信息端點配置
                oauth2LoginCustomizer.userInfoEndpoint().userService(new WeChatUserService());
            });
    return http.build();
}

到了這里就真的大功告成…
接著準備測試…

三. 測試驗證

有了以上的自定義改造后,剩下的就是測試驗證了,對于微信,因為我們只是測試,沒有接入網(wǎng)站應用,因此我們也沒法使用那種二維碼掃碼登錄的方式來測試了。。
但我們可以使用微信開發(fā)者工具來發(fā)起請求,微信開發(fā)者工具需要先使用微信賬號登錄,這樣你發(fā)起請求就相當于是用這個賬號來申請微信的權限。

  • 打開后登錄后如下界面:

    在這里插入圖片描述

  • 啟動我們的應用,然后在微信開發(fā)者工具中訪問http://347b2d93.r8.cpolar.top/hellohttp://347b2d93.r8.cpolar.top/user

    在這里插入圖片描述

  • 點擊tencent-wechat,同意授權:

    在這里插入圖片描述

  • 最后訪問到資源:

    在這里插入圖片描述

    注意:關于獲取用戶信息,性別和地區(qū)等字段是空的問題,不要慌,是因為微信他不再返回這些字段的值了。
    具體可以查看這個:微信公眾平臺用戶信息相關接口調(diào)整公告

四. 總結(jié)

這一篇主要是介紹了對于微信的第三方登錄自定義,講的可能比較亂,還是得結(jié)合源碼理解理解,我只想把思路和為什么盡量都分享清楚,當然這只是測試,真正的支持微信第三方還得需要在微信登記公眾號等操作,那些是需要認證啥的,我們當前學習的話目前的已經(jīng)足夠了。

到此這篇關于SpringSecurityOAuth2實現(xiàn)微信授權登錄的文章就介紹到這了,更多相關SpringSecurityOAuth2微信授權登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java?nacos動態(tài)配置實現(xiàn)流程詳解

    Java?nacos動態(tài)配置實現(xiàn)流程詳解

    使用動態(tài)配置的原因是properties和yaml是寫到項目中的,好多時候有些配置需要修改,每次修改就要重新啟動項目,不僅增加了系統(tǒng)的不穩(wěn)定性,也大大提高了維護成本,非常麻煩,且耗費時間
    2022-09-09
  • Java?list移除元素相關操作指南

    Java?list移除元素相關操作指南

    這篇文章主要給大家介紹了關于Java?list移除元素相關操作的相關資料,文中介紹的方法包括增強for循環(huán)、迭代器、Stream流和removeIf()方法,同時還介紹了如何從一個列表中刪除包含另一個列表元素的方法,以及如何刪除指定下標位置的元素,需要的朋友可以參考下
    2024-12-12
  • 一文帶你了解Java創(chuàng)建型設計模式之原型模式

    一文帶你了解Java創(chuàng)建型設計模式之原型模式

    原型模式其實就是從一個對象在創(chuàng)建另外一個可定制的對象,不需要知道任何創(chuàng)建的細節(jié)。本文就來通過示例為大家詳細聊聊原型模式,需要的可以參考一下
    2022-09-09
  • SpringBoot?內(nèi)置工具類的使用

    SpringBoot?內(nèi)置工具類的使用

    本文主要介紹了SpringBoot?內(nèi)置工具類的使用,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • Java編程Webservice指定超時時間代碼詳解

    Java編程Webservice指定超時時間代碼詳解

    這篇文章主要介紹了Java編程Webservice指定超時時間代碼詳解,簡單介紹了webservice,然后分享了通過使用JDK對Webservice的支持進行Webservice調(diào)用實現(xiàn)指定超時時間完整示例,具有一定借鑒價值,需要的朋友可以參考下。
    2017-11-11
  • Java的MyBatis框架中實現(xiàn)多表連接查詢和查詢結(jié)果分頁

    Java的MyBatis框架中實現(xiàn)多表連接查詢和查詢結(jié)果分頁

    這篇文章主要介紹了Java的MyBatis框架中實現(xiàn)多表連接查詢和查詢結(jié)果分頁,借助MyBatis框架中帶有的動態(tài)SQL查詢功能可以比普通SQL查詢做到更多,需要的朋友可以參考下
    2016-04-04
  • Springboot異步事件配置和使用示例詳解

    Springboot異步事件配置和使用示例詳解

    Spring框架提供了一套事件處理機制,允許應用在各個組件之間傳遞狀態(tài)信息,自定義事件通常繼承自ApplicationEvent類,Springboot通過自動配置簡化了異步處理的配置,實現(xiàn)開箱即用,Spring事件模型核心是觀察者模式,適用于解耦和提高響應速度
    2024-10-10
  • Nacos?版本不一致報錯Request?nacos?server?failed解決

    Nacos?版本不一致報錯Request?nacos?server?failed解決

    這篇文章主要為大家介紹了Nacos?版本不一致報錯Request?nacos?server?failed的解決方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-11-11
  • 聊聊Spring Cloud Cli 初體驗

    聊聊Spring Cloud Cli 初體驗

    這篇文章主要介紹了聊聊Spring Cloud Cli 初體驗,SpringBoot CLI 是spring Boot項目的腳手架工具。非常具有實用價值,需要的朋友可以參考下
    2018-04-04
  • SpringBoot如何讀取resources目錄下的文件

    SpringBoot如何讀取resources目錄下的文件

    這篇文章主要介紹了SpringBoot如何讀取resources目錄下的文件問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07

最新評論