微信登陆和QQ登陆大致流程一致,只是有些api不一样,主要是QQ的getUserInfo微信多了一个参数openId。这是因为微信互联文档中在OAuth2.0的认证流程示意图第五步时,微信的openid 同access_token一起返回。而Spring Social获取access_token的类AccessGrant.java中没有openid
微信暂时没有申请测试账户,先上代码后续网站备案下来再自行测试
这里也分三个模块进行开发 api connect config
api
-
package com.rui.tiger.auth.core.social.wechat.api;
-
-
/**
-
* 微信用户api接口
-
* @author CaiRui
-
* @Date 2019-01-12 12:08
-
*/
-
public
interface WechatApi {
-
/**
-
* 获取微信用户信息
-
* @param openId
-
* @return
-
*/
-
WechatUserInfo getUserInfo(String openId);
-
}
-
package com.rui.tiger.auth.core.social.wechat.api;
-
-
import com.alibaba.fastjson.JSON;
-
import lombok.extern.slf4j.Slf4j;
-
import org.apache.commons.lang.StringUtils;
-
import org.springframework.http.converter.HttpMessageConverter;
-
import org.springframework.http.converter.StringHttpMessageConverter;
-
import org.springframework.social.oauth2.AbstractOAuth2ApiBinding;
-
import org.springframework.social.oauth2.TokenStrategy;
-
-
import java.nio.charset.Charset;
-
import java.util.List;
-
-
/**
-
* @author CaiRui
-
* @Date 2019-01-12 12:24
-
*/
-
@Slf4j
-
public
class WechatApiImpl extends AbstractOAuth2ApiBinding implements WechatApi {
-
-
/**
-
* 获取用户信息的url
-
* https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID
-
* access_token 父类会帮我们拼接
-
*/
-
private
static
final String URL_GET_USER_INFO =
"https://api.weixin.qq.com/sns/userinfo?openid=";
-
-
/**
-
* @param accessToken
-
*/
-
public WechatApiImpl(String accessToken) {
-
super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);
-
}
-
-
/**
-
* 默认注册的StringHttpMessageConverter字符集为ISO-8859-1,而微信返回的是UTF-8的,所以覆盖了原来的方法。
-
*/
-
protected List<HttpMessageConverter<?>> getMessageConverters() {
-
List<HttpMessageConverter<?>> messageConverters =
super.getMessageConverters();
-
messageConverters.remove(
0);
-
messageConverters.add(
new StringHttpMessageConverter(Charset.forName(
"UTF-8")));
-
return messageConverters;
-
}
-
-
@Override
-
public WechatUserInfo getUserInfo(String openId) {
-
String url = URL_GET_USER_INFO + openId;
-
String response = getRestTemplate().getForObject(url, String.class);
-
if(StringUtils.contains(response,
"errcode")) {
-
log.info(
"微信用户信息获取失败:"+response);
-
return
null;
-
}
-
WechatUserInfo profile =
null;
-
try {
-
profile = JSON.parseObject(response, WechatUserInfo.class);
-
}
catch (Exception e) {
-
log.info(
"微信用户信息json转换异常",e);
-
}
-
return profile;
-
}
-
}
微信社交用户信息封装
-
package com.rui.tiger.auth.core.social.wechat.api;
-
-
import lombok.Data;
-
-
/**
-
* 微信用户信息
-
* https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316518&token=&lang=zh_CN}
-
* @author CaiRui
-
* @Date 2019-01-12 12:13
-
*/
-
@Data
-
public
class WechatUserInfo {
-
-
/**
-
* 普通用户的标识,对当前开发者帐号唯一
-
*/
-
private String openid;
-
/**
-
* 普通用户昵称
-
*/
-
private String nickname;
-
/**
-
* 语言
-
*/
-
private String language;
-
/**
-
* 普通用户性别,1为男性,2为女性
-
*/
-
private String sex;
-
/**
-
* 普通用户个人资料填写的省份
-
*/
-
private String province;
-
/**
-
* 普通用户个人资料填写的城市
-
*/
-
private String city;
-
/**
-
* 国家,如中国为CN
-
*/
-
private String country;
-
/**
-
* 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空
-
*/
-
private String headimgurl;
-
/**
-
* 用户特权信息,json数组,如微信沃卡用户为(chinaunicom)
-
*/
-
private String[] privilege;
-
/**
-
* 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。
-
*/
-
private String unionid;
-
-
}
connect
微信的返回比标准的多了个openId
-
package com.rui.tiger.auth.core.social.wechat.connect;
-
-
import org.springframework.social.oauth2.AccessGrant;
-
-
/**
-
* 微信的access_token信息。与标准OAuth2协议不同,微信在获取access_token时会同时返回openId,并没有单独的通过accessToke换取openId的服务
-
* 所以在这里继承了标准AccessGrant,添加了openId字段,作为对微信access_token信息的封装。
-
* @author CaiRui
-
* @Date 2019-01-12 13:08
-
*/
-
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);
-
}
-
-
/**
-
* @return the openId
-
*/
-
public String getOpenId() {
-
return openId;
-
}
-
-
/**
-
* @param openId the openId to set
-
*/
-
public void setOpenId(String openId) {
-
this.openId = openId;
-
}
-
}
api适配器
-
package com.rui.tiger.auth.core.social.wechat.connect;
-
-
import com.rui.tiger.auth.core.social.wechat.api.WechatApi;
-
import com.rui.tiger.auth.core.social.wechat.api.WechatUserInfo;
-
import org.springframework.social.connect.ApiAdapter;
-
import org.springframework.social.connect.ConnectionValues;
-
import org.springframework.social.connect.UserProfile;
-
-
/**
-
* 微信 api适配器,将微信 api的数据模型转为spring social的标准模型。
-
* @author CaiRui
-
* @Date 2019-01-12 13:34
-
*/
-
public
class WechatApiAdapter implements ApiAdapter<WechatApi> {
-
private String openId;
-
-
public WechatApiAdapter() {}
-
-
public WechatApiAdapter(String openId){
-
this.openId = openId;
-
}
-
-
/**
-
* @param api
-
* @return
-
*/
-
@Override
-
public boolean test(WechatApi api) {
-
return
true;
-
}
-
-
/**
-
* @param api
-
* @param values
-
*/
-
@Override
-
public void setConnectionValues(WechatApi api, ConnectionValues values) {
-
WechatUserInfo profile = api.getUserInfo(openId);
-
values.setProviderUserId(profile.getOpenid());
-
values.setDisplayName(profile.getNickname());
-
values.setImageUrl(profile.getHeadimgurl());
-
}
-
-
/**
-
* @param api
-
* @return
-
*/
-
@Override
-
public UserProfile fetchUserProfile(WechatApi api) {
-
return
null;
-
}
-
-
/**
-
* @param api
-
* @param message
-
*/
-
@Override
-
public void updateStatus(WechatApi api, String message) {
-
//do nothing
-
}
-
}
-
package com.rui.tiger.auth.core.social.wechat.connect;
-
-
import com.alibaba.fastjson.JSON;
-
import lombok.extern.slf4j.Slf4j;
-
import org.apache.commons.collections.MapUtils;
-
import org.apache.commons.lang.StringUtils;
-
import org.springframework.http.converter.StringHttpMessageConverter;
-
import org.springframework.social.oauth2.AccessGrant;
-
import org.springframework.social.oauth2.OAuth2Parameters;
-
import org.springframework.social.oauth2.OAuth2Template;
-
import org.springframework.util.MultiValueMap;
-
import org.springframework.web.client.RestTemplate;
-
-
import java.nio.charset.Charset;
-
import java.util.Map;
-
-
/**
-
* 完成微信的OAuth2认证流程的模板类。国内厂商实现的OAuth2每个都不同,
-
* spring默认提供的OAuth2Template适应不了,只能针对每个厂商自己微调。
-
* @author CaiRui
-
* @Date 2019-01-12 13:23
-
*/
-
@Slf4j
-
public
class WechatOAuth2Template extends OAuth2Template {
-
-
private String clientId;
-
-
private String clientSecret;
-
-
private String accessTokenUrl;
-
/**
-
* 获取token
-
* https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
-
*/
-
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;
-
}
-
-
/* (non-Javadoc)
-
* @see org.springframework.social.oauth2.OAuth2Template#exchangeForAccess(java.lang.String, java.lang.String, org.springframework.util.MultiValueMap)
-
*/
-
@Override
-
public AccessGrant exchangeForAccess(String authorizationCode, String redirectUri,
-
MultiValueMap<String, String> parameters) {
-
-
StringBuilder accessTokenRequestUrl =
new StringBuilder(accessTokenUrl);
-
-
accessTokenRequestUrl.append(
"?appid="+clientId);
-
accessTokenRequestUrl.append(
"&secret="+clientSecret);
-
accessTokenRequestUrl.append(
"&code="+authorizationCode);
-
accessTokenRequestUrl.append(
"&grant_type=authorization_code");
-
accessTokenRequestUrl.append(
"&redirect_uri="+redirectUri);
-
-
return getAccessToken(accessTokenRequestUrl);
-
}
-
-
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);
-
}
-
-
@SuppressWarnings(
"unchecked")
-
private AccessGrant getAccessToken(StringBuilder accessTokenRequestUrl) {
-
-
log.info(
"获取access_token, 请求URL: "+accessTokenRequestUrl.toString());
-
-
String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);
-
-
log.info(
"获取access_token, 响应内容: "+response);
-
-
Map<String, Object> result =
null;
-
try {
-
result = JSON.parseObject(response, Map.class);
-
}
catch (Exception e) {
-
log.error(
"微信获取token解析json异常",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);
-
}
-
-
WechatAccessGrant accessToken =
new WechatAccessGrant(
-
MapUtils.getString(result,
"access_token"),
-
MapUtils.getString(result,
"scope"),
-
MapUtils.getString(result,
"refresh_token"),
-
MapUtils.getLong(result,
"expires_in"));
-
-
accessToken.setOpenId(MapUtils.getString(result,
"openid"));
-
-
return accessToken;
-
}
-
-
/**
-
* 构建获取授权码的请求。也就是引导用户跳转到微信的地址。
-
* https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN
-
*/
-
public String buildAuthenticateUrl(OAuth2Parameters parameters) {
-
String url =
super.buildAuthenticateUrl(parameters);
-
url=url.replace(
"client_id",
"appid");
-
//url = url + "&appid="+clientId+"&scope=snsapi_login";
-
url = url +
"&scope=snsapi_login";
-
log.info(
"微信获取授权码地址url:"+url);
-
return url;
-
}
-
-
public String buildAuthorizeUrl(OAuth2Parameters parameters) {
-
return buildAuthenticateUrl(parameters);
-
}
-
-
/**
-
* 微信返回的contentType是html/text,添加相应的HttpMessageConverter来处理。
-
*/
-
protected RestTemplate createRestTemplate() {
-
RestTemplate restTemplate =
super.createRestTemplate();
-
restTemplate.getMessageConverters().add(
new StringHttpMessageConverter(Charset.forName(
"UTF-8")));
-
return restTemplate;
-
}
-
-
}
-
package com.rui.tiger.auth.core.social.wechat.connect;
-
-
import com.rui.tiger.auth.core.social.wechat.api.WechatApi;
-
import com.rui.tiger.auth.core.social.wechat.api.WechatApiImpl;
-
import org.springframework.social.oauth2.AbstractOAuth2ServiceProvider;
-
-
/**
-
* 微信的OAuth2流程处理器的提供器,供spring social的connect体系调用
-
* @author CaiRui
-
* @Date 2019-01-12 13:40
-
*/
-
public
class WechatServiceProvider extends AbstractOAuth2ServiceProvider<WechatApi> {
-
-
/**
-
* 微信获取授权码的url
-
*
-
* https://open.weixin.qq.com/connect/qrconnect?
-
* appid=APPID&
-
* redirect_uri=REDIRECT_URI&
-
* response_type=code&
-
* scope=SCOPE&
-
* state=STATE#wechat_redirect
-
*/
-
private
static
final String URL_AUTHORIZE =
"https://open.weixin.qq.com/connect/qrconnect";
-
/**
-
* 微信获取accessToken的url
-
*/
-
private
static
final String URL_ACCESS_TOKEN =
"https://api.weixin.qq.com/sns/oauth2/access_token";
-
-
/**
-
* @param appId
-
* @param appSecret
-
*/
-
public WechatServiceProvider(String appId, String appSecret) {
-
super(
new WechatOAuth2Template(appId, appSecret,URL_AUTHORIZE,URL_ACCESS_TOKEN));
-
}
-
-
-
/* (non-Javadoc)
-
* @see org.springframework.social.oauth2.AbstractOAuth2ServiceProvider#getApi(java.lang.String)
-
*/
-
@Override
-
public WechatApi getApi(String accessToken) {
-
return
new WechatApiImpl(accessToken);
-
}
-
-
}
-
package com.rui.tiger.auth.core.social.wechat.connect;
-
-
import com.rui.tiger.auth.core.social.wechat.api.WechatApi;
-
import org.springframework.social.connect.ApiAdapter;
-
import org.springframework.social.connect.Connection;
-
import org.springframework.social.connect.ConnectionData;
-
import org.springframework.social.connect.support.OAuth2Connection;
-
import org.springframework.social.connect.support.OAuth2ConnectionFactory;
-
import org.springframework.social.oauth2.AccessGrant;
-
import org.springframework.social.oauth2.OAuth2ServiceProvider;
-
-
/**
-
* @author CaiRui
-
* @Date 2019-01-12 13:32
-
*/
-
public
class WechatConnectionFactory extends OAuth2ConnectionFactory<WechatApi> {
-
-
/**
-
* @param appId
-
* @param appSecret
-
*/
-
public WechatConnectionFactory(String providerId, String appId, String appSecret) {
-
super(providerId,
new WechatServiceProvider(appId, appSecret),
new WechatApiAdapter());
-
}
-
-
/**
-
* 由于微信的openId是和accessToken一起返回的,所以在这里直接根据accessToken设置providerUserId即可,不用像QQ那样通过QQAdapter来获取
-
*/
-
@Override
-
protected String extractProviderUserId(AccessGrant accessGrant) {
-
if(accessGrant
instanceof WechatAccessGrant) {
-
return ((WechatAccessGrant)accessGrant).getOpenId();
-
}
-
return
null;
-
}
-
-
/* (non-Javadoc)
-
* @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.oauth2.AccessGrant)
-
*/
-
public Connection<WechatApi> createConnection(AccessGrant accessGrant) {
-
return
new OAuth2Connection<WechatApi>(getProviderId(), extractProviderUserId(accessGrant), accessGrant.getAccessToken(),
-
accessGrant.getRefreshToken(), accessGrant.getExpireTime(), getOAuth2ServiceProvider(), getApiAdapter(extractProviderUserId(accessGrant)));
-
}
-
-
/* (non-Javadoc)
-
* @see org.springframework.social.connect.support.OAuth2ConnectionFactory#createConnection(org.springframework.social.connect.ConnectionData)
-
*/
-
public Connection<WechatApi> createConnection(ConnectionData data) {
-
return
new OAuth2Connection<WechatApi>(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId()));
-
}
-
-
private ApiAdapter<WechatApi> getApiAdapter(String providerUserId) {
-
return
new WechatApiAdapter(providerUserId);
-
}
-
-
private OAuth2ServiceProvider<WechatApi> getOAuth2ServiceProvider() {
-
return (OAuth2ServiceProvider<WechatApi>) getServiceProvider();
-
}
-
-
}
config
-
package com.rui.tiger.auth.core.social.wechat.config;
-
-
import com.rui.tiger.auth.core.properties.SecurityProperties;
-
import com.rui.tiger.auth.core.properties.WechatProperties;
-
import com.rui.tiger.auth.core.social.wechat.connect.WechatConnectionFactory;
-
import org.springframework.beans.factory.annotation.Autowired;
-
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-
import org.springframework.context.annotation.Configuration;
-
import org.springframework.core.env.Environment;
-
import org.springframework.social.config.annotation.ConnectionFactoryConfigurer;
-
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
-
import org.springframework.social.connect.ConnectionFactory;
-
import org.springframework.social.connect.ConnectionFactoryLocator;
-
import org.springframework.social.connect.UsersConnectionRepository;
-
-
/**
-
* 微信登陆配置
-
* @author CaiRui extends SocialConfigurerAdapter
-
* @Date 2019-01-12 13:57
-
*/
-
//@Configuration
-
//@ConditionalOnProperty(prefix = "tiger.auth.social.wechat", name = "app-id")
-
public
class WechatAutoConfiguration extends SocialConfigurerAdapter {
-
/* @Autowired
-
private SecurityProperties securityProperties;
-
-
@Override
-
public void addConnectionFactories(ConnectionFactoryConfigurer connectionFactoryConfigurer, Environment environment) {
-
connectionFactoryConfigurer.addConnectionFactory(createConnectionFactory());
-
}
-
-
-
-
private ConnectionFactory<?> createConnectionFactory() {
-
WechatProperties weixinConfig = securityProperties.getSocial().getWechat();
-
return new WechatConnectionFactory(weixinConfig.getProviderId(), weixinConfig.getAppId(),
-
weixinConfig.getAppSecret());
-
}
-
-
-
// 后补:做到处理注册逻辑的时候发现的一个bug:登录完成后,数据库没有数据,但是再次登录却不用注册了
-
// 就怀疑是否是在内存中存储了。结果果然发现这里父类的内存ConnectionRepository覆盖了SocialConfig中配置的jdbcConnectionRepository
-
// 这里需要返回null,否则会返回内存的 ConnectionRepository
-
@Override
-
public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
-
return null;
-
}*/
-
-
}
配置文件调整
-
package com.rui.tiger.auth.core.properties;
-
-
/**
-
* 微信配置文件
-
* @author CaiRui
-
* @Date 2019-01-12 14:00
-
*/
-
public
class WechatProperties {
-
-
/**
-
* 第三方id,用来决定发起第三方登录的url,默认是 weixin。
-
*/
-
private String providerId =
"wechat";
-
-
private String appId;
//应用id
-
-
private String appSecret;
//应用密匙
-
-
public String getProviderId() {
-
return providerId;
-
}
-
-
public void setProviderId(String providerId) {
-
this.providerId = providerId;
-
}
-
-
public String getAppId() {
-
return appId;
-
}
-
-
public void setAppId(String appId) {
-
this.appId = appId;
-
}
-
-
public String getAppSecret() {
-
return appSecret;
-
}
-
-
public void setAppSecret(String appSecret) {
-
this.appSecret = appSecret;
-
}
-
}
放到社交配置中
添加微信相关配置
social: filterProcessesUrl: /auth qq: app-id: *** app-secret: *** wechat: app-id: *** app-secret: ***
前台界面添加微信登陆 tiger-login.html
-
<html>
-
<head>
-
<meta charset="UTF-8">
-
<title>登录
</title>
-
</head>
-
<body>
-
<h2>标准登录页面
</h2>
-
<h3>表单登录
</h3>
-
<form action="/authentication/form" method="post">
-
<table>
-
<tr>
-
<td>用户名:
</td>
-
<td>
<input type="text" name="username">
</td>
-
</tr>
-
<tr>
-
<td>密码:
</td>
-
<td>
<input type="password" name="password">
</td>
-
</tr>
-
<tr>
-
<td>图形验证码:
</td>
-
<td>
-
<input type="text" name="imageCode">
-
<img src="/captcha/image">
-
</td>
-
</tr>
-
<tr>
-
<td colspan="2">
<input type="checkbox" name="remember-me" value="true"/>记住我
</td>
-
</tr>
-
<tr>
-
<td colspan="2">
-
<button type="submit">登录
</button>
-
</td>
-
</tr>
-
</table>
-
</form>
-
-
<h3>短信登录
</h3>
-
<form action="/authentication/mobile" method="post">
-
<table>
-
<tr>
-
<td>手机号:
</td>
-
<td>
<input type="text" name="mobile" value="15026929536">
</td>
-
</tr>
-
<tr>
-
<td>短信验证码:
</td>
-
<td>
-
<input type="text" name="smsCode">
-
<a href="/captcha/sms?mobile=13012345678">发送验证码
</a>
-
</td>
-
</tr>
-
<tr>
-
<td colspan="2">
<button type="submit">登录
</button>
</td>
-
</tr>
-
</table>
-
</form>
-
-
<!--<h3>社交登录</h3>
-
<!–不支持get请求 /auth/qq 默认是这个请求 /login/qq –>
-
<form action="/auth/qq" method="post">
-
<button type="submit">QQ登录</button>
-
-
<a href="/qqLogin/weixin">微信登录</a>
-
</form>-->
-
-
<h3>社交登录
</h3>
-
<a href="/auth/qq">QQ登录
</a>
-
-
<a href="/auth/wechat">微信登录
</a>
-
-
</body>
-
</html>
ok 微信登陆的代码都写完了 ,等待后续测试
文章转载至:https://blog.csdn.net/ahcr1026212/article/details/86247943