Spring Security源碼分析四:Spring Social實現微信社交登錄

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

社交登錄又稱作社會化登錄(Social Login),是指網站的用戶可以使用騰訊QQ、人人網、開心網、新浪微博、搜狐微博、騰訊微博、淘寶、豆瓣、MSN、Google等社會化媒體賬號登錄該網站。

前言

在上一章Spring-Security源碼分析三-Spring-Social社交登錄過程中,我們已經實現了使用Spring Social+Security的QQ社交登錄。本章我們將實現微信的社交登錄。(微信和QQ登錄的大體流程相同,但存在一些細節上的差異,下面我們來簡單實現一下)

準備工作

  1. 熟悉OAuth2.0協議標準,微信登錄是基于OAuth2.0中的authorization_code模式的授權登錄;
  2. 微信開放平臺申請網站應用開發,獲取appidappsecret
  3. 熟讀網站應用微信登錄開發指南
  4. 參考Spring-Security源碼分析三-Spring-Social社交登錄過程的準備工作

為了方便大家測試,博主在某寶租用了一個月的appid和appSecret

appidwxfd6965ab1fc6adb2
appsecret66bb4566de776ac699ec1dbed0cc3dd1

目錄結構

http://dandandeshangni.oss-cn-beijing.aliyuncs.com/github/Spring%20Security/spring_social_weixin.png

參考

  1. api 定義api綁定的公共接口
  2. config 微信的一些配置信息
  3. connect與服務提供商建立連接所需的一些類。

定義返回用戶信息接口

public interface Weixin {WeixinUserInfo getUserInfo(String openId);
}

這里我們看到相對于QQ的getUserInfo微信多了一個參數openId。這是因為微信文檔中在OAuth2.0的認證流程示意圖第五步時,微信的openidaccess_token一起返回。而Spring Social獲取access_token的類AccessGrant.java中沒有openid。因此我們自己需要擴展一下Spring Social獲取令牌的類(AccessGrant.java);

處理微信返回的access_token類(添加openid)

@Data
public class WeixinAccessGrant extends AccessGrant{private String openId;public WeixinAccessGrant() {super("");}public WeixinAccessGrant(String accessToken, String scope, String refreshToken, Long expiresIn) {super(accessToken, scope, refreshToken, expiresIn);}
}

實現返回用戶信息接口

public class WeiXinImpl extends AbstractOAuth2ApiBinding implements Weixin {/*** 獲取用戶信息的url*/private static final String WEIXIN_URL_GET_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?openid=";private ObjectMapper objectMapper = new ObjectMapper();public WeiXinImpl(String accessToken) {super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);}/*** 獲取用戶信息** @param openId* @return*/@Overridepublic WeixinUserInfo getUserInfo(String openId) {String url = WEIXIN_URL_GET_USER_INFO + openId;String result = getRestTemplate().getForObject(url, String.class);if(StringUtils.contains(result, "errcode")) {return null;}WeixinUserInfo userInfo = null;try{userInfo = objectMapper.readValue(result,WeixinUserInfo.class);}catch (Exception e){e.printStackTrace();}return userInfo;}/*** 使用utf-8 替換默認的ISO-8859-1編碼* @return*/@Overrideprotected List<HttpMessageConverter<?>> getMessageConverters() {List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();messageConverters.remove(0);messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));return messageConverters;}
}

QQ獲取用戶信息相比,微信的實現類中少了一步通過access_token獲取openid的請求。openid由自己定義的擴展類WeixinAccessGrant中獲取;

WeixinOAuth2Template處理微信返回的令牌信息

