首页 > 编程语言 >Spring Security 源码分析(四):Spring Social实现微信社交登录

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

时间:2023-01-02 21:05:55浏览次数:44  
标签:return String Spring token 源码 connect 微信 Override


前言

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

准备工作

熟悉OAuth2.0协议标准,微信登录是基于OAuth2.0中的authorization_code模式的授权登录;

微信开放平台申请网站应用开发,获取 ​​appid​​​和 ​​appsecret​

熟读网站应用微信登录开发指南

参考Spring-Security源码分析三-Spring-Social社交登录过程的准备工作

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

appid

​wxfd6965ab1fc6adb2​

appsecret

​66bb4566de776ac699ec1dbed0cc3dd1​

目录结构

Spring Security 源码分析(四):Spring Social实现微信社交登录_微信

参考

​api​​ 定义api绑定的公共接口

​config​​ 微信的一些配置信息

​connect​​与服务提供商建立连接所需的一些类。

定义返回用户信息接口

​​publicinterfaceWeixin{​​
​​WeixinUserInfo(String);​​
​​}​​

这里我们看到相对于QQ的 ​​getUserInfo​​​微信多了一个参数 ​​openId​​​。这是因为微信文档中在OAuth2.0的认证流程示意图第五步时,微信的 ​​openid​​​ 同 ​​access_token​​​一起返回。而 ​​SpringSocial​​​获取 ​​access_token​​​的类 ​​AccessGrant.java​​​中没有 ​​openid​​​。因此我们自己需要扩展一下 ​​SpringSocial​​​获取令牌的类( ​​AccessGrant.java​​);

处理微信返回的access_token类(添加openid)

​​@Data​​
​​publicclassWeixinAccessGrantextendsAccessGrant{​​

​​privateString;​​

​​publicWeixinAccessGrant(){​​
​​super("");​​
​​}​​

​​publicWeixinAccessGrant(String,String,String,Long){​​
​​super(accessToken,,,);​​
​​}​​
​​}​​

实现返回用户信息接口

​​publicclassWeiXinImplextendsAbstractOAuth2ApiBindingimplementsWeixin{​​

​​/**​​
​​ * 获取用户信息的url​​
​​ */​​
​​privatestaticfinalString="https://api.weixin.qq.com/sns/userinfo?openid=";​​

​​privateObjectMapper=newObjectMapper();​​

​​publicWeiXinImpl(String){​​
​​super(accessToken,TokenStrategy.ACCESS_TOKEN_PARAMETER);​​
​​}​​

​​/**​​
​​ * 获取用户信息​​
​​ *​​
​​ * @param openId​​
​​ * @return​​
​​ */​​
​​@Override​​
​​publicWeixinUserInfo(String){​​
​​String=+;​​

​​String=().getForObject(url,String.class);​​
​​if(StringUtils.contains(result,"errcode")){​​
​​returnnull;​​
​​}​​

​​WeixinUserInfo=null;​​

​​try{​​
​​=.readValue(result,WeixinUserInfo.class);​​
​​}catch(Exception){​​
​​.printStackTrace();​​
​​}​​

​​return;​​
​​}​​

​​/**​​
​​ * 使用utf-8 替换默认的ISO-8859-1编码​​
​​ * @return​​
​​ */​​
​​@Override​​
​​protectedList<HttpMessageConverter<?>>(){​​
​​List<HttpMessageConverter<?>>=super.getMessageConverters();​​
​​.remove(0);​​
​​.add(newStringHttpMessageConverter(Charset.forName("UTF-8")));​​
​​return;​​
​​}​​
​​}​​

与 ​​QQ​​​获取用户信息相比, ​​微信​​​的实现类中少了一步通过 ​​access_token​​​获取 ​​openid​​​的请求。 ​​openid​​​由自己定义的扩展类 ​​WeixinAccessGrant​​中获取;

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

