Spring Security构建Rest服务-0102-Spring Social开发第三方登录之qq登录

图一
基于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登录的更多相关文章
- Spring Security构建Rest服务-1000-使用SpringSocial开发第三方登录之大白话OAuth协议
OAuth协议简介 OAuth协议要解决的问题 OAuth协议中的各种角色 OAuth协议运行流程 OAuth协议,在网上也看了一些资料,意思就是给你颁发一个临时的通行证,你拿着这个通行证可以访 ...
- Spring Security构建Rest服务-1001-spring social开发第三方登录之spring social基本原理
OAuth协议是一个授权协议,目的是让用户在不将服务提供商的用户名密码交给第三方应用的条件下,让第三方应用可以有权限访问用户存在服务提供商上的资源. 接着上一篇说的,在第三方应用获取到用户资源后,如果 ...
- Spring Security构建Rest服务-0100-前言
一.我的前言 这是看慕课网老师讲的SpringSecurity的学习笔记,老师讲的很好,开篇就说到了我的心里,老师说道: 有一定经验的程序员如何提升自己? 1,每天都很忙,但是感觉水平没有提升 2,不 ...
- Spring Security构建Rest服务-1300-Spring Security OAuth开发APP认证框架之JWT实现单点登录
基于JWT实现SSO 在淘宝( https://www.taobao.com )上点击登录,已经跳到了 https://login.taobao.com,这是又一个服务器.只要在淘宝登录了,就能直接访 ...
- Spring Security构建Rest服务-1202-Spring Security OAuth开发APP认证框架之重构3种登录方式
SpringSecurityOAuth核心源码解析 蓝色表示接口,绿色表示类 1,TokenEndpoint 整个入口点,相当于一个controller,不同的授权模式获取token的地址都是 /oa ...
- Spring Security构建Rest服务-1200-SpringSecurity OAuth开发APP认证框架
基于服务器Session的认证方式: 前边说的用户名密码登录.短信登录.第三方登录,都是普通的登录,是基于服务器Session保存用户信息的登录方式.登录信息都是存在服务器的session(服务器的一 ...
- Spring Security构建Rest服务-1201-Spring Security OAuth开发APP认证框架之实现服务提供商
实现服务提供商,就是要实现认证服务器.资源服务器. 现在做的都是app的东西,所以在app项目写代码 认证服务器: 新建 ImoocAuthenticationServerConfig 类,@Ena ...
- Spring Security构建Rest服务-0702-短信验证码登录
先来看下 Spring Security密码登录大概流程,模拟这个流程,开发短信登录流程 1,密码登录请求发送给过滤器 UsernamePasswordAuthenticationFilter 2,过 ...
- Spring Security构建Rest服务-0900-rememberMe记住我
Spring security记住我基本原理: 登录的时候,请求发送给过滤器UsernamePasswordAuthenticationFilter,当该过滤器认证成功后,会调用RememberMeS ...
随机推荐
- Python 正斜杠/与反斜杠\
首先,"/"左倾斜是正斜杠,"\"右倾斜是反斜杠,可以记为:除号是正斜杠一般来说对于目录分隔符,Unix和Web用正斜杠/,Windows用反斜杠,但是现在Wi ...
- Node.js是什么[译]
当我向人们介绍Node.js的时候,一般会有两种反应:多数立刻表示“哦,这样啊”,另外的则会感到困惑. 如果你是第二种的话,我会试着这样解释node: 这是一个命令行工具.你可以下载一个tar包,然后 ...
- express 阮一峰的博客
http://javascript.ruanyifeng.com/nodejs/express.html next没怎么用过... 一个不进行任何操作.只传递request对象的中间件 functio ...
- 最佳编程字体:M+
英文原文:The Best Font for Programming: M+ 程序员的最佳等宽字体是 M+. 一个偶然机会遇到了这个字体,在命运多舛的 kod 编辑器体验了.这个字体非比寻常,我坚持 ...
- TFS支持移动设备,微软已经走出了第一步(手机上更新、查询工作项)
TFS支持移动设备,微软已经走出了第一步! 从现在开始,你可以在手机浏览器上打开自己的VSTS团队项目,会看大手机版的工作项界面,你可以在手机设备上更新.查询工作项. 这是原生自带的,这些移动功能马上 ...
- <mvc:annotation-driven />到底帮我们做了啥
一句 <mvc:annotation-driven />实际做了以下工作:(不包括添加自己定义的拦截器) 我们了解这些之后,对Spring3 MVC的控制力就更强大了,想改哪就改哪里. s ...
- MySql数据库远程连接失败问题解决
例如,你想myuser使用mypassword从任何主机连接到mysql服务器的话. GRANT ALL PRIVILEGES ON *.* TO 'myuser'@'%' IDENTIFIED BY ...
- 在AbpZero中hangfire后台作业的使用——hangfire的调度
在abpzero框架中,hangfiire通过依赖注入来进行接口的调用 hangfire的事件处理分为以下几种: 1.基于队列的任务处理(Fire-and-forget jobs) var jobId ...
- [转载]将json字符串转换成json对象
例如: JSON字符串: var str1 = '{ "name": "cxh", "sex": "man" }'; J ...
- C# 一些代码小结--串口操作
串口解析显示中文 private String SerialPortReadStr() { try { String str = null; int n = serialPort1.BytesToRe ...