@Slf4j
public class WeixinOAuth2Template extends OAuth2Template {private String clientId;private String clientSecret;private String accessTokenUrl;private static final String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token";public WeixinOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {super(clientId, clientSecret, authorizeUrl, accessTokenUrl);setUseParametersForClientAuthentication(true);this.clientId = clientId;this.clientSecret = clientSecret;this.accessTokenUrl = accessTokenUrl;}/* (non-Javadoc)* @see org.springframework.social.oauth2.OAuth2Template#exchangeForAccess(java.lang.String, java.lang.String, org.springframework.util.MultiValueMap)*/@Overridepublic AccessGrant exchangeForAccess(String authorizationCode, String redirectUri,MultiValueMap<String, String> parameters) {StringBuilder accessTokenRequestUrl = new StringBuilder(accessTokenUrl);accessTokenRequestUrl.append("?appid="+clientId);accessTokenRequestUrl.append("&secret="+clientSecret);accessTokenRequestUrl.append("&code="+authorizationCode);accessTokenRequestUrl.append("&grant_type=authorization_code");accessTokenRequestUrl.append("&redirect_uri="+redirectUri);return getAccessToken(accessTokenRequestUrl);}public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters) {StringBuilder refreshTokenUrl = new StringBuilder(REFRESH_TOKEN_URL);refreshTokenUrl.append("?appid="+clientId);refreshTokenUrl.append("&grant_type=refresh_token");refreshTokenUrl.append("&refresh_token="+refreshToken);return getAccessToken(refreshTokenUrl);}@SuppressWarnings("unchecked")private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) {log.info("獲取access_token, 請求URL: "+accessTokenRequestUrl.toString());String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);log.info("獲取access_token, 響應內容: "+response);Map<String, Object> result = null;try {result = new ObjectMapper().readValue(response, Map.class);} catch (Exception e) {e.printStackTrace();}//返回錯誤碼時直接返回空if(StringUtils.isNotBlank(MapUtils.getString(result, "errcode"))){String errcode = MapUtils.getString(result, "errcode");String errmsg = MapUtils.getString(result, "errmsg");throw new RuntimeException("獲取access token失敗, errcode:"+errcode+", errmsg:"+errmsg);}WeixinAccessGrant accessToken = new WeixinAccessGrant(MapUtils.getString(result, "access_token"),MapUtils.getString(result, "scope"),MapUtils.getString(result, "refresh_token"),MapUtils.getLong(result, "expires_in"));accessToken.setOpenId(MapUtils.getString(result, "openid"));return accessToken;}/*** 構建獲取授權碼的請求。也就是引導用戶跳轉到微信的地址。*/public String buildAuthenticateUrl(OAuth2Parameters parameters) {String url = super.buildAuthenticateUrl(parameters);url = url + "&appid="+clientId+"&scope=snsapi_login";return url;}public String buildAuthorizeUrl(OAuth2Parameters parameters) {return buildAuthenticateUrl(parameters);}/*** 微信返回的contentType是html/text,添加相應的HttpMessageConverter來處理。*/protected RestTemplate createRestTemplate() {RestTemplate restTemplate = super.createRestTemplate();restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));return restTemplate;}
}

QQ處理令牌類相比多了三個全局變量并且復寫了exchangeForAccess方法。這是因為微信在通過code獲取access_token是傳遞的參數是appidsecret而不是標準的client_idclient_secret

WeixinServiceProvider連接服務提供商

public class WeixinServiceProvider extends AbstractOAuth2ServiceProvider<Weixin> {/*** 微信獲取授權碼的url*/private static final String WEIXIN_URL_AUTHORIZE = "https://open.weixin.qq.com/connect/qrconnect";/*** 微信獲取accessToken的url(微信在獲取accessToken時也已經返回openId)*/private static final String WEIXIN_URL_ACCESS_TOKEN = "https://api.weixin.qq.com/sns/oauth2/access_token";public WeixinServiceProvider(String appId, String appSecret) {super(new WeixinOAuth2Template(appId, appSecret, WEIXIN_URL_AUTHORIZE, WEIXIN_URL_ACCESS_TOKEN));}@Overridepublic Weixin getApi(String accessToken) {return new WeiXinImpl(accessToken);}
}

WeixinConnectionFactory連接服務提供商的工廠類

