0
点赞
收藏
分享

微信扫一扫

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

DT_M 2023-01-02 阅读 126


前言

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

效果如下:

代码下载

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

 



举报

相关推荐

0 条评论