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.mzph.cn/news/539636.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

求方程的解 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 …

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

最近在看io相关的知识&#xff0c;遇到一些小问题&#xff0c;以下有例子来说明问题&#xff1a;比如&#xff1a;12345 是一个十进制数根据ASCII码找到是 &#xff1a;二进制 00110001 00110010 00110011 00110100 00110101十进制4950515253十六进制 0x310x320x330x340x35文件…

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/…

小马激活软件下载,当心伪小马,有病毒

官方的小马激活软件已经停止更新了&#xff0c;下文是官方停更公告。 http://www.pccppc.com/xiaomajihuo-html 所以小马oem7以后的都不是官方的&#xff0c;包含病毒。重装系统后&#xff0c;一般是先激活系统&#xff0c;再安装杀毒软件&#xff0c;这就给“伪小马激活工具”…

三张图搞懂JavaScript的原型对象与原型链

对于新人来说&#xff0c;JavaScript的原型是一个很让人头疼的事情&#xff0c;一来prototype容易与__proto__混淆&#xff0c;二来它们之间的各种指向实在有些复杂&#xff0c;其实市面上已经有非常多的文章在尝试说清楚&#xff0c;有一张所谓很经典的图&#xff0c;上面画了…

Mybatis中resultMap

MyBatis中在查询进行select映射的时候&#xff0c;返回类型可以用resultType&#xff0c;也可以用resultMap&#xff0c;resultType是直接 表示返回类型的&#xff0c;而resultMap则是对外部ResultMap的引用&#xff0c;但是resultType跟resultMap不能同时存在。 1.resultType …

Erlang/OTP设计原则(文档翻译)

http://erlang.org/doc/design_principles/des_princ.html 图和代码皆源自以上链接中Erlang官方文档&#xff0c;翻译时的版本为20.1。 这个设计原则&#xff0c;其实是说用户在设计系统的时候应遵循的标准和规范。阅读前我一直以为写的是作者在设计 Erlang/OTP 框架时的一些原…

存储过程的参数可以使用sql的函数

系统已做成存储过程&#xff0c;调用方法如下&#xff1a; crh_entry(yyyymmdd,起始时间,结束时间); 示例1&#xff1a;进行2016年4月10日10时到12时的测试&#xff0c;应写为&#xff1a; exec crh_entry(20160410,10,11); 示例2&#xff1a;进行2016年4月8日14时到15时的…

java如何实例化集合_如何在java中实例化一个Queue对象?

Queue是一个接口&#xff0c;这意味着你不能直接构造一个Queue 。最好的select是构造一个已经实现Queue接口的类&#xff0c;如下所示&#xff1a; AbstractQueue &#xff0c; ArrayBlockingQueue &#xff0c; ConcurrentLinkedQueue &#xff0c; DelayQueue &#xff0c; D…

java使用队列实现栈思路_算法面试:队列实现栈的方案

声明&#xff1a;码字不易&#xff0c;转载请注明出处&#xff0c;欢迎文章下方讨论交流。前言&#xff1a;Java数据结构与算法专题会不定时更新&#xff0c;欢迎各位读者监督。本篇介绍的是如何用两个队列实现栈的问题。这道题作为上一篇文章算法面试&#xff1a;栈实现队列的…

Uber如何使用go语言创建高效的查询服务

在2015年初我们创建了一个微服务&#xff0c;它只做一件事&#xff08;也确实做得很好&#xff09;就是地理围栏查询。一年后它成了Uber高频查询&#xff08;QPS&#xff09;服务&#xff0c;本次要讲的故事就是我们为什么创建这个服务&#xff0c;以及编程语言新秀Go如何帮我们…

centos7:塔建pure_ftpd虚拟用户

2019独角兽企业重金招聘Python工程师标准>>> 1.下载pure_ftpd&#xff0c;上传服务器,目录路径:/usr/local/src/ 下载地址:https://pan.baidu.com/s/1kWe8FAn 2.安装pure_ftpd cd /usr/local/srctar -xjf pure-ftpd-1.0.36.tar.bz2cd pure-ftpd-1.0.36./configure -…

寒武纪芯片——有自己的SDK,支持tf、caffe、MXNet

寒武纪芯片产品中心>智能处理器IP智能处理器IP MLU智能芯片 软件开发环境 Cambricon-1A 高性能硬件架构及软件支持兼容Caffe、Tensorflow、MXnet等主流AI开发平台&#xff0c;已多次成功流片 国际上首个成功商用的深度学习处理器IP产品&#xff0c;可广泛应用于计算机视觉、…

基于百度语音识别API的Python语音识别小程序

一、功能概述 实现语音为文字&#xff0c;可以扩展到多种场景进行工作&#xff0c;这里只实现其基本的语言接收及转换功能。 在语言录入时&#xff0c;根据语言内容的多少与停顿时间&#xff0c;自动截取音频进行转换。 工作示例&#xff1a; 二、软件环境 操作系统&#xff1a…

spring框架结构介绍

Spring提供了一站式解决方案&#xff1a; 1&#xff09; Spring Core spring的核心功能&#xff1a; IOC容器, 解决对象创建及依赖关系 2&#xff09; Spring Web Spring对web模块的支持。 -->可以与struts整合,让struts的action创建交给spring -->spring mvc模式 3…

百度坐标转换API使用

http://api.map.baidu.com/geoconv/v1/?coords121.54759,29.870724&from1&to5&aksGSOaO07WkRHHiCRxxbSQVBn 前提&#xff1a;121.54759,29.870724 是由手机硬件或谷歌地图获取的 错误的方法一&#xff1a; function standard2china(lng,lat){//http://api.map.ba…

如何解决ajax跨域问题

原文&#xff1a;http://www.congmo.net/blog/2012/06/27/ajax-cross-domain/ 跨域问题 起 因是这样的&#xff0c;为了复用&#xff0c;减少重复开发&#xff0c;单独开发了一个用户权限管理系统&#xff0c;共其他系统获取认证与授权信息&#xff0c;暂且称之为A系统&#xf…

MySQL数据库是非关系_MySQL(数据库)基础知识、关系型数据库yu非关系型数据库、连接认证...

什么是数据库&#xff1f;数据库(Database)&#xff1a;存储数据的仓库高效地存储和处理数据的介质(介质主要是两种&#xff1a;磁盘和内存)数据库系统&#xff1a;DBS(Database System)&#xff1a;是一种虚拟系统&#xff0c;将多种内容关联起来的称呼DBS DBMS DBDBMS&…

Spring properties定义bean

2019独角兽企业重金招聘Python工程师标准>>> Spring提供了丰富的标签和注解来进行bean的定义&#xff0c;除此之外框架来提供了扩展机制让使用可以通过properties来定义bean&#xff0c;与强大的标签式和注解式的bean定义相比&#xff0c;properties提供的规则要简单…