​​@Slf4j​​
​​publicclassWeixinOAuth2TemplateextendsOAuth2Template{​​

​​privateString;​​

​​privateString;​​

​​privateString;​​

​​privatestaticfinalString="https://api.weixin.qq.com/sns/oauth2/refresh_token";​​

​​publicWeixinOAuth2Template(String,String,String,String){​​
​​super(clientId,,,);​​
​​(true);​​
​​this.clientId =;​​
​​this.clientSecret =;​​
​​this.accessTokenUrl =;​​
​​}​​

​​/* (non-Javadoc)​​
​​ * @see org.springframework.social.oauth2.OAuth2Template#exchangeForAccess(java.lang.String, java.lang.String, org.springframework.util.MultiValueMap)​​
​​ */​​
​​@Override​​
​​publicAccessGrant(String,String,​​
​​MultiValueMap<String,String>){​​

​​StringBuilder=newStringBuilder(accessTokenUrl);​​

​​.append("?appid="+clientId);​​
​​.append("&secret="+clientSecret);​​
​​.append("&code="+authorizationCode);​​
​​.append("&grant_type=authorization_code");​​
​​.append("&redirect_uri="+redirectUri);​​

​​return(accessTokenRequestUrl);​​
​​}​​

​​publicAccessGrant(String,MultiValueMap<String,String>){​​

​​StringBuilder=newStringBuilder(REFRESH_TOKEN_URL);​​

​​.append("?appid="+clientId);​​
​​.append("&grant_type=refresh_token");​​
​​.append("&refresh_token="+refreshToken);​​

​​return(refreshTokenUrl);​​
​​}​​

​​@SuppressWarnings("unchecked")​​
​​privateAccessGrant(StringBuilder){​​

​​.info("获取access_token, 请求URL: "+accessTokenRequestUrl.toString());​​

​​String=().getForObject(accessTokenRequestUrl.toString(),String.class);​​

​​.info("获取access_token, 响应内容: "+response);​​

​​Map<String,Object>=null;​​
​​try{​​
​​=newObjectMapper().readValue(response,Map.class);​​
​​}catch(Exception){​​
​​.printStackTrace();​​
​​}​​

​​//返回错误码时直接返回空​​
​​if(StringUtils.isNotBlank(MapUtils.getString(result,"errcode"))){​​
​​String=MapUtils.getString(result,"errcode");​​
​​String=MapUtils.getString(result,"errmsg");​​
​​thrownewRuntimeException("获取access token失败, errcode:"+errcode+", errmsg:"+errmsg);​​
​​}​​

​​WeixinAccessGrant=newWeixinAccessGrant(​​
​​MapUtils.getString(result,"access_token"),​​
​​MapUtils.getString(result,"scope"),​​
​​MapUtils.getString(result,"refresh_token"),​​
​​MapUtils.getLong(result,"expires_in"));​​

​​.setOpenId(MapUtils.getString(result,"openid"));​​

​​return;​​
​​}​​

​​/**​​
​​ * 构建获取授权码的请求。也就是引导用户跳转到微信的地址。​​
​​ */​​
​​publicString(OAuth2Parameters){​​
​​String=super.buildAuthenticateUrl(parameters);​​
​​=+"&appid="+clientId+"&scope=snsapi_login";​​
​​return;​​
​​}​​

​​publicString(OAuth2Parameters){​​
​​return(parameters);​​
​​}​​

​​/**​​
​​ * 微信返回的contentType是html/text,添加相应的HttpMessageConverter来处理。​​
​​ */​​
​​protectedRestTemplate(){​​
​​RestTemplate=super.createRestTemplate();​​
​​.getMessageConverters().add(newStringHttpMessageConverter(Charset.forName("UTF-8")));​​
​​return;​​
​​}​​
​​}​​

与 ​​QQ​​​处理令牌类相比多了三个全局变量并且复写了 ​​exchangeForAccess​​​方法。这是因为 ​​微信​​​在通过 ​​code​​​获取 ​​access_token​​​是传递的参数是 ​​appid​​​和 ​​secret​​​而不是标准的 ​​client_id​​​和 ​​client_secret​​。

WeixinServiceProvider连接服务提供商

