OAuth协议
含义
发放给用户一个令牌,用户通过令牌来访问数据.
流程
授权模式
- 授权码模式
- 简化模式
- 密码模式
- 客户端模式
授权码模式
SpringSocial实现第三方登陆
基本原理
当用户使用授权码模式拿到令牌之后,可以在服务器上获取用户的基本信息,然后根据用户信息构建Authentication,并放入SecurityContext,此时对于SpringSecurity已经登陆成功.
具体实现
-
ServiceProvider(AbstractOAuth2ServiceProvider):服务提供商的抽象,针对每个具体的服务提供商(比如qq,微信等)提供的接口的实现,并提供抽象类AbstractOAuth2ServiceProvider,我们实现这个抽象类
- OAuth2Operations(OAuth2Template):OAuth协议相关的操作,OAuth2Template完成OAuth协议的执行流程
- api(AbstractOAuth2ApiBinding):实现获取用户信息
- Connection(OAuth2Connection):封装之前获取到的用户信息,实际实现类为OAuth2Connection
-
ConnectionFactory(OAuth2ConnectionFactory):负责创建OAuth2Connection
- ServiceProvider:封装了前面所有的流程,通过调用ServiceProvider走前面所有的流程,走通之后通过获取到的用户数据创建OAuth2Connection对象
- ApiAdapter:不同的服务提供商具有不同的用户数据结构,ApiAdapter主要负责将不同的数据猎狗转换成Connection标准的数据结构
- UserConnection:在数据库中,将我们业务的用户于服务提供商的用户建立对应关系
- UserConnectionRepository(JdbcUserConnectionRepository):负责操作UserConnection的对应关系,JdbcUserConnectionRepository负责针对UserConnection表中的数据做增删改查的操作
QQ登录
封装QQ登录返回的用户数据
@Data
public class QQUserInfo {
/** 返回码 */
private String ret;
/** 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码。 */
private String msg;
/** */
private String openId;
/** 不知道什么东西,文档上没写,但是实际api返回里有。 */
private String is_lost;
/** 省(直辖市) */
private String province;
/** 市(直辖市区) */
private String city;
/** 出生年月 */
private String year;
/** 用户在QQ空间的昵称。 */
private String nickname;
/** 大小为30×30像素的QQ空间头像URL。 */
private String figureurl;
/** 大小为50×50像素的QQ空间头像URL。 */
private String figureurl_1;
/** 大小为100×100像素的QQ空间头像URL。 */
private String figureurl_2;
/** 大小为40×40像素的QQ头像URL。 */
private String figureurl_qq_1;
/** 大小为100×100像素的QQ头像URL。需要注意,不是所有的用户都拥有QQ的100×100的头像,但40×40像素则是一定会有。 */
private String figureurl_qq_2;
/** 性别。 如果获取不到则默认返回”男” */
private String gender;
/** 标识用户是否为黄钻用户(0:不是;1:是)。 */
private String is_yellow_vip;
/** 标识用户是否为黄钻用户(0:不是;1:是) */
private String vip;
/** 黄钻等级 */
private String yellow_vip_level;
/** 黄钻等级 */
private String level;
/** 标识是否为年费黄钻用户(0:不是; 1:是) */
private String is_yellow_year_vip;
}
建立获取用户信息的接口
public interface QQ {
QQUserInfo getUserInfo();
}
建立QQ接口的实现类,并继承AbstractOAuth2ApiBinding抽象类,
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {
private static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";
private static final String URL_GET_USERINFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";
private String appId;
priate String openId;
public QQImpl(String accessToken, String appId) {
super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
this.appId = appId;
String url = String.format(URL_GET_OPENID, accessToken);
String result = getRestTemplate().getForObject(url, String.class);
System.out.println(result);
this.openId = String.valueOf(JSONUtil.parseObj(result).get("openid"));
}
/**
* 获取用户信息
*
* @return
*/
@Override
public QQUserInfo getUserInfo() {
String url = String.format(URL_GET_USERINFO, appId, openId);
String result = getRestTemplate().getForObject(url, String.class);
System.out.println(result);
QQUserInfo userInfo = null;
try {
userInfo = JSONUtil.toBean(result, QQUserInfo.class);
userInfo.setOpenId(openId);
return userInfo;
} catch (Exception e) {
throw new RuntimeException("获取用户信息失败", e);
}
}
}
实现ServiceProvider
public class QQServiceProvider extends AbstractOAuth2ServiceProvider {
private String appId;
private static final String URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";
private static final String URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";
public QQServiceProvider(String appId, String appSecret) {
super(new OAuth2Template(appId, appSecret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
}
@Override
public QQ getApi(String accessToken) {
return new QQImpl(accessToken, appId);
}
}
实现ApiAdapter
public class QQAdapter implements ApiAdapter {
//发出请求判断请求是不是通的,true为是通的,不发出请求
@Override
public boolean test(QQ qq) {
return true;
}
//适配onnection数据和api数据
@Override
public void setConnectionValues(QQ qq, ConnectionValues values) {
QQUserInfo userInfo = qq.getUserInfo();
values.setDisplayName(userInfo.getNickname());
values.setImageUrl(userInfo.getFigureurl_qq_1());
values.setProfileUrl(null);
values.setProviderUserId(userInfo.getOpenId());
}
@Override
public UserProfile fetchUserProfile(QQ qq) {
return null;
}
@Override
public void updateStatus(QQ qq, String s) {
}
}
实现ConnectionFactory
public class QQConnectionFactory extends OAuth2ConnectionFactory {
public QQConnectionFactory(String providerId, String appId, String appSecret) {
super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());
}
}
qq登录的各项配置
public class QQProperties extends SocialProperties {
private String providerId = "qq";
public String getProviderId() {
return providerId;
}
public void setProviderId(String providerId) {
this.providerId = providerId;
}
}
注:springboot2.0在org.springframework.boot.autoconfigure里没有social相关的配置简化类。直接将SocialAutoConfigurerAdapter、SocialProperties和SocialWebAutoConfiguration这三个类拷贝到项目里
将QQProperties添加到整体的SecurityProperties中
public class SocialProperties {
private QQProperties qq = new QQProperties();
}