述
上文中实现了整个QQ登录的流程,本文来实现以下微信的授权登录, 总体逻辑是和QQ一样的,部分地方有所区别
ServiceProvider构建
构建 ServiceProvider
需要 API
和 OAuth2Operations
微信API
新建接口 WeChat
1
2
3
4
5
6
7
8public interface WeChat {
/**
* 获取微信的用户信息
* @return {@link WeixinUserInfo}
*/
WeixinUserInfo getUserInfo(String openId);
}
这里和QQ有所区别, QQ 是获取到token之后,再去获取openId,然后再去获取微信的用户信息, 而微信在获取token的时候,就会把 openId 返回来,所以这里直接传过来就好了
然后是实现类 WeChatImpl
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@Slf4j
public class WeChatImpl extends AbstractOAuth2ApiBinding implements WeChat {
private ObjectMapper objectMapper = new ObjectMapper();
/**
* 获取用户信息的url
*/
private static final String URL_GET_USER_INFO = "https://api.weixin.qq.com/sns/userinfo?openid=";
public WeChatImpl(String accessToken) {
super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
}
/**
* 默认注册的StringHttpMessageConverter字符集为ISO-8859-1,而微信返回的是UTF-8的,所以覆盖了原来的方法。
*/
@Override
protected List<HttpMessageConverter<?>> getMessageConverters() {
List<HttpMessageConverter<?>> messageConverters = super.getMessageConverters();
messageConverters.remove(0);
messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return messageConverters;
}
@Override
public WeixinUserInfo getUserInfo(String openId) {
String url = URL_GET_USER_INFO + openId;
String responseStr = getRestTemplate().getForObject(url, String.class);
log.info("获取微信信息的用户返回数据:{}", responseStr);
if(StringUtils.contains(responseStr, "errcode")) {
return null;
}
WeixinUserInfo userInfo = null;
try {
userInfo = objectMapper.readValue(responseStr, WeixinUserInfo.class);
} catch (IOException e) {
log.info("微信用户信息转换失败:{}", e);
}
return userInfo;
}
}
这里和QQ大致一样, 重写了一个方法是 getMessageConverters()
, 设置了一下编码格式,否则可能返回来是乱码
API
到这里就完成了,然后是 OAuth2Operations
OAuth2Operations
创建 WeChatOAuth2Template
然后继承 OAuth2Template
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
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126@Slf4j
public class WeChatOAuth2Template extends OAuth2Template {
private String clientId;
private String clientSecret;
private String accessTokenUrl;
private ObjectMapper objectMapper = new ObjectMapper();
/**
* 刷新token的url
*/
private static final String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token";
public WeChatOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {
super(clientId, clientSecret, authorizeUrl, accessTokenUrl);
setUseParametersForClientAuthentication(true);
this.clientId = clientId;
this.clientSecret = clientSecret;
this.accessTokenUrl = accessTokenUrl;
}
/**
* 构建获取授权码的请求。也就是引导用户跳转到微信的地址。
*/
@Override
public String buildAuthenticateUrl(OAuth2Parameters parameters) {
String url = super.buildAuthenticateUrl(parameters);
url = url + "&appid=" + clientId + "&scope=snsapi_login";
return url;
}
@Override
public String buildAuthorizeUrl(OAuth2Parameters parameters) {
return buildAuthenticateUrl(parameters);
}
/**
* 微信返回的contentType是html/text,添加相应的HttpMessageConverter来处理。
*/
@Override
protected RestTemplate createRestTemplate() {
RestTemplate restTemplate = super.createRestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));
return restTemplate;
}
/**
* 获取accesstoken
* @param authorizationCode
* @param redirectUri
* @param additionalParameters
* @return
*/
@Override
public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri, MultiValueMap<String, String> additionalParameters) {
// 拼接url
StringBuilder accessTokenRequestUrl = new StringBuilder();
accessTokenRequestUrl.append("?appid=").append(clientId);
accessTokenRequestUrl.append("&secret=").append(clientSecret);
accessTokenRequestUrl.append("&code=").append(authorizationCode);
accessTokenRequestUrl.append("&grant_type=authorization_code");
accessTokenRequestUrl.append("&redirect_uri=").append(redirectUri);
return getAccessToken(accessTokenRequestUrl);
}
/**
* 刷新token的方法
* @param refreshToken
* @param additionalParameters
* @return
*/
@Override
public AccessGrant refreshAccess(String refreshToken, MultiValueMap<String, String> additionalParameters) {
StringBuilder refreshTokenUrl = new StringBuilder(REFRESH_TOKEN_URL);
refreshTokenUrl.append("?appid="+clientId);
refreshTokenUrl.append("&grant_type=refresh_token");
refreshTokenUrl.append("&refresh_token="+refreshToken);
return getAccessToken(refreshTokenUrl);
}
private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) {
log.info("获取微信token,请求:{}", accessTokenRequestUrl);
String url = accessTokenUrl + accessTokenRequestUrl;
String responseStr = getRestTemplate().getForObject(url , String.class);
log.info("获取微信token,返回:{}", responseStr);
// 转成一个map
Map<String, Object> result = null;
try {
result = objectMapper.readValue(responseStr, Map.class);
} catch (IOException e) {
log.error("获取微信token,转换失败:{}", e);
}
// 返回错误码时直接返回空
if(StringUtils.isNotBlank(MapUtils.getString(result, "errcode"))){
String errcode = MapUtils.getString(result, "errcode");
String errmsg = MapUtils.getString(result, "errmsg");
throw new RuntimeException("获取access token失败, errcode:" + errcode + ", errmsg:" + errmsg);
}
// 构建 AccessGrant 对象返回
WeChatAccessGrant weChatAccessGrant = new WeChatAccessGrant(
MapUtils.getString(result, "access_token"),
MapUtils.getString(result, "scope"),
MapUtils.getString(result, "refresh_token"),
MapUtils.getLong(result, "expires_in")
);
weChatAccessGrant.setOpenId(MapUtils.getString(result, "openid"));
return weChatAccessGrant;
}
}
这里重写的方法比较多,主要是获取token的方法, 在标准的 OAuth 流程中, APPID和 appSecret的参数名是 client_id
和 client_secret
这个是 OAuth2Template
里面写死的参数名, 但是在微信的请求中,参数名是 appId
所以我们只能重写一下她的获取token的方法
这里用到的一个实体类是 WeChatAccessGrant
这个是我们自定义的,继承自 AccessGrant
因为微信返回来的数据多了一个 openId1
2
3
4
5
6
7
8
9
10
11
12
13
14@Data
public class WeChatAccessGrant extends AccessGrant {
private String openId;
public WeChatAccessGrant(){
super("");
}
public WeChatAccessGrant(String accessToken, String scope, String refreshToken, Long expiresIn) {
super(accessToken, scope, refreshToken, expiresIn);
}
}
ServiceProvider
API
和 OAuth2Operations
都有了,就可以构建 ServiceProvider
了,新建 WeChatServiceProvider
1 | public class WeChatServiceProvider extends AbstractOAuth2ServiceProvider<WeChat> { |
和 QQ 基本类似
ApiAdapter
构建 ConnectionFactory
需要 ServiceProvider
和 ApiAdapter
, 下面看一下 ApiAdapter
,新建 WeChatAdapter
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
35public class WeChatAdapter implements ApiAdapter<WeChat> {
private String openId;
public WeChatAdapter() {}
public WeChatAdapter(String openId){
this.openId = openId;
}
@Override
public boolean test(WeChat api) {
return true;
}
@Override
public void setConnectionValues(WeChat api, ConnectionValues values) {
WeixinUserInfo userInfo = api.getUserInfo(openId);
values.setProviderUserId(userInfo.getOpenid());
values.setImageUrl(userInfo.getHeadimgurl() != null ? userInfo.getHeadimgurl() : null);
values.setProfileUrl(null);
values.setDisplayName(userInfo.getNickname());
}
@Override
public UserProfile fetchUserProfile(WeChat api) {
return null;
}
@Override
public void updateStatus(WeChat api, String message) {
}
}
这里和QQ基本都是一样的,也不做过多解释
ConnectionFactory
新建 WeChatConnectionFactory
代码如下: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
43public class WeChatConnectionFactory extends OAuth2ConnectionFactory<WeChat> {
public WeChatConnectionFactory(String providerId, String appId, String appSecret) {
super(providerId, new WeChatServiceProvider(appId, appSecret), new WeChatAdapter());
}
@Override
public Connection<WeChat> createConnection(AccessGrant accessGrant) {
return new OAuth2Connection<WeChat>(getProviderId(), extractProviderUserId(accessGrant), accessGrant.getAccessToken(),
accessGrant.getRefreshToken(), accessGrant.getExpireTime(), getOAuth2ServiceProvider(), getApiAdapter(extractProviderUserId(accessGrant)));
}
@Override
public Connection<WeChat> createConnection(ConnectionData data) {
return new OAuth2Connection<WeChat>(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId()));
}
/**
* 获取第三方用户的openId
* 由于微信的openId是和accessToken一起返回的,所以在这里直接根据accessToken设置providerUserId即可,不用像QQ那样通过QQAdapter来获取
*/
@Override
protected String extractProviderUserId(AccessGrant accessGrant) {
if(accessGrant instanceof WeChatAccessGrant) {
return ((WeChatAccessGrant)accessGrant).getOpenId();
}
return null;
}
/**
* 把openid传给 WeChatAdapter
* @param providerUserId
* @return
*/
private ApiAdapter<WeChat> getApiAdapter(String providerUserId) {
return new WeChatAdapter(providerUserId);
}
private OAuth2ServiceProvider<WeChat> getOAuth2ServiceProvider() {
return (OAuth2ServiceProvider<WeChat>) getServiceProvider();
}
}
这里跟QQ不同的地方也是因为 微信的openId是和accessToken一起返回的,所以在这里直接根据accessToken设置providerUserId即可,不用像QQ那样通过QQAdapter来获取
配置类
最后是微信的配置类, 新建 WeChatProperties
1
2
3
4
5
6
7
8
9@Data
public class WeChatProperties extends SocialProperties {
/**
* 第三方id,用来决定发起第三方登录的url,默认是 weixin。
*/
private String providerId = "weixin";
}
这里也和QQ是一样的,然后放到 SocialProperties
里面
最后是微信的自动配置 新建 WeChatAutoConfig
1
2
3
4
5
6
7
8
9
10
11
12
13@Configuration
@ConditionalOnProperty(prefix = "core.security.social.weChat", name = "app-id")
public class WeChatAutoConfig extends SocialAutoConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
@Override
protected ConnectionFactory<?> createConnectionFactory() {
WeChatProperties weChat = securityProperties.getSocial().getWeChat();
return new WeChatConnectionFactory(weChat.getProviderId(), weChat.getAppId(), weChat.getAppSecret());
}
}
这里也和QQ是一样的
配置文件配置
在 application.yml
中配置微信的appid等信息1
2
3
4
5
6
7core:
security:
social:
weChat:
app-id: xxx
app-secret: xxx
providerId: xxx
页面配置
加入微信登录的跳转1
<a href = '/qqLogin/weChat'>微信登录</a>
这里的前缀还是 qqLogin
因为我们配置的 filterProcessesUrl
是 /qqLogin
到这里全部的功能就完成了
总结
- API 部分: 拿到token之后获取用户信息的接口
OAuth2Template
部分: 通过 appid 等信息去获取tokenWeChatServiceProvider
: 把OAuth2Template
用到的一些clientId
,clientSecret
等等信息传过去WeChatConnectionFactory
创建Connection
对象,以及指定api适配器WeChatAutoConfig
读取用户的配置