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字符串编码相关的知识
原文地址:http://www.cnblogs.com/Xjng/p/5093905.html 开发经常会遇到各种字符串编码的问题,例如报错SyntaxError: Non-ASCII charact ...
- vue实现消息的无缝滚动效果
export default { data() { return { animate:false, items:[ {name:"马云"}, {name:"雷军" ...
- SSH整合 第五篇 struts2的到来
struts2的好处,web层的显示,同时Action类相当于MVC模式的C.整合进来的话,是通过与Spring整合,减少重复代码,利用IoC和AOP. 1.struts-2.5.2.jar 以上是s ...
- 几个经典的数学库之一学习---VCGlib(2)
几个经典的数学库之一学习---VCGlib(2) 1. Optional Component(可选的组件) 有许多Vertex和Face的属性并不是一直都是必要的,如Face-Face的邻接关系.VC ...
- spring boot搭建Hello Word
一.安装与配置jdk 二.安装与配置maven 安装好maven,必须配置环境变量 通过cmd命令查询maven是否安装成功,以下是安装成功的界面 修改setting.xml的配置,制定本地仓库的路径 ...
- 团队作业(HCL队)第三周—需求改进和系统分析
2.需求&原型改进: 1.问题:游戏中坦克的移动和攻击怎么控制的? 改进: 在游戏中,我控制我方坦克,按下方向键坦克便向按下的方向移动,按下Z键,我方坦克发射炮弹.敌方坦克面向随机的方向移动, ...
- 使用VSTS进行单元测试练习
本次作业要求:练习教科书第22~25页单元测试练习,要求自行安装Visual Studio开发平台,版本至少在2010以上,要求把程序安装过程和练习过程写到博客上,越详细越好,要图文并茂,没有书的同学 ...
- MAC将 /etc/sudoers文件修改错后的几种解决方法
文件修改错误后难以再次修改的原因: 1.修改此文件必须是root权限 2.此文件出现问题时sudo命令不可用 3.默认情况下MAC系统不启用root用户 解决的方法: 一.启用root用户,使用roo ...
- TCP/IP协议族分层
协议族的分层抽象,一定意义上来说,每层敬职敬责的做自己的工作,同时也共同完成通讯协议的共同目标. 这是一个垂直划分的抽象层次,挺有意义. 1.链路层/数据链路层/网络接口层 操作系统中的设备驱动程序和 ...
- 详解webpack + vue + node 打造单页面(入门篇)
1.node下载地址:http://nodejs.cn/download/,安装完成检查node和npm版本 2.淘宝镜像 : npm install cnpm -g --registry=https ...