​​publicclassWeixinServiceProviderextendsAbstractOAuth2ServiceProvider<Weixin>{​​

​​/**​​
​​ * 微信获取授权码的url​​
​​ */​​
​​privatestaticfinalString="https://open.weixin.qq.com/connect/qrconnect";​​
​​/**​​
​​ * 微信获取accessToken的url(微信在获取accessToken时也已经返回openId)​​
​​ */​​
​​privatestaticfinalString="https://api.weixin.qq.com/sns/oauth2/access_token";​​

​​publicWeixinServiceProvider(String,String){​​
​​super(newWeixinOAuth2Template(appId,,,));​​
​​}​​

​​@Override​​
​​publicWeixin(String){​​
​​returnnewWeiXinImpl(accessToken);​​
​​}​​
​​}​​

WeixinConnectionFactory连接服务提供商的工厂类

​​publicclassWeixinConnectionFactoryextendsOAuth2ConnectionFactory<Weixin>{​​

​​/**​​
​​ * @param appId​​
​​ * @param appSecret​​
​​ */​​
​​publicWeixinConnectionFactory(String,String,String){​​
​​super(providerId,newWeixinServiceProvider(appId,),newWeixinAdapter());​​
​​}​​

​​/**​​
​​ * 由于微信的openId是和accessToken一起返回的,所以在这里直接根据accessToken设置providerUserId即可,不用像QQ那样通过QQAdapter来获取​​
​​ */​​
​​@Override​​
​​protectedString(AccessGrant){​​
​​if(accessGrant instanceofWeixinAccessGrant){​​
​​return((WeixinAccessGrant)accessGrant).getOpenId();​​
​​}​​
​​returnnull;​​
​​}​​

​​/* (non-Javadoc)​​
​​ * @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.oauth2.AccessGrant)​​
​​ */​​
​​publicConnection<Weixin>(AccessGrant){​​
​​returnnewOAuth2Connection<Weixin>(getProviderId(),(accessGrant),.getAccessToken(),​​
​​.getRefreshToken(),.getExpireTime(),(),(extractProviderUserId(accessGrant)));​​
​​}​​

​​/* (non-Javadoc)​​
​​ * @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.connect.ConnectionData)​​
​​ */​​
​​publicConnection<Weixin>(ConnectionData){​​
​​returnnewOAuth2Connection<Weixin>(data,(),(data.getProviderUserId()));​​
​​}​​

​​privateApiAdapter<Weixin>(String){​​
​​returnnewWeixinAdapter(providerUserId);​​
​​}​​

​​privateOAuth2ServiceProvider<Weixin>(){​​
​​return(OAuth2ServiceProvider<Weixin>)();​​
​​}​​

​​}​​

WeixinAdapter将微信api返回的数据模型适配Spring Social的标准模型

​​publicclassWeixinAdapterimplementsApiAdapter<Weixin>{​​

​​privateString;​​

​​publicWeixinAdapter(){​​
​​}​​

​​publicWeixinAdapter(String){​​
​​this.openId =;​​
​​}​​

​​@Override​​
​​publicboolean(Weixin){​​
​​returntrue;​​
​​}​​

​​@Override​​
​​publicvoid(Weixin,ConnectionValues){​​
​​WeixinUserInfo=.getUserInfo(openId);​​
​​.setProviderUserId(userInfo.getOpenid());​​
​​.setDisplayName(userInfo.getNickname());​​
​​.setImageUrl(userInfo.getHeadimgurl());​​
​​}​​

​​@Override​​
​​publicUserProfile(Weixin){​​
​​returnnull;​​
​​}​​

​​@Override​​
​​publicvoid(Weixin,String){​​

​​}​​
​​}​​

WeixinAuthConfig创建工厂和设置数据源

​​@Configuration​​
​​publicclassWeixinAuthConfigextendsSocialAutoConfigurerAdapter{​​

​​@Autowired​​
​​privateDataSource;​​

​​@Autowired​​
​​privateConnectionSignUp;​​

​​@Override​​
​​protectedConnectionFactory<?>(){​​
​​returnnewWeixinConnectionFactory(DEFAULT_SOCIAL_WEIXIN_PROVIDER_ID,SecurityConstants.DEFAULT_SOCIAL_WEIXIN_APP_ID,​​
​​SecurityConstants.DEFAULT_SOCIAL_WEIXIN_APP_SECRET);​​
​​}​​

​​@Override​​
​​publicUsersConnectionRepository(ConnectionFactoryLocator){​​
​​JdbcUsersConnectionRepository=newJdbcUsersConnectionRepository(dataSource,​​
​​,Encryptors.noOpText());​​
​​if(myConnectionSignUp !=null){​​
​​.setConnectionSignUp(myConnectionSignUp);​​
​​}​​
​​return;​​
​​}​​

​​/**​​
​​ * /connect/weixin POST请求,绑定微信返回connect/weixinConnected视图​​
​​ * /connect/weixin DELETE请求,解绑返回connect/weixinConnect视图​​
​​ * @return​​
​​ */​​
​​@Bean({"connect/weixinConnect","connect/weixinConnected"})​​
​​@ConditionalOnMissingBean(name ="weixinConnectedView")​​
​​publicView(){​​
​​returnnewSocialConnectView();​​
​​}​​

​​}​​

社交登录配置类

由于社交登录都是通过 ​​SocialAuthenticationFilter​​过滤器拦截的,如果 上一章 已经配置过,则本章不需要配置。

效果如下:

Spring Security 源码分析(四):Spring Social实现微信社交登录_ci_02

代码下载

从我的 github 中下载,https://github.com/longfeizheng/logback

 



标签:return,String,Spring,token,源码,connect,微信,Override
From: https://blog.51cto.com/u_15147537/5984066

相关文章

  • Springboot 整合 Dubbo/ZooKeeper 详解 SOA 案例
    一、为啥整合Dubbo实现SOADubbo不单单只是高性能的RPC调用框架,更是SOA服务治理的一种方案。核心:1.远程通信,向本地调用一样调用远程方法。2.集群容错3.服务自动发......
  • Spring 事务源码(三):事务相关对象的创建
    事务源码(二)中,已经分析了beanDefinition的加载,下面来创建对应beanDefinition的bean。1、PropertySourcesPlaceholderConfigurer创建占位符处理的beanPropertyS......
  • Spring注解综合应用
    需求通过注解方式,实现下面xml的配置(实现“控制层(controller)--业务逻辑层(service)--dao层--数据源”的关系)<?xmlversion="1.0"encoding="UTF-8"?><beansxmlns="ht......
  • Spring AOP源码(四):创建被代理类的代理对象
    在AOP源码(三):创建AOP相关的Bean中,介绍了Spring创建AOP的Advisor、AnnotationAwareAspectJAutoProxyCreator的创建,其中被代理类的代理对象是如何创建的未做说明,下面来......
  • spring cache
    SpringCache是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。SpringCache提供了一层抽象,底层可以切换不同的cache实现。具体就是通过Cache......
  • Spring注解:使用注解的方式完成IOC
    补充:xml配置最开始(Spring1.x),Spring都是通过xml配置控制层(controller)--业务逻辑层(service)--dao层--数据源的关系,但是比较复杂Spring2.x的时候,随着JDK1.5支持注解的方式,......
  • SpringCloud+Bus动态刷新
    1.设计思想:利用消息机制来进行来进行动态刷新(1)利用消息总线触发一公客户端/bus/refresh,而刷新所有客户端的配置(2)利用消息总线触发一个服务端ConfigServer的/bus/refresh端......
  • 人体姿态估计(人体关键点检测)2D Pose训练代码和Android源码
    人体姿态估计(人体关键点检测)2DPose训练代码和Android源码目录​​人体姿态估计(人体关键点检测)2DPose训练代码和Android源码​​​​1.人体姿态估计2DPose方法​​​......
  • 《iOS开发指南》第二版 iOS7版-源码-样章-目录,感谢大家一直以来的支持
    目录:​基础篇​1开篇综述​1.1iOS概述21.1.1iOS介绍21.1.2iOS7新特性21.2开发环境及开发工具31.3本书中的约定41.3.1案例代码约定51.3.2......
  • 《iOS开发指南》正式出版-源码-样章-目录,欢迎大家提出宝贵意见
    目录大纲基础篇1 开篇综述1.1iOS概述1.1.1iOS是什么?1.1.2iOS6新特性1.2开发环境及开发工具1.3本书中的约定1.3.1案例代码约定1.3.2图示的约定2 第一个iOS应用程序......