public class WeixinConnectionFactory extends OAuth2ConnectionFactory<Weixin> {/*** @param appId* @param appSecret*/public WeixinConnectionFactory(String providerId, String appId, String appSecret) {super(providerId, new WeixinServiceProvider(appId, appSecret), new WeixinAdapter());}/*** 由于微信的openId是和accessToken一起返回的,所以在這里直接根據accessToken設置providerUserId即可,不用像QQ那樣通過QQAdapter來獲取*/@Overrideprotected String extractProviderUserId(AccessGrant accessGrant) {if(accessGrant instanceof WeixinAccessGrant) {return ((WeixinAccessGrant)accessGrant).getOpenId();}return null;}/* (non-Javadoc)* @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.oauth2.AccessGrant)*/public Connection<Weixin> createConnection(AccessGrant accessGrant) {return new OAuth2Connection<Weixin>(getProviderId(), extractProviderUserId(accessGrant), accessGrant.getAccessToken(),accessGrant.getRefreshToken(), accessGrant.getExpireTime(), getOAuth2ServiceProvider(), getApiAdapter(extractProviderUserId(accessGrant)));}/* (non-Javadoc)* @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.connect.ConnectionData)*/public Connection<Weixin> createConnection(ConnectionData data) {return new OAuth2Connection<Weixin>(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId()));}private ApiAdapter<Weixin> getApiAdapter(String providerUserId) {return new WeixinAdapter(providerUserId);}private OAuth2ServiceProvider<Weixin> getOAuth2ServiceProvider() {return (OAuth2ServiceProvider<Weixin>) getServiceProvider();}}

WeixinAdapter將微信api返回的數據模型適配Spring Social的標準模型

public class WeixinAdapter implements ApiAdapter<Weixin> {private String openId;public WeixinAdapter() {}public WeixinAdapter(String openId) {this.openId = openId;}@Overridepublic boolean test(Weixin api) {return true;}@Overridepublic void setConnectionValues(Weixin api, ConnectionValues values) {WeixinUserInfo userInfo = api.getUserInfo(openId);values.setProviderUserId(userInfo.getOpenid());values.setDisplayName(userInfo.getNickname());values.setImageUrl(userInfo.getHeadimgurl());}@Overridepublic UserProfile fetchUserProfile(Weixin api) {return null;}@Overridepublic void updateStatus(Weixin api, String message) {}
}

WeixinAuthConfig創建工廠和設置數據源

@Configuration
public class WeixinAuthConfig extends SocialAutoConfigurerAdapter {@Autowiredprivate DataSource dataSource;@Autowiredprivate ConnectionSignUp myConnectionSignUp;@Overrideprotected ConnectionFactory<?> createConnectionFactory() {return new WeixinConnectionFactory(DEFAULT_SOCIAL_WEIXIN_PROVIDER_ID, SecurityConstants.DEFAULT_SOCIAL_WEIXIN_APP_ID,SecurityConstants.DEFAULT_SOCIAL_WEIXIN_APP_SECRET);}@Overridepublic UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,connectionFactoryLocator, Encryptors.noOpText());if (myConnectionSignUp != null) {repository.setConnectionSignUp(myConnectionSignUp);}return repository;}/*** /connect/weixin POST請求,綁定微信返回connect/weixinConnected視圖* /connect/weixin DELETE請求,解綁返回connect/weixinConnect視圖* @return*/@Bean({"connect/weixinConnect", "connect/weixinConnected"})@ConditionalOnMissingBean(name = "weixinConnectedView")public View weixinConnectedView() {return new SocialConnectView();}}

社交登錄配置類

由于社交登錄都是通過SocialAuthenticationFilter過濾器攔截的,如果 上一章 已經配置過,則本章不需要配置。

效果如下:

http://dandandeshangni.oss-cn-beijing.aliyuncs.com/github/Spring%20Security/weixin.gif

代碼下載

從我的 github 中下載,https://github.com/longfeizheng/logback

轉載于:https://my.oschina.net/merryyou/blog/1606071

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/539636.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/539636.shtml
英文地址,請注明出處:http://en.pswp.cn/news/539636.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

jdbc開發優缺點

1&#xff09;優點&#xff1a;簡單易學,上手快,非常靈活構建SQL&#xff0c;效率高 2&#xff09;缺點&#xff1a;代碼繁瑣&#xff0c;難以寫出高質量的代碼&#xff08;例如&#xff1a;資源的釋放&#xff0c;SQL注入安全性等&#xff09; 開發者既要寫業務邏輯&#xff0…

java numberformat 方法_Java NumberFormat parse()用法及代碼示例

parse(str)方法是java.text.NumberFormat的內置方法&#xff0c;該方法從給定字符串的開頭解析文本以產生數字。該方法可能不使用給定字符串的整個文本用法:public Number parse?(String str)參數&#xff1a;該函數接受一個字符串str&#xff0c;其開頭應進行分析。返回值&am…

hibernate開發優缺點

1&#xff09;優點&#xff1a;不用寫SQL&#xff0c;完全以面向對象的方式設計和訪問&#xff0c;不用管底層具體數據庫的語法&#xff0c;&#xff08;例如&#xff1a;分頁&#xff09;便于理解。 2&#xff09;缺點&#xff1a;處理復雜業務時&#xff0c;靈活度差, 復雜的…

求方程的解 Solve the Equation

為什么80%的碼農都做不了架構師&#xff1f;>>> 問題&#xff1a; Solve a given equation and return the value of x in the form of string "x#value". The equation contains only , - operation, the variable x and its coefficient. If there is …

[C#學習] DataAdapter.Fill()分頁

查詢結果分頁是以較小數據子集&#xff08;即頁&#xff09;的形式返回查詢結果的過程。 它通常用于以易于管理的小塊形式向用戶顯示結果。DataAdapter 提供了通過 Fill 方法的重載來僅返回一頁數據的功能。 但是&#xff0c;對于大量的查詢結果&#xff0c;它可能并不是首選的…

java流讀取字符串_javaIO之字符流是怎么讀取的?

最近在看io相關的知識&#xff0c;遇到一些小問題&#xff0c;以下有例子來說明問題&#xff1a;比如&#xff1a;12345 是一個十進制數根據ASCII碼找到是 &#xff1a;二進制 00110001 00110010 00110011 00110100 00110101十進制4950515253十六進制 0x310x320x330x340x35文件…

什么是mybatis,mybatis有什么特點

jdbc開發優缺點&#xff1a;http://blog.csdn.net/zengmingen/article/details/51180796 hibernate開發優缺點&#xff1a;http://blog.csdn.net/zengmingen/article/details/51180805 1&#xff09;基于上述二種支持&#xff0c;我們需要在中間找到一個平衡點呢&#xff1f;結…

簡易RPC框架實現

寫在最前面 PRC(Remote Procedure Call) 遠程過程調用。通俗的講就是程序通過RPC框架調用遠程主機的方法就如同調用本地方法一樣。Dubbo就是這樣一個Rpc框架&#xff0c;本文主要參考Dubbo的設計思路&#xff0c;簡易實現了Rpc框架。 本文涉及到知識點包括&#xff1a; Jdk 動態…

kafka java獲取topic_通過編程方式獲取Kafka中Topic的Metadata信息

如果我們需要通過編程的方式來獲取到TopicMetadataRequest請求到 def findLeader(topic: String): Unit {val consumer connect("www.iteblog.com", 9092)val req TopicMetadataRequest(TopicMetadataRequest.CurrentVersion,0, kafkaGroupId, List(topic))val to…

redis java 遍歷key_java遍歷讀取整個redis數據庫實例

redis提供了靈活的數據查詢方式&#xff0c;最牛的就是key的搜索支持正則表達式。jedis.keys(“*”);表示搜索所有keyjedis.keys(“abc*”)表示搜索開頭為abc的key數據遍歷了key就能遍歷到value。其實就是一個setRedisDO rd new RedisDO();rd.open();Set s rd.jedis.keys(&qu…

js學習

為什么80%的碼農都做不了架構師&#xff1f;>>> /* my code */ var gArrSpell [ 1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, 999999, AAAA, bbbb, cccc, dddd, eeee, fffff ];var gArrSplDmg [11,12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24…

代碼在eclipse下不報錯,在doc命令行下報錯--jar file和runable jar file

今天開發一個小工具&#xff0c;引用了Log4j&#xff0c;來記錄日志&#xff0c;在eclipse下運行&#xff0c;代碼正常&#xff0c;打包成jar放到doc命令行下運行報錯&#xff1a; Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/logging/…

gradle java ide_使用Gradle構建Java項目

使用Gradle構建Java項目這個手冊將通過一個簡單的Java項目向大家介紹如何使用Gradle構建Java項目。我們將要做什么&#xff1f;我們將在這篇文檔航中創建一個簡單的Java項目&#xff0c;然后使用Gradle構建它。需要準備什么&#xff1f;預留15分鐘空閑時間一件稱手的兵器(你最喜…

小馬激活軟件下載,當心偽小馬,有病毒

官方的小馬激活軟件已經停止更新了&#xff0c;下文是官方停更公告。 http://www.pccppc.com/xiaomajihuo-html 所以小馬oem7以后的都不是官方的&#xff0c;包含病毒。重裝系統后&#xff0c;一般是先激活系統&#xff0c;再安裝殺毒軟件&#xff0c;這就給“偽小馬激活工具”…

truncate,delete,drop之間的區別

TRUNCATE TABLE 在功能上與不帶 WHERE 子句的 DELETE 語句相同&#xff1a;二者均刪除表中的全部行。 但 TRUNCATE TABLE 比 DELETE 速度快&#xff0c;且使用的系統和事務日志資源少。 DELETE 語句每次刪除一行&#xff0c;并在事務日志中為所刪除的每行記錄一項。 TRUNCATE…

三張圖搞懂JavaScript的原型對象與原型鏈

對于新人來說&#xff0c;JavaScript的原型是一個很讓人頭疼的事情&#xff0c;一來prototype容易與__proto__混淆&#xff0c;二來它們之間的各種指向實在有些復雜&#xff0c;其實市面上已經有非常多的文章在嘗試說清楚&#xff0c;有一張所謂很經典的圖&#xff0c;上面畫了…

python partial_如何在python多處理模塊中使用partial函數?

下面是我如何解決這個問題的一個簡單例子from functools import partialfrom multiprocessing import Pooldef VariadicLifter(func, args):return func(*args)def func(x,y,z,a):return x2*y3*z4*aif __name__ __main__:func_ partial( func, 500, 1007)lfunc_ partial( Va…

Mybatis中resultMap

MyBatis中在查詢進行select映射的時候&#xff0c;返回類型可以用resultType&#xff0c;也可以用resultMap&#xff0c;resultType是直接 表示返回類型的&#xff0c;而resultMap則是對外部ResultMap的引用&#xff0c;但是resultType跟resultMap不能同時存在。 1.resultType …

超簡單的mysql多實例布置

一、基本概念mysql下載&#xff1a;http://mirrors.sohu.com/mysql/MySQL-5.5/1、MySQL多實例就是在一臺機器上面開啟多個不同的端口&#xff0c;運行多個MySQL服務進程。這些MySQL多實例公用一套安裝程序&#xff0c;使用不同的(也可以是相同的)配置文件&#xff0c;啟動程序&…

java程序設計計算器_Java程序設計計算器(含代碼)

Java程序課程設計任務書實用性計算器的設計與開發1、主要內容&#xff1a;開發一個實用型的計算器程序&#xff0c;實現基本的計算功能同時并進行相應的功能拓展&#xff0c;使其具更加人性化的功能。我們可以用其進行相應的計算功能來方便我們的學習&#xff0c;代替我們進行一…