Spring Security-14-开发QQ登录(上)

本文开始,实现一个第三方登录,以QQ为例,首先需要去QQ互联里面,申请开发者认证,然后创建应用,搞到AppId,和AppKey

实现流程

按照上文中的实现流程,先是构建 ServiceProvider ,然后是 ConnectionFactory 然后 Connection 最后交互数据库

这里第三方登录可能在APP和web环境下都会用到,所以写在core项目中

ServiceProvider的实现

API部分

首先 ServiceProvider 里面需要两个东西, 一个是 API 一个是 OAuth2Operations

OAuth2Operations 先用 Spring Social 的默认实现 OAuth2Template ,所以现在我们需要搞定 API 部分

首先新建一个获取QQ用户信息的接口.如下:

1
2
3
4
5
6
7
8
9
10
public interface QQ {

/**
* 获取用户信息
* @return {@link QQUserInfo}
*/
QQUserInfo getUserInfo() throws IOException;


}

实体类 QQUserInfo 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
@Data
public class QQUserInfo {

/**
* 返回码
*/
private String ret;
/**
* 如果ret<0,会有相应的错误信息提示,返回数据全部用UTF-8编码。
*/
private String msg;
/**
*
*/
private String openId;
/**
* 不知道什么东西,文档上没写,但是实际api返回里有。
*/
private String is_lost;
/**
* 用户在QQ空间的昵称。
*/
private String nickname;
/**
* 性别。 如果获取不到则默认返回”男”
*/
private String gender;
/**
* 省(直辖市)
*/
private String province;
/**
* 市(直辖市区)
*/
private String city;
/**
* 出生年月
*/
private String year;
/**
* 星座
*/
private String constellation;
/**
* 大小为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 figureurl_qq;
/**
* 头像类型
*/
private String figureurl_type;
/**
* 标识用户是否为黄钻用户(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;
}

然后需要一个实现类 QQImpl,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Slf4j
public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {

public static final String URL_GET_OPENID = "https://graph.qq.com/oauth2.0/me?access_token=%s";

public static final String URL_GET_USER_INFO = "https://graph.qq.com/user/get_user_info?oauth_consumer_key=%s&openid=%s";

private String appId;

private String openId;

private ObjectMapper objectMapper = new ObjectMapper();

public QQImpl(String accessToken, String appId){
super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
this.appId = appId;
// 有了Appid 和 token之后去取openid
String url = String.format(URL_GET_OPENID, accessToken);
String result = getRestTemplate().getForObject(url, String.class);

log.info("QQ获取用户openid结果:{}", result);
// 返回格式是 callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} ) 字符串所以需要截取一下
this.openId = StringUtils.substringBetween(result, "\"openid\":\"", "\"}");
}

@Override
public QQUserInfo getUserInfo() {
// 有了openid之后就可以获取用户信息了
String url = String.format(URL_GET_USER_INFO, appId, openId);
String result = getRestTemplate().getForObject(url, String.class);
log.info("QQ获取用户信息结果:{}", result);

// 转成对象返回
QQUserInfo qqUserInfo = null;
try {
qqUserInfo = objectMapper.readValue(result, QQUserInfo.class);
// 设置openid到用户信息里
qqUserInfo.setOpenId(openId);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("获取QQ用户信息失败");
}

return qqUserInfo;
}
}

首先这里继承了 AbstractOAuth2ApiBinding 这个就是API的默认实现, 首先获取QQ用户需要 Token, appid 和 openId

openId需要我们自己去取, 也就是这个类构造里面写的

来看下 AbstractOAuth2ApiBinding 的代码:
image

他这里的参数一个是accessToken, 一个是RestTemplate, 这俩个都是个全局的变量,所以后面我们不能使用依赖注入, 否则会有线程安全的问题

然后 accessToken 用来放token, 这个类会为我们获取用户信息做一些默认的处理,就是 构造中的 super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER); 我们子类调用了他的这个构造, 第二个参数表示,将token当做参数传递, 如果不传的话,默认会将 token 放到请求头里面

RestTemplate 就是用来发送Http请求到第三方

在构造中拿到openId之后,就可以使用我们的获取用户的方法,去跟QQ交互,获取用户的信息了.

服务提供商实现

API的部分已经实现了,现在再用一个默认的 OAuth2Template 就可以组成 ServiceProvider

新建一个 QQServiceProvider 继承 ServiceProvider 的默认实现 AbstractOAuth2ServiceProvider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> {

/**
* 认证链接
*/
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";

private String appId;

public QQServiceProvider(String appId, String secret) {
super(new OAuth2Template(appId, secret, URL_AUTHORIZE, URL_ACCESS_TOKEN));
this.appId = appId;
}

@Override
public QQ getApi(String accessToken) {
return new QQImpl(accessToken, appId);
}

}

这里会重写一个父类的方法是 getApi ,就把我们刚才写的 API 的实现返回就好

然后上面的构造是指定了一个 OAuth2Operations ,用了他的默认实现 OAuth2Template

到这儿服务提供商的开发就完成了,对照上文中的图去看比较容易理解.

本文代码传送门