摘自上篇文章:spring-security进阶2—第三方登陆之QQ登陆1【获取QQ用户信息】。
spring-security进阶1—第三方登陆原理2【springsocial】那篇文章里介绍了springsocial开发第三方登陆的原理,在文中介绍到要想获取服务提供商(QQ等)的用户信息.
也就是说,如果获得一个Connection对象,需要先有下面的这一系列的类.
上篇文章已经介绍了Api对象的开发+利用Oauth2Operation对象和Api对象封装ServiceProvider对象,本篇将在此基础上继续开发。
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
}
}
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());
}
}
特别注意
: 我最近写的一系列关于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相关的配置简化类。
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;
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;
将两个版本的jar包对比如下:
仔细看一下源码,可以发现SocialAutoConfigurerAdapter和SocialProperties特别简单。
其实看到social这个包下面的类,我们解决这个问题的方式就多了,比如说
或者
注意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() ~[spring-social-config-1.1.6.RELEASE.jar:1.1.6.RELEASE]
at org.springframework.social.config.annotation.SocialConfiguration$$EnhancerBySpringCGLIB$$26eb830$$FastClassBySpringCGLIB$$2cea3657.invoke() ~[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() ~[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
类型的类时,该方法才会生效。源码里都没该类,所以肯定可以将该方法注释掉。
Connection对象的获取,不用我们去管,只要我们弄好ConnectionFactory对象,springsocial会自动根据ConnectionFactory对象生成相对应的Connection对象。
不知不觉已经夜里三点了,时间过的真快!!!
本篇代码的改动已经提交到github:https://github.com/nieandsun/security