文章目录
项目源码地址 https://github.com/nieandsun/security
1 开发流程简介
摘自上篇文章:spring-security进阶2—第三方登陆之QQ登陆1【获取QQ用户信息】。
spring-security进阶1—第三方登陆原理2【springsocial】那篇文章里介绍了springsocial开发第三方登陆的原理,在文中介绍到要想获取服务提供商(QQ等)的用户信息.
- 首先需要Connection类,它的作用是封装从提供商获取到的用户信息
- 获取Connection类,又需要ConnectionFactory类,它的作用就是创建Connection对象
- 获取ConnectionFactory,又需要ServiceProvider对象和ApiAdapter对象(ApiAdapter的作用为将获取的用户信息转换成本系统对应的信息)
- 获取ServiceProvider对象又需要 Oauth2Operation对象(封装了Oauth2协议的整个流程)和Api对象(真正用来获取服务提供商(QQ等)的用户信息)
也就是说,如果获得一个Connection对象,需要先有下面的这一系列的类.
上篇文章已经介绍了Api对象的开发+利用Oauth2Operation对象和Api对象封装ServiceProvider对象,本篇将在此基础上继续开发。
2 ApiAdapter对象的开发
package com.nrsc.security.core.social.qq.connect;
import com.nrsc.security.core.social.qq.api.QQ;
import com.nrsc.security.core.social.qq.api.QQUserInfo;
import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.ConnectionValues;
import org.springframework.social.connect.UserProfile;
/**
* @author : Sun Chuan
* @date : 2019/8/7 20:21
* Description:
*/
public class QQAdapter implements ApiAdapter<QQ> {
/**
* 用来测试当前服务是否是通的,这里假设服务一直是通的
*
* @param api
* @return
*/
@Override
public boolean test(QQ api) {
return true;
}
/**
*
* 将我们从QQ获取的用户信息设置成Connection对象相对应的字段信息
* 记住:connection对象的字段是固定的
* @param api
* @param values
*/
@Override
public void setConnectionValues(QQ api, ConnectionValues values) {
QQUserInfo userInfo = api.getUserInfo();
values.setDisplayName(userInfo.getNickname());
values.setImageUrl(userInfo.getFigureurl_qq_1());
values.setProfileUrl(null);//个人主页url,QQ没有,这里设null
values.setProviderUserId(userInfo.getOpenId());
}
/**
* 同上面的方法一样也是通过api拿到一个标准的用户信息---》之后会讲
* @param api
* @return
*/
@Override
public UserProfile fetchUserProfile(QQ api) {
return null;
}
/***
* 微博等更新主页信息---这里不用管
* @param api
* @param message
*/
@Override
public void updateStatus(QQ api, String message) {
//do nothing
}
}
3 利用ApiAdapter对象+ServiceProvider对象构建ConnectionFactory对象
3.1 构建ConnectionFactory对象–继承OAuth2ConnectionFactory的方式
package com.nrsc.security.core.social.qq.connect;
import com.nrsc.security.core.social.qq.api.QQ;
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
/**
* @author : Sun Chuan
* @date : 2019/8/7 20:29
* Description: 组装ConnectionFactory对象---》ConnectionFactory对象由ServiceProvider和Adapter对象组成
*/
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> {
/**
* 除了ServiceProvider和Adapter对象还需要一个providerId---》提供商的唯一标识
*
* @param providerId
* @param appId
* @param appSecret
*/
public QQConnectionFactory(String providerId, String appId, String appSecret) {
super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
}
}
3.2 将 ConnectionFactory对象交给spring容器
特别注意
: 我最近写的一系列关于spring-security、springsocial、oauth2的文章都算是学习慕课网《Spring Security技术栈开发企业级认证与授权》这个课程的笔记,但是我使用的springboot是2.1.5.RELEASE版本,而老师课程中使用的是1.5.6.RELEASE版本。在标题对应的这个问题上,两个版本之间存在一些比较大的差异。
差异原因
: 2.1.5.RELEASE版本的springboot相比于1.5.6.RELEASE版本来说org.springframework.boot.autoconfigure
里没有social相关的配置简化类。
3.2.1 springboot为1.5.6.RELEASE的版本
- 首先来看一下老师在课程中是如何做的
package com.imooc.security.core.social.qq.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter;
import org.springframework.context.annotation.Configuration;
import org.springframework.social.connect.ConnectionFactory;
import com.imooc.security.core.properties.QQProperties;
import com.imooc.security.core.properties.SecurityProperties;
import com.imooc.security.core.social.qq.connet.QQConnectionFactory;
@Configuration
@ConditionalOnProperty(prefix = "imooc.security.social.qq", name = "app-id")
public class QQAutoConfig extends SocialAutoConfigurerAdapter {
/**这里不多解释,就是将providerId,appId和appSecret写入到配置类,
然后这里注入配置类,就可以读到这些信息
可以下载源码进行查看*/
@Autowired
private SecurityProperties securityProperties;
@Override
protected ConnectionFactory<?> createConnectionFactory() {
QQProperties qqConfig = securityProperties.getSocial().getQq();
return new QQConnectionFactory(qqConfig.getProviderId(), qqConfig.getAppId(), qqConfig.getAppSecret());
}
}
注意看import
: 上面的SocialAutoConfigurerAdapter 来自于
org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter;
- 其实老师在配置providerId,appId和appSecret属性时也用到了org.springframework.boot.autoconfigure.social里的一个类,代码如下,(完整代码可以从老师的github地址下载:https://github.com/jojozhai/security)
package com.imooc.security.core.properties;
import org.springframework.boot.autoconfigure.social.SocialProperties;
public class QQProperties extends SocialProperties {
private String providerId = "qq";
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
}
注意看import
: 上面的SocialProperties来自于
org.springframework.boot.autoconfigure.social.SocialProperties;
3.2.2 springboot1.5.6和2.1.5中autoconfigure包对比及2.1.5中的解决方法
将两个版本的jar包对比如下:
仔细看一下源码,可以发现SocialAutoConfigurerAdapter和SocialProperties特别简单。
其实看到social这个包下面的类,我们解决这个问题的方式就多了,比如说
- 参照Facebook、LinkedIn或Twitter的栗子再联系一下各个类之间的依赖关系去自己写配置类
或者
- 直接将SocialAutoConfigurerAdapter、SocialProperties和SocialWebAutoConfiguration这三个类拷贝到我们项目里 —> 我采用的这种方式(因为比较简单☺),可以去github查看代码commit记录https://github.com/nieandsun/security
注意1
: 其实我一开始没有拷贝SocialWebAutoConfiguration这个类,因为觉得好像没用,但是后来启动项目时报了如下错误,大体意思就是必须有一个配置类实现getUserIdSource方法
,后来发现SocialWebAutoConfiguration类中正好实现了该方法,所以也将其拷贝了过来。
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-08-17 02:23:28.590 ERROR 19412 --- [ restartedMain] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userIdSource' defined in class path resource [org/springframework/social/config/annotation/SocialConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.social.UserIdSource]: Factory method 'userIdSource' threw exception; nested exception is java.lang.IllegalArgumentException: One configuration class must implement getUserIdSource from SocialConfigurer.
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:627) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:456) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1321) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1160) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:843) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:877) ~[spring-context-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:142) ~[spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) [spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) [spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.5.RELEASE.jar:2.1.5.RELEASE]
at com.nrsc.security.SecurityApplication.main(SecurityApplication.java:10) [classes/:na]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_191]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_191]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191]
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.1.5.RELEASE.jar:2.1.5.RELEASE]
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.social.UserIdSource]: Factory method 'userIdSource' threw exception; nested exception is java.lang.IllegalArgumentException: One configuration class must implement getUserIdSource from SocialConfigurer.
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:622) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
... 24 common frames omitted
Caused by: java.lang.IllegalArgumentException: One configuration class must implement getUserIdSource from SocialConfigurer.
at org.springframework.util.Assert.notNull(Assert.java:198) ~[spring-core-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.social.config.annotation.SocialConfiguration.userIdSource(SocialConfiguration.java:80) ~[spring-social-config-1.1.6.RELEASE.jar:1.1.6.RELEASE]
at org.springframework.social.config.annotation.SocialConfiguration$$EnhancerBySpringCGLIB$$26eb830.CGLIB$userIdSource$4(<generated>) ~[spring-social-config-1.1.6.RELEASE.jar:1.1.6.RELEASE]
at org.springframework.social.config.annotation.SocialConfiguration$$EnhancerBySpringCGLIB$$26eb830$$FastClassBySpringCGLIB$$2cea3657.invoke(<generated>) ~[spring-social-config-1.1.6.RELEASE.jar:1.1.6.RELEASE]
at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.1.7.RELEASE.jar:5.1.7.RELEASE]
at org.springframework.social.config.annotation.SocialConfiguration$$EnhancerBySpringCGLIB$$26eb830.userIdSource(<generated>) ~[spring-social-config-1.1.6.RELEASE.jar:1.1.6.RELEASE]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_191]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_191]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_191]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_191]
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.7.RELEASE.jar:5.1.7.RELEASE]
... 25 common frames omitted
注意2
SocialWebAutoConfiguration类中有个方法如下,飘红,说明有问题,看该方法的意思是如果有SpringResourceResourceResolver
类型的类时,该方法才会生效。源码里都没该类,所以肯定可以将该方法注释掉。
4 Connection对象
Connection对象的获取,不用我们去管,只要我们弄好ConnectionFactory对象,springsocial会自动根据ConnectionFactory对象生成相对应的Connection对象。
5 结束语
不知不觉已经夜里三点了,时间过的真快!!!
本篇代码的改动已经提交到github:https://github.com/nieandsun/security