springsocial/oauth2---第三方登陆之QQ登陆2【springboot2.X没有SocialAutoConfigurerAdapter、SocialProperties的问题】


项目源码地址 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

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值