图一

基于SpringSocial实现qq登录,要走一个OAuth流程,拿到服务提供商qq返回的用户信息。

由上篇介绍的可知,用户信息被封装在了Connection里,所以最终要拿到Connection

1,Connection <---- ConnectionFactory:拿到一个Connection,就需要一个ConnectionFactory工厂

2,ConnectionFactory:需要ServiceProvider 服务提供商、ApiAdapter api适配器,在服务提供商和业务系统user之间转换

3,ServiceProvider :需要 OAuthe2Operations、Api 读取用户信息

4,在数据库中建数据库表

上图中括号里都是Spring Social对对应接口的默认实现,代码中使用它们。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

开始开发:

1,构建ServiceProvider

  1.1 构建ServiceProvider需要的Api(api 用来获取用户信息,就是图一的第6步,自定义QQ接口,继承 Spring Social的默认实现 AbstractOAuth2ApiBinding )

    

代码:

接口

public interface QQ {

    /**
* 获取qq用户信息*/
QQUserInfo getUserInfo() throws Exception;
}

实现类:

package com.imooc.security.core.social.qq.api;/**
* 流程中的Api。
* ClassName: QQImpl
* @Description:
* ***********注意*************
* 这个是多例的,每个用户不一样进来他们的accessToken、openid是不一样的
* 所以不能@Component声明为spring组件!!!
* ***********注意*************
* @author lihaoyang
* @date 2018年3月6日
*/
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ{ /**
* 调用qq的get_user_info
* 1:3个参数,OAuth2.0协议的通用三个参数:
* access_token: 父类已提供
* appid://申请QQ登录成功后,分配给应用的appid
* openid://用户的ID,与QQ号码一一对应。
*
* 2:2个路径
* 获取openid的路径:
* https://graph.qq.com/oauth2.0/me?access_token=YOUR_ACCESS_TOKEN
* 获取用户信息的路径:
* https://graph.qq.com/user/get_user_info?access_token=*************&oauth_consumer_key=12345&openid=
*/ private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s"; private static final String URL_GET_USRE_INFO = "https://graph.qq.com/user/get_user_info?access_token=%s&oauth_consumer_key=12345&openid=%s"; private String appId; private String openId; private ObjectMapper objectMapper = new ObjectMapper(); /**
* 实例化时获取openid
* <p>Description: </p>
* @param accessToken
* @param appId
*/
public QQImpl(String accessToken , String appId){
//父类默认构造会把accessToken放在请求头里,这是不符合qq要求的放在url参数里的,所以掉一下作为参数的构造
super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER); this.appId = appId; String url = String.format(URL_GET_USRE_INFO, accessToken);
String result = getRestTemplate().getForObject(url, String.class);//调用父类的restTemplate发请求,获取openid System.err.println(result);
//{"client_id":"YOUR_APPID","openid":"YOUR_OPENID"}
//截取openid
this.openId = StringUtils.substringBetween(result, "\"openid\"", "}");
} @Override
public QQUserInfo getUserInfo() throws Exception {
//accessToken已在父类挂在了参数
String url = String.format(URL_GET_USRE_INFO, appId,openId);
String result = getRestTemplate().getForObject(url, String.class); System.err.println(result);
QQUserInfo userInfo = objectMapper.readValue(result, QQUserInfo.class); return userInfo;
} }

  AbstractOAuth2ApiBinding:

  

********* 登录QQ互联官网get_user_info接口http://wiki.connect.qq.com/get_user_info *************

需要的3个参数:

QQUserInfo照着qq要求的构造:

1.2,有了Api,就可以构建ServiceProvider,因为ServiceProvider需要的第三个参数OAuthOperations可以使用Spring Social提供的OAuth2Template

代码:

package com.imooc.security.core.social.qq.connect;

import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
import org.springframework.social.oauth2.OAuth2Template; import com.imooc.security.core.social.qq.api.QQ;
import com.imooc.security.core.social.qq.api.QQImpl; /**
* QQ服务提供商
* ClassName: QQServiceProvider
* @Description:
* 需要继承spring social的默认实现AbstractOAuth2ServiceProvider
* 泛型是指获取用户信息的Api,类型就是QQ
* @author lihaoyang
* @date 2018年3月6日
*/
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ>{ private String appId; //将用户引导到获取授权码的地址
private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
//拿着授权码申请令牌token的地址
private static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token"; /**
* 返回ServiceProvider需要的OAuthOperations
* <p>Description: </p>
* @param appId 不同的应用是不一样的
* @param appSecret 不同的应用是不一样的
*/
public QQServiceProvider(String appId , String appSecret) {
super(new OAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN)); } /**
* 返回ServiceProvider需要的Api
*/
@Override
public QQ getApi(String accessToken) { return new QQImpl(accessToken, appId);
} }

 到此为止,Service已构建好

2,构建ConnectionFactory工厂,产生Connection

新建QQAdapter,实现ApiAdapter接口:

package com.imooc.security.core.social.qq.connect;

import org.springframework.social.connect.ApiAdapter;
import org.springframework.social.connect.ConnectionValues;
import org.springframework.social.connect.UserProfile; import com.imooc.security.core.social.qq.api.QQ;
import com.imooc.security.core.social.qq.api.QQUserInfo; /**
* 在服务提供商qq和第三方应用之间做用户信息的转换
* ClassName: QQAdapter
* @Description:
* 在服务提供商qq和第三方应用之间做用户信息的转换
* 实现 ApiAdapter接口,泛型是API接口,对应我们的QQ接口
* @author lihaoyang
* @date 2018年3月6日
*/
public class QQAdapter implements ApiAdapter<QQ>{ /**
* 测试当前api是否可用,测试qq是否可用
*/
@Override
public boolean test(QQ api) {
//就不掉了,直接true
return true;
} /**
* Connection和api之间的适配
* ConnectionValues:
* 创建Connection需要的数据项
* 从api中获取数据,给ConnectionValues设置值
*/
@Override
public void setConnectionValues(QQ api, ConnectionValues values) { //获取用户信息
QQUserInfo userInfo = api.getUserInfo();
values.setDisplayName(userInfo.getNickname());//展示名,qq用户名
values.setImageUrl(userInfo.getFigureurl_1()); //qq头像
values.setProfileUrl(null); //个人主页,qq没有,微博有
values.setProviderUserId(userInfo.getOpenId());
} /**
*
*/
@Override
public UserProfile fetchUserProfile(QQ api) {
// TODO Auto-generated method stub
return null;
} /**
* 某些社交如微博才有
*/
@Override
public void updateStatus(QQ api, String message) {
// do nothing
} }

构建ConnectionFactory:QQConnectionFactory类实现ConnectionFactory接口,把构造器需要的 ServiceProvider (QQServiceProvider)     ApiAdapter   (QQAdapter)  给他

package com.imooc.security.core.social.qq.connect;

import org.springframework.social.connect.support.OAuth2ConnectionFactory;
import com.imooc.security.core.social.qq.api.QQ; /**
* 创建Connection工厂
* ClassName: QQConnectionFactory
* @Description:
* 创建Connection工厂
* 继承默认实现 OAuth2ConnectionFactory
* 泛型:Api是什么,就是我们的QQ
* @author lihaoyang
* @date 2018年3月7日
*/
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> { /**
* * 需要两个对象:
* 1,ServiceProvider --> QQServieProvider
* 2,ApiAdapter --> QQApiAdapter
* <p>Description: </p>
* @param providerId
* @param appId
* @param appSecret
*/
public QQConnectionFactory(String providerId, String appId , String appSecret) {
super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter()); } }

有了ConnectionFactory,就可以自己产生Connection了,我们不用管产生Connection的过程。

第四步:配置JdbcUsersConnectionRepository

有了Connection,还需要把Connection中的数据保存到数据库中,所以还需要JdbcUsersConnectionRepository。Spring已经写好了,只需要配置一下即可。

新建SocialConfig配置类,继承SocialConfigurerAdapter,注入数据源

package com.imooc.security.core.social;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository; /**
* SpringSocial相关配置
* ClassName: SocialConfig
* @Description: 社交相关配置
* @author lihaoyang
* @date 2018年3月7日
*/
@Configuration
@EnableSocial //把Spring Social相关的特性启动
public class SocialConfig extends SocialConfigurerAdapter{ @Autowired
private DataSource dataSource; /**
* 配置JdbcUsersConnectionRepository
*/
@Override
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) { /**
* 参数:
* 1, dataSource:数据源 注进来
* 2,connectionFactoryLocator:
* 根据条件去查找需要的 ConnectionFactory,因为系统里可能有多个ConnectionFactory,如qq,微信等,使用默认穿的
* 3,textEncryptor:把插入到数据库的数据加密解密
*/
return new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());//先不加密
}
}

新建存Connection数据的表,该sql在JdbcUsersConnectionRepository所在的包里:org.springframework.social.connect.jdbc

sql:

-- This SQL contains a "create table" that can be used to create a table that JdbcUsersConnectionRepository can persist
-- connection in. It is, however, not to be assumed to be production-ready, all-purpose SQL. It is merely representative
-- of the kind of table that JdbcUsersConnectionRepository works with. The table and column names, as well as the general
-- column types, are what is important. Specific column types and sizes that work may vary across database vendors and
-- the required sizes may vary across API providers. create table UserConnection (userId varchar(255) not null,
providerId varchar(255) not null,
providerUserId varchar(255),
rank int not null,
displayName varchar(255),
profileUrl varchar(512),
imageUrl varchar(512),
accessToken varchar(512) not null,
secret varchar(512),
refreshToken varchar(512),
expireTime bigint,
primary key (userId, providerId, providerUserId));
create unique index UserConnectionRank on UserConnection(userId, providerId, rank);

字段说明:

userId        :业务系统用户id
providerId :服务提供商id,是qq、微信、微博...
providerUserId :openId
rank       :等级
displayName :昵称
profileUrl :主页
imageUrl :头像
accessToken :
secret :
refreshToken :
expireTime :

在数据库执行一下,这个表名不能变,但是可以根据公司需要加前缀,如加上imooc_

若加上了前缀,,则配置 JdbcUsersConnectionRepository 时需要指定一下前缀:

JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
repository.setTablePrefix("imooc_");

用户表单登录时,我们建了MyUserDetailsService 用 实现了SpringSecurity的UserDetailsService接口通过用户名来获取用户信息;社交登录,spring提供了SocialUserDetailsService接口,通过userId获取UserDetail用户信息,在MyUserDetailsService 也实现SocialUserDetailsService来处理社交登录,SocialUserDetails 是UserDetails的实现

package com.imooc.security.browser;/**
* UserDetailsService是SpringSecurity的一个接口,
* 只有一个方法:根据用户名获取用户详情
* ClassName: MyUserDetailService
* @Description:
* SocialUserDetailsService:SpringSocial查询用户信息的接口
* @author lihaoyang
* @date 2018年2月28日
*/
@Component
public class MyUserDetailsService_ implements UserDetailsService,SocialUserDetailsService{ private Logger logger = LoggerFactory.getLogger(getClass()); @Autowired
private PasswordEncoder passwordEncoder; /**
* UserDetails接口,实际可以自己实现这个接口,返回自己的实现类
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
logger.info("表单登录用户名:"+username);
//根据用户名查询用户信息 //User:springsecurity 对 UserDetails的一个实现
//为了演示在这里用passwordEncoder加密一下密码,实际中在注册时就加密,此处直接拿出密码
// String password = passwordEncoder.encode("123456");
// System.err.println("加密后密码: "+password);
// //参数:用户名|密码|是否启用|账户是否过期|密码是否过期|账户是否锁定|权限集合
// return new User(username,password,true,true,true,true,AuthorityUtils.commaSeparatedStringToAuthorityList("admin")); return buildUser(username);
} /**
* 第三方登录使用
*/
@Override
public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {
logger.info("设交用户id:"+userId);
return buildUser(userId);
} private SocialUserDetails buildUser(String userId) {
String password = passwordEncoder.encode("123456");
System.err.println("加密后密码: "+password);
//参数:用户名|密码|是否启用|账户是否过期|密码是否过期|账户是否锁定|权限集合
return new SocialUser(userId,password,true,true,true,true,AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
} }

第五步:配置appId、

QQProperties:

package com.imooc.security.core.properties;

import org.springframework.boot.autoconfigure.social.SocialProperties;

/**
* QQ登录相关配置
* ClassName: QQProperties
* @Description: TODO
* @author lihaoyang
* @date 2018年3月7日
*/
public class QQProperties extends SocialProperties { private String providerId = "qq"; public String getProviderId() {
return providerId;
} public void setProviderId(String providerId) {
this.providerId = providerId;
} }
SocialProperties 是spring提供的:

加一层SocialProperties:

package com.imooc.security.core.properties;

/**
* 第三方登录相关配置
* ClassName: SocialProperties
* @Description: TODO
* @author lihaoyang
* @date 2018年3月7日
*/
public class SocialProperties { private QQProperties qq = new QQProperties(); public QQProperties getQq() {
return qq;
} public void setQq(QQProperties qq) {
this.qq = qq;
} }

QQAutoConfig:

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.connect.QQConnectionFactory; /**
*
* ClassName: QQAutoConfig
* @Description:
* @ConditionalOnProperty: 配置里有imooc.security.social.qq.app-id 这个类才会生效
* @author lihaoyang
* @date 2018年3月7日
*/
@Configuration
@ConditionalOnProperty(prefix = "imooc.security.social.qq" , name = "app-id")
public class QQAutoConfig extends SocialAutoConfigurerAdapter { @Autowired
private SecurityProperties securityProperties; @Override
protected ConnectionFactory<?> createConnectionFactory() {
QQProperties qqConfig = securityProperties.getSocial().getQq();
return new QQConnectionFactory(qqConfig.getProviderId(), qqConfig.getAppId(), qqConfig.getAppSecret());
} }

在demo项目的application.properties 里配置:

imooc.security.social.qq.app-id =
imooc.security.social.qq.app-secret =

Spring Security构建Rest服务-0102-Spring Social开发第三方登录之qq登录的更多相关文章

  1. Spring Security构建Rest服务-1000-使用SpringSocial开发第三方登录之大白话OAuth协议

    OAuth协议简介 OAuth协议要解决的问题    OAuth协议中的各种角色 OAuth协议运行流程 OAuth协议,在网上也看了一些资料,意思就是给你颁发一个临时的通行证,你拿着这个通行证可以访 ...

  2. Spring Security构建Rest服务-1001-spring social开发第三方登录之spring social基本原理

    OAuth协议是一个授权协议,目的是让用户在不将服务提供商的用户名密码交给第三方应用的条件下,让第三方应用可以有权限访问用户存在服务提供商上的资源. 接着上一篇说的,在第三方应用获取到用户资源后,如果 ...

  3. Spring Security构建Rest服务-0100-前言

    一.我的前言 这是看慕课网老师讲的SpringSecurity的学习笔记,老师讲的很好,开篇就说到了我的心里,老师说道: 有一定经验的程序员如何提升自己? 1,每天都很忙,但是感觉水平没有提升 2,不 ...

  4. Spring Security构建Rest服务-1300-Spring Security OAuth开发APP认证框架之JWT实现单点登录

    基于JWT实现SSO 在淘宝( https://www.taobao.com )上点击登录,已经跳到了 https://login.taobao.com,这是又一个服务器.只要在淘宝登录了,就能直接访 ...

  5. Spring Security构建Rest服务-1202-Spring Security OAuth开发APP认证框架之重构3种登录方式

    SpringSecurityOAuth核心源码解析 蓝色表示接口,绿色表示类 1,TokenEndpoint 整个入口点,相当于一个controller,不同的授权模式获取token的地址都是 /oa ...

  6. Spring Security构建Rest服务-1200-SpringSecurity OAuth开发APP认证框架

    基于服务器Session的认证方式: 前边说的用户名密码登录.短信登录.第三方登录,都是普通的登录,是基于服务器Session保存用户信息的登录方式.登录信息都是存在服务器的session(服务器的一 ...

  7. Spring Security构建Rest服务-1201-Spring Security OAuth开发APP认证框架之实现服务提供商

    实现服务提供商,就是要实现认证服务器.资源服务器. 现在做的都是app的东西,所以在app项目写代码  认证服务器: 新建 ImoocAuthenticationServerConfig 类,@Ena ...

  8. Spring Security构建Rest服务-0702-短信验证码登录

    先来看下 Spring Security密码登录大概流程,模拟这个流程,开发短信登录流程 1,密码登录请求发送给过滤器 UsernamePasswordAuthenticationFilter 2,过 ...

  9. Spring Security构建Rest服务-0900-rememberMe记住我

    Spring security记住我基本原理: 登录的时候,请求发送给过滤器UsernamePasswordAuthenticationFilter,当该过滤器认证成功后,会调用RememberMeS ...

随机推荐

  1. C++之类和对象的使用(二)

    析构函数 析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一系列清理工作,使这部分内存可以被程序分配给新对象使用.对象生命周期结束,程序就自动执行析构函数来完成这些工作. 析构函数是一种 ...

  2. file.write(str),file.writelines(sequence)

    file.write(str)的参数是一个字符串,就是你要写入文件的内容.file.writelines(sequence)的参数是序列,比如列表,它会迭代帮你写入文件.

  3. Windows 下安装ReText

    打算使用MarkDown了,群友推荐使用ReText,基于Python的,同时依赖了Python的几个包,通过easystall可以方便地安装,同时制作了快捷启动方式,网上找了篇文章以备忘. Inst ...

  4. Opengl中的gluProject函数认识

    1. 从官方说明如下 https://www.opengl.org/sdk/docs/man2/xhtml/gluProject.xml Name gluProject — map object co ...

  5. Linux 用 sftp scp命令 互传文件

    sftp它类似于 ftp, 但它进行加密传输,比FTP有更高的安全性. sftp 是SSH服务的子程序 常用命令 pwd 查看当前工作目录 ls 查看远程当前目录下的所以文件或者目录信息 lls 查看 ...

  6. POJ1556 最短路 + 线段相交问题

    POJ1556 题目大意:比较明显的题目,在一个房间中有几堵墙,直着走,问你从(0,5)到(10,5)的最短路是多少 求最短路问题,唯一变化的就是边的获取,需要我们获取边,这就需要判断我们想要走的这条 ...

  7. 四则运算(Java)--温铭淇,付夏阳

    GitHub项目地址: https://github.com/fxyJAVA/Calculation 四则运算项目要求: 程序处理用户需求的模式为: Myapp.exe -n num -r size ...

  8. MLLib实践Naive Bayes

    引言 本文基于Spark (1.5.0) ml库提供的pipeline完整地实践一次文本分类.pipeline将串联单词分割(tokenize).单词频数统计(TF),特征向量计算(TF-IDF),朴 ...

  9. CUDA开发 - CUDA 版本

    "CUDA runtime is insufficient with CUDA driver"CUDA 9.2: 396.xx CUDA 9.1: 387.xx CUDA 9.0: ...

  10. mac下的抓包工具 -- Charles

    # 背景 换了mac电脑,