社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ、人人网、开心网、新浪微博、搜狐微博、腾讯微博、淘宝、豆瓣、MSN、Google等社会化媒体账号登录该网站。

前言

在上一章Spring Security系列之Spring Social实现QQ社交登录(八)过程中,我们已经实现了使用Spring Social+Security的QQ社交登录。本章我们将实现微信的社交登录。(微信和QQ登录的大体流程相同,但存在一些细节上的差异,下面我们来简单实现一下)

准备工作

  1. 熟悉OAuth2.0协议标准,微信登录是基于OAuth2.0中的authorization_code模式的授权登录;
  2. 微信开放平台申请网站应用开发,获取appid和appsecret
  3. 熟读网站应用微信登录开发指南
  4. 参考Spring Security系列之Spring Social实现QQ社交登录(八)的准备工作

为了方便大家测试,博主在某宝租用了一个月的appid和appSecret

回调域名 www.pinzhi365.com

appid appsecret
wxd99431bbff8305a0 60f78681d063590a469f1b297feff3c4

目录结构

  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过滤器拦截的,如果 上一章 已经配置过,则本章不需要配置。

效果如下:

代码下载

从我的 github 中下载,github.com/longfeizhen…

Spring Security系列之Spring Social实现微信社交登录(九)相关推荐

  1. Spring Security源码分析四:Spring Social实现微信社交登录

    2019独角兽企业重金招聘Python工程师标准>>> 社交登录又称作社会化登录(Social Login),是指网站的用户可以使用腾讯QQ.人人网.开心网.新浪微博.搜狐微博.腾讯 ...

  2. Spring Security系列教程-Spring Security核心API讲解

    前言 经过前面几个章节的学习,一一哥 带大家实现了基于内存和数据库模型的认证与授权,尤其是基于自定义的数据库模型更是可以帮助我们进行灵活开发.但是前面章节的内容,属于让我们达到了 "会用&q ...

  3. spring security系列一:架构概述

    一直以来都想好好写一写spring security 系列文章,每每提笔又不知何处下笔,又赖于spring security体系强大又过于繁杂,且spring security 与auth2.0结合的 ...

  4. Spring Security系列教程03--创建SpringSecurity项目

    前言 在上一章节中,一一哥 已经带大家认识了Spring Security,对其基本概念已有所了解,但是作为一个合格的程序员,最关键的肯定还是得动起手来,所以从本篇文章开始,我就带大家搭建第一个Spr ...

  5. Spring Security系列(22)- Security实现手机短信登录功能

    准备 需求 采用手机号+短信验证码登录方式是很常见的一种需求. 那我们如何在Spring Security实现这种功能呢? 表单登录流程 首先再回顾一下用户名密码表单登录流程. 登录请求进入过滤器 调 ...

  6. Spring Security OAuth2 优雅的集成短信验证码登录以及第三方登录

    基于SpringCloud做微服务架构分布式系统时,OAuth2.0作为认证的业内标准,Spring Security OAuth2也提供了全套的解决方案来支持在Spring Cloud/Spring ...

  7. Spring Security 实战:Spring Boot 下的自动配置

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | 公众号「码农小胖哥」 1. 前言 我们在前几篇 ...

  8. Spring Security 玩出花!两种方式 DIY 登录

    Spring Security 玩出花!两种方式 DIY 登录 一般情况下,我们在使用 Spring Security 的时候,用的是 Spring Security 自带的登录方案,配置一下登录接口 ...

  9. 千锋教育威哥学Java——爆破专栏丨Spring Security系列教程之解决Spring Security环境中的跨域问题

    前言 上一章节中,一一哥 给各位讲解了同源策略和跨域问题,以及跨域问题的解决方案,在本篇文章中,我会带大家进行代码实现,看看在Spring Security环境中如何解决跨域问题. 需要更多教程,微信 ...

最新文章

  1. 小样本点云深度学习库_2019-01-07-小样本深度学习
  2. ELK之ElasticSearch快速入门
  3. php动态获取函数参数
  4. php点击按钮跳转页面heeader,php - 通过FPDF生成后插入水印并打印PDF - SO中文参考 - www.soinside.com...
  5. Python爬虫批量下载糗事百科段子,怀念的天王盖地虎,小鸡炖蘑菇...
  6. 使用Anaconda3安装tensorflow,opencv,使其可以在spyder中运行
  7. 隐藏或者修改nginx信息
  8. 7-1 是否同一棵二叉搜索树 (25 分)
  9. 微信小程序——云开发实现图片上传到云存储并实时预览当前上传的图片
  10. 关于SSL认证的小坑 SSLPeerUnverifiedException
  11. 深度学习与计算机视觉(12)_tensorflow实现基于深度学习的图像补全
  12. Primeng PrimeFlex 的使用总结 (Angular 10)
  13. 所谓的360,到底窥探了你多少隐私
  14. lowess和loess方法
  15. 可执行文件结构:PE文件结构讲解
  16. 仿flash滚动播放图片
  17. QT实现简单的塔防游戏
  18. 再谈angularJS数据绑定机制及背后原理—angularJS常见问题总结
  19. twitter无法获取推文_某些第三方Twitter客户端可能无法发送更长的推文
  20. node-bindings无法在Electron中使用的解决办法

热门文章

  1. 新建jsp报错“The superclass javax.servlet.http.HttpServlet was not found on the Java Build Path”...
  2. 转载---KMP算法(Matrix67原创)
  3. Java数组与容器类分析资料--数组、List和Set、Map-asp.net关注
  4. 大数据之-Hadoop3.x_HDFS_数据完整性_HDFS的CRC数据校验---大数据之hadoop3.x工作笔记0078
  5. Netty工作笔记0048---Http服务过滤资源
  6. SpringCloud工作笔记088---SpringBoot启动报错:启动后的端口是8080但是明明配置文件中是8000_Request execution error_ java.net.Conn
  7. IntelliJ Idea学习笔记007---IntelliJ Idea2018 1.6破解
  8. ASP.Net学习笔记012--12ViewState初探
  9. mysql数据库BigInt数据类型和实体对象中BigInteger,Long类型的冲突
  10. 深度学习实践与部署(开篇)