1、前言

今天我们就来讲解下最后一篇如何使用SpringSocial来处理类似微信、QQ社交账号登录自己的平台,也就是大家说的第三方登录,获取社交账户所在平台的用户信息,与自己平台信息做个绑定的操作,两个系统之间是通过UserId交换信息的,这点一定要注意,平台用户表(Users)之间是社交用户表(UserConnection)之间关系如下所示:

Users表:

id

name

age

1

David

18

2

Sam

22

3

Tom

30

UserConnection表:

userId

providerId

providerUserId

accessToken

secret

1

qq

xxxxxxxxx

2

wechat

xxxxxxxxx

3

sian

xxxxxxxxx

这里我要提一下,在你没有登录你自己的平台系统之前你点击微信登录的时候要进行判断是否已经绑定,判断的依据是通过providerId和providerUserId从UserConnection表里面查询出社交账户信息,然后通过对应的userId再通过SocialUserDetailsService提供的loadUserByUserId方法查询出系统平台的User用户信息。

1、如果已经绑定(说明上面查询到了)就直接生成token并返回给前端;

2、如果没有(说明上面没有查询到)就需要注册并绑定了(往两张表中插入数据并建立关系)。

2、QQ的实现

首先我们来看看QQ的实现,我把代码都贴出来,遇到关键的地方我会讲解一下,其它的自己看看就明白了

上面是具体的代码目录结构,你没必要和我一模一样,只要能实现就好。

QQUserInfo

package com.awbeci.ssb.core.social.qq.api;

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;

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 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 String getRet() {

return ret;

}

public void setRet(String ret) {

this.ret = ret;

}

public String getMsg() {

return msg;

}

public void setMsg(String msg) {

this.msg = msg;

}

public String getOpenId() {

return openId;

}

public void setOpenId(String openId) {

this.openId = openId;

}

public String getIs_lost() {

return is_lost;

}

public void setIs_lost(String is_lost) {

this.is_lost = is_lost;

}

public String getProvince() {

return province;

}

public void setProvince(String province) {

this.province = province;

}

public String getCity() {

return city;

}

public void setCity(String city) {

this.city = city;

}

public String getYear() {

return year;

}

public void setYear(String year) {

this.year = year;

}

public String getNickname() {

return nickname;

}

public void setNickname(String nickname) {

this.nickname = nickname;

}

public String getFigureurl() {

return figureurl;

}

public void setFigureurl(String figureurl) {

this.figureurl = figureurl;

}

public String getFigureurl_1() {

return figureurl_1;

}

public void setFigureurl_1(String figureurl_1) {

this.figureurl_1 = figureurl_1;

}

public String getFigureurl_2() {

return figureurl_2;

}

public void setFigureurl_2(String figureurl_2) {

this.figureurl_2 = figureurl_2;

}

public String getFigureurl_qq_1() {

return figureurl_qq_1;

}

public void setFigureurl_qq_1(String figureurl_qq_1) {

this.figureurl_qq_1 = figureurl_qq_1;

}

public String getFigureurl_qq_2() {

return figureurl_qq_2;

}

public void setFigureurl_qq_2(String figureurl_qq_2) {

this.figureurl_qq_2 = figureurl_qq_2;

}

public String getGender() {

return gender;

}

public void setGender(String gender) {

this.gender = gender;

}

public String getIs_yellow_vip() {

return is_yellow_vip;

}

public void setIs_yellow_vip(String is_yellow_vip) {

this.is_yellow_vip = is_yellow_vip;

}

public String getVip() {

return vip;

}

public void setVip(String vip) {

this.vip = vip;

}

public String getYellow_vip_level() {

return yellow_vip_level;

}

public void setYellow_vip_level(String yellow_vip_level) {

this.yellow_vip_level = yellow_vip_level;

}

public String getLevel() {

return level;

}

public void setLevel(String level) {

this.level = level;

}

public String getIs_yellow_year_vip() {

return is_yellow_year_vip;

}

public void setIs_yellow_year_vip(String is_yellow_year_vip) {

this.is_yellow_year_vip = is_yellow_year_vip;

}

public String getConstellation() {

return constellation;

}

public void setConstellation(String constellation) {

this.constellation = constellation;

}

}

QQ

public interface QQ {

QQUserInfo getUserInfo();

}

QQImpl

package com.awbeci.ssb.core.social.qq.api.impl;

public class QQImpl extends AbstractOAuth2ApiBinding implements QQ {

private Logger logger = LoggerFactory.getLogger(getClass());

//http://wiki.connect.qq.com/openapi%E8%B0%83%E7%94%A8%E8%AF%B4%E6%98%8E_oauth2-0

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

//http://wiki.connect.qq.com/get_user_info(access_token由父类提供)

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

//appId 配置文件读取

private String appId;

//openId 请求QQ_URL_GET_OPENID返回

private String openId;

/**

* 工具类

*/

private ObjectMapper objectMapper = new ObjectMapper();

/**

* 构造方法获取openId

*/

public QQImpl(String accessToken, String appId) {

//access_token作为查询参数来携带。

super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);

this.appId = appId;

String url = String.format(QQ_URL_GET_OPENID, accessToken);

String result = getRestTemplate().getForObject(url, String.class);

logger.info("【QQImpl】 QQ_URL_GET_OPENID={} result={}", QQ_URL_GET_OPENID, result);

this.openId = StringUtils.substringBetween(result, "\"openid\":\"", "\"}");

}

public QQUserInfo getUserInfo() {

String url = String.format(QQ_URL_GET_USER_INFO, appId, openId);

String result = getRestTemplate().getForObject(url, String.class);

logger.info("【QQImpl】 QQ_URL_GET_USER_INFO={} result={}", QQ_URL_GET_USER_INFO, result);

QQUserInfo userInfo = null;

try {

userInfo = objectMapper.readValue(result, QQUserInfo.class);

userInfo.setOpenId(openId);

logger.info("userinfo={}", userInfo);

return userInfo;

} catch (Exception e) {

throw new RuntimeException("获取用户信息失败", e);

}

}

}

QQAutoConfig

@Configuration

public class QQAutoConfig extends SocialAutoConfigurerAdapter{

@Value("${ssb.security.social.qq.app-id}")

private String qqAppId;

@Value("${ssb.security.social.qq.app-secret}")

private String qqAppSecret;

@Value("${ssb.security.social.qq.provider-id}")

private String qqProviderId;

@Override

protected ConnectionFactory> createConnectionFactory() {

return new QQConnectionFactory(qqProviderId, qqAppId, qqAppSecret);

}

}

SocialAutoConfigurerAdapter

/**

* spring social 1.1.6已经移除了该类,所以自己新建一下

*/

public abstract class SocialAutoConfigurerAdapter extends SocialConfigurerAdapter {

@Override

public void addConnectionFactories(ConnectionFactoryConfigurer configurer, Environment environment) {

configurer.addConnectionFactory(createConnectionFactory());

}

protected abstract ConnectionFactory> createConnectionFactory();

}

QQAdapter

public class QQAdapter implements ApiAdapter {

public boolean test(QQ qq) {

return true;

}

public void setConnectionValues(QQ qq, ConnectionValues connectionValues) {

QQUserInfo userInfo = qq.getUserInfo();

//openId 唯一标识

connectionValues.setProviderUserId(userInfo.getOpenId());

connectionValues.setDisplayName(userInfo.getNickname());

connectionValues.setImageUrl(userInfo.getFigureurl_qq_1());

connectionValues.setProfileUrl(null);

}

public UserProfile fetchUserProfile(QQ qq) {

return null;

}

public void updateStatus(QQ qq, String s) {

}

}

QQConnectionFactory

public class QQConnectionFactory extends OAuth2ConnectionFactory {

public QQConnectionFactory(String providerId, String appId, String appSecret) {

super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter());

}

}

QQOAuth2Template

public class QQOAuth2Template extends OAuth2Template {

private Logger logger = LoggerFactory.getLogger(getClass());

QQOAuth2Template(String clientId, String clientSecret, String authorizeUrl, String accessTokenUrl) {

super(clientId, clientSecret, authorizeUrl, accessTokenUrl);

setUseParametersForClientAuthentication(true);

}

@Override

protected AccessGrant postForAccessGrant(String accessTokenUrl, MultiValueMap parameters) {

String responseStr = getRestTemplate().postForObject(accessTokenUrl, parameters, String.class);

logger.info("【QQOAuth2Template】获取accessToke的响应:responseStr={}" + responseStr);

String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(responseStr, "&");

//http://wiki.connect.qq.com/使用authorization_code获取access_token

//access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14

String accessToken = StringUtils.substringAfterLast(items[0], "=");

Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));

String refreshToken = StringUtils.substringAfterLast(items[2], "=");

return new AccessGrant(accessToken, null, refreshToken, expiresIn);

}

@Override

protected RestTemplate createRestTemplate() {

RestTemplate restTemplate = super.createRestTemplate();

restTemplate.getMessageConverters().add(new StringHttpMessageConverter(Charset.forName("UTF-8")));

return restTemplate;

}

}

QQServiceProvider

public class QQServiceProvider extends AbstractOAuth2ServiceProvider {

//获取code

private static final String QQ_URL_AUTHORIZE = "https://graph.qq.com/oauth2.0/authorize";

//获取access_token 也就是令牌

private static final String QQ_URL_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";

private String appId;

public QQServiceProvider(String appId, String appSecret) {

super(new QQOAuth2Template(appId, appSecret, QQ_URL_AUTHORIZE, QQ_URL_ACCESS_TOKEN));

this.appId = appId;

}

public QQ getApi(String accessToken) {

return new QQImpl(accessToken, appId);

}

}

3、微信的实现

Wechat

public interface Wechat {

WechatUserInfo getUserInfo(String openId);

}

WechatImpl

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=";

/**

* @param accessToken

*/

public WechatImpl(String accessToken) {

super(accessToken, TokenStrategy.ACCESS_TOKEN_PARAMETER);

}

/**

* 默认注册的StringHttpMessageConverter字符集为ISO-8859-1,而微信返回的是UTF-8的,所以覆盖了原来的方法。

*/

protected List> getMessageConverters() {

List> messageConverters = super.getMessageConverters();

messageConverters.remove(0);

messageConverters.add(new StringHttpMessageConverter(Charset.forName("UTF-8")));

return messageConverters;

}

/**

* 获取微信用户信息。

*/

public WechatUserInfo getUserInfo(String openId) {

String url = URL_GET_USER_INFO + openId;

String response = getRestTemplate().getForObject(url, String.class);

if(StringUtils.contains(response, "errcode")) {

return null;

}

WechatUserInfo profile = null;

try {

profile = objectMapper.readValue(response, WechatUserInfo.class);

} catch (Exception e) {

e.printStackTrace();

}

return profile;

}

}

WechatUserInfo

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;

/**

* @return the openid

*/

public String getOpenid() {

return openid;

}

/**

* @param openid the openid to set

*/

public void setOpenid(String openid) {

this.openid = openid;

}

/**

* @return the nickname

*/

public String getNickname() {

return nickname;

}

/**

* @param nickname the nickname to set

*/

public void setNickname(String nickname) {

this.nickname = nickname;

}

/**

* @return the sex

*/

public String getSex() {

return sex;

}

/**

* @param sex the sex to set

*/

public void setSex(String sex) {

this.sex = sex;

}

/**

* @return the province

*/

public String getProvince() {

return province;

}

/**

* @param province the province to set

*/

public void setProvince(String province) {

this.province = province;

}

/**

* @return the city

*/

public String getCity() {

return city;

}

/**

* @param city the city to set

*/

public void setCity(String city) {

this.city = city;

}

/**

* @return the country

*/

public String getCountry() {

return country;

}

/**

* @param country the country to set

*/

public void setCountry(String country) {

this.country = country;

}

/**

* @return the headimgurl

*/

public String getHeadimgurl() {

return headimgurl;

}

/**

* @param headimgurl the headimgurl to set

*/

public void setHeadimgurl(String headimgurl) {

this.headimgurl = headimgurl;

}

/**

* @return the privilege

*/

public String[] getPrivilege() {

return privilege;

}

/**

* @param privilege the privilege to set

*/

public void setPrivilege(String[] privilege) {

this.privilege = privilege;

}

/**

* @return the unionid

*/

public String getUnionid() {

return unionid;

}

/**

* @param unionid the unionid to set

*/

public void setUnionid(String unionid) {

this.unionid = unionid;

}

/**

* @return the language

*/

public String getLanguage() {

return language;

}

/**

* @param language the language to set

*/

public void setLanguage(String language) {

this.language = language;

}

}

WechatAutoConfiguration

@Configuration

@ConditionalOnProperty(prefix = "ssb.security.social.wechat", name = "app-id")

@Order(2)

public class WechatAutoConfiguration extends SocialAutoConfigurerAdapter {

@Value("${ssb.security.social.wechat.app-id}")

private String appId;

@Value("${ssb.security.social.wechat.app-secret}")

private String appSecret;

@Value("${ssb.security.social.wechat.provider-id}")

private String providerId;

/*

* (non-Javadoc)

*

* @see

* org.springframework.boot.autoconfigure.social.SocialAutoConfigurerAdapter

* #createConnectionFactory()

*/

@Override

protected ConnectionFactory> createConnectionFactory() {

return new WechatConnectionFactory(providerId, appId, appSecret);

}

}

WechatAccessGrant

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;

}

}

WechatAdapter

public class WechatAdapter implements ApiAdapter {

private String openId;

public WechatAdapter() {}

public WechatAdapter(String openId){

this.openId = openId;

}

/**

* @param api

* @return

*/

public boolean test(Wechat api) {

return true;

}

/**

* @param api

* @param values

*/

public void setConnectionValues(Wechat api, ConnectionValues values) {

WechatUserInfo profile = api.getUserInfo(openId);

values.setProviderUserId(profile.getOpenid());

values.setDisplayName(profile.getNickname());

values.setImageUrl(profile.getHeadimgurl());

}

/**

* @param api

* @return

*/

public UserProfile fetchUserProfile(Wechat api) {

return null;

}

/**

* @param api

* @param message

*/

public void updateStatus(Wechat api, String message) {

//do nothing

}

}

WechatConnectionFactory

public class WechatConnectionFactory extends OAuth2ConnectionFactory {

/**

* @param appId

* @param appSecret

*/

public WechatConnectionFactory(String providerId, String appId, String appSecret) {

super(providerId, new WechatServiceProvider(appId, appSecret), new WechatAdapter());

}

/**

* 由于微信的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 createConnection(AccessGrant accessGrant) {

return new OAuth2Connection(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 createConnection(ConnectionData data) {

return new OAuth2Connection(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId()));

}

private ApiAdapter getApiAdapter(String providerUserId) {

return new WechatAdapter(providerUserId);

}

private OAuth2ServiceProvider getOAuth2ServiceProvider() {

return (OAuth2ServiceProvider) getServiceProvider();

}

}

WechatOAuth2Template

public class WechatOAuth2Template extends OAuth2Template {

private String clientId;

private String clientSecret;

private String accessTokenUrl;

private static final String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token";

private Logger logger = LoggerFactory.getLogger(getClass());

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 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);

logger.info("----------- accessTokenRequestUrl = {} ------------",accessTokenRequestUrl);

return getAccessToken(accessTokenRequestUrl);

}

public AccessGrant refreshAccess(String refreshToken, MultiValueMap 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) {

logger.info("获取access_token, 请求URL: " + accessTokenRequestUrl.toString());

String response = getRestTemplate().getForObject(accessTokenRequestUrl.toString(), String.class);

logger.info("获取access_token, 响应内容: " + response);

Map result = null;

try {

result = new ObjectMapper().readValue(response, Map.class);

} catch (Exception e) {

e.printStackTrace();

}

//返回错误码时直接返回空

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;

}

/**

* 构建获取授权码的请求。也就是引导用户跳转到微信的地址。

*/

public String buildAuthenticateUrl(OAuth2Parameters parameters) {

String url = super.buildAuthenticateUrl(parameters);

url = url + "&appid=" + clientId + "&scope=snsapi_login";

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;

}

}

WechatServiceProvider

public class WechatServiceProvider extends AbstractOAuth2ServiceProvider {

/**

* 微信获取授权码的url

*/

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 Wechat getApi(String accessToken) {

return new WechatImpl(accessToken);

}

}

4、QQ和微信相关配置

SsbSocialConfig

@Configuration

@EnableSocial

@Order(1)

public class SsbSocialConfig extends SocialConfigurerAdapter {

@Value("${ssb.security.social.filter-processes-url}")

private String filterProcessesUrl;

@Value("${ssb.security.social.register-url}")

private String registerUrl;

@Autowired

private DataSource dataSource;

@Autowired(required = false)

ConnectionSignUp connectionSignUp;

// 后处理器

@Autowired(required = false)

SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;

public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {

//建立jdbc的连接

JdbcUsersConnectionRepository repository = new JdbcUsersConnectionRepository(dataSource,

connectionFactoryLocator, Encryptors.noOpText());

repository.setTablePrefix("xfind_");

// 默认注册用户

// if (connectionSignUp != null) {

// repository.setConnectionSignUp(connectionSignUp);

// }

return repository;

}

/**

* 自定义qq登录路径和注册路径

*

* @return

*/

@Bean

public SpringSocialConfigurer ssbSocialSecurityConfig() {

SsbSpringSocialConfigurer configurer = new SsbSpringSocialConfigurer(filterProcessesUrl);

//1、认证失败跳转注册页面

// 跳转到signUp controller,从session中获取用户信息并通过生成的uuid保存到redis里面,然后跳转bind页面

// 前端绑定后发送用户信息到后台bind controller,1)保存到自己系统用户;2)保存一份userconnection表数据,Spring Social通过这里面表数据进行判断是否绑定

configurer.signupUrl(registerUrl);

//2、认证成功跳转后处理器,跳转带token的成功页面

configurer.setSocialAuthenticationFilterPostProcessor(socialAuthenticationFilterPostProcessor);

return configurer;

}

@Bean

public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {

return new ProviderSignInUtils(connectionFactoryLocator, getUsersConnectionRepository(connectionFactoryLocator));

}

@Override

public UserIdSource getUserIdSource() {

return new AuthenticationNameUserIdSource();

}

}

SsbSpringSocialConfigurer

public class SsbSpringSocialConfigurer extends SpringSocialConfigurer {

private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor;

// 设置自定义url

private String filterProcessesUrl;

public SsbSpringSocialConfigurer(String filterProcessesUrl) {

this.filterProcessesUrl = filterProcessesUrl;

}

/**

* 重写qq登录url

*

* @param object

* @param

* @return

*/

@Override

protected T postProcess(T object) {

SocialAuthenticationFilter filter = (SocialAuthenticationFilter) super.postProcess(object);

filter.setFilterProcessesUrl(filterProcessesUrl);

if (socialAuthenticationFilterPostProcessor != null) {

socialAuthenticationFilterPostProcessor.process(filter);

}

return (T) filter;

}

public SocialAuthenticationFilterPostProcessor getSocialAuthenticationFilterPostProcessor() {

return socialAuthenticationFilterPostProcessor;

}

public void setSocialAuthenticationFilterPostProcessor(SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor) {

this.socialAuthenticationFilterPostProcessor = socialAuthenticationFilterPostProcessor;

}

}

SocialAuthenticationFilterPostProcessor

public interface SocialAuthenticationFilterPostProcessor {

void process(SocialAuthenticationFilter socialAuthenticationFilter);

}

SsbSocialAuthenticationFilterPostProcessor

@Component

public class SsbSocialAuthenticationFilterPostProcessor implements SocialAuthenticationFilterPostProcessor {

@Autowired

private AuthenticationSuccessHandler jsAuthenticationSuccessHandler;

// 后处理器

public void process(SocialAuthenticationFilter socialAuthenticationFilter) {

socialAuthenticationFilter.setAuthenticationSuccessHandler(jsAuthenticationSuccessHandler);

}

}

SocialRedisHelper

/**

* 将第三方用户信息保存到redis里面

*/

@Component

public class SocialRedisHelper {

@Autowired

private RedisTemplate redisTemplate;

@Autowired

private UsersConnectionRepository usersConnectionRepository;

@Autowired

private ConnectionFactoryLocator connectionFactoryLocator;

public void saveConnectionData(String mkey, ConnectionData connectionData) {

redisTemplate.opsForValue().set(getKey(mkey), connectionData, 10, TimeUnit.MINUTES);

}

public void saveStateUserId(String mkey, String userId) {

redisTemplate.opsForValue().set(getKey(mkey), userId, 10, TimeUnit.MINUTES);

}

public String getStateUserId(String mkey) {

String key = getKey(mkey);

if (!redisTemplate.hasKey(key)) {

throw new RuntimeException("无法找到缓存的第三方社交账号信息");

}

return (String) redisTemplate.opsForValue().get(key);

}

public void doPostSignUp(String mkey,String userId){

String key = getKey(mkey);

if (!redisTemplate.hasKey(key)){

throw new RuntimeException("无法找到缓存的第三方社交账号信息");

}

ConnectionData connectionData = (ConnectionData) redisTemplate.opsForValue().get(key);

Connection> connection = connectionFactoryLocator.getConnectionFactory(connectionData.getProviderId())

.createConnection(connectionData);

usersConnectionRepository.createConnectionRepository(userId).addConnection(connection);

redisTemplate.delete(key);

}

private String getKey(String mkey) {

if (StringUtils.isEmpty(mkey)) {

throw new RuntimeException("设置ID:mkey 不为空");

}

return "awbeci:security:social.connect." + mkey;

}

}

SsbAuthenticationSuccessHandler

@Component("ssbAuthenticationSuccessHandler")

public class SsbAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

@Autowired

private ObjectMapper objectMapper;

@Autowired

private ClientDetailsService clientDetailsService;

@Autowired

private AuthorizationServerTokenServices authorizationServerTokenServices;

/*

* (non-Javadoc)

*

* @see org.springframework.security.web.authentication.

* AuthenticationSuccessHandler#onAuthenticationSuccess(javax.servlet.http.

* HttpServletRequest, javax.servlet.http.HttpServletResponse,

* org.springframework.security.core.Authentication)

*/

@Override

public void onAuthenticationSuccess(HttpServletRequest request,

HttpServletResponse response,

Authentication authentication) throws IOException, ServletException {

String header = request.getHeader("Authorization");

String name = authentication.getName();

// String password = (String) authentication.getCredentials();

if (header == null || !header.startsWith("Basic ")) {

throw new UnapprovedClientAuthenticationException("请求头中无client信息");

}

String[] tokens = extractAndDecodeHeader(header, request);

assert tokens.length == 2;

String clientId = tokens[0];

String clientSecret = tokens[1];

ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);

if (clientDetails == null) {

throw new UnapprovedClientAuthenticationException("clientId对应的配置信息不存在:" + clientId);

} else if (!StringUtils.equals(clientDetails.getClientSecret(), clientSecret)) {

throw new UnapprovedClientAuthenticationException("clientSecret不匹配:" + clientId);

}

TokenRequest tokenRequest = new TokenRequest(MapUtils.EMPTY_MAP, clientId, clientDetails.getScope(), "custom");

OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);

OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);

OAuth2AccessToken token = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);

response.setContentType("application/json;charset=UTF-8");

response.getWriter().write(objectMapper.writeValueAsString(token));

}

private String[] extractAndDecodeHeader(String header, HttpServletRequest request) throws IOException {

byte[] base64Token = header.substring(6).getBytes("UTF-8");

byte[] decoded;

try {

decoded = Base64.decode(base64Token);

} catch (IllegalArgumentException e) {

throw new BadCredentialsException("Failed to decode basic authentication token");

}

String token = new String(decoded, "UTF-8");

int delim = token.indexOf(":");

if (delim == -1) {

throw new BadCredentialsException("Invalid basic authentication token");

}

return new String[] { token.substring(0, delim), token.substring(delim + 1) };

}

}

ApiUserDetailsService

@Component

public class ApiUserDetailsService implements UserDetailsService, SocialUserDetailsService {

private Logger logger = LoggerFactory.getLogger(getClass());

@Autowired

private PasswordEncoder passwordEncoder;

/*

* (non-Javadoc)

*

* @see org.springframework.security.core.userdetails.UserDetailsService#

* loadUserByUsername(java.lang.String)

*/

// 这里的username 可以是username、mobile、email

public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

logger.info("表单登录用户名:" + username);

return buildUser(username);

}

public SocialUserDetails loadUserByUserId(String userId) throws UsernameNotFoundException {

logger.info("设计登录用户Id:" + userId);

return buildUser(userId);

}

private SocialUser buildUser(String userId) {

// 根据用户名查找用户信息

//根据查找到的用户信息判断用户是否被冻结

String password = passwordEncoder.encode("123456");

logger.info("数据库密码是:" + password);

return new SocialUser(userId, password,

true, true, true, true,

AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_USER"));

}

}

获取社交账号数据并保存到redis里面待前端绑定时使用

@GetMapping("/social/signUp")

public void socialSignUp(HttpServletRequest request, HttpServletResponse response) throws IOException {

String uuid = UUID.randomUUID().toString();

SocialUserInfo userInfo = new SocialUserInfo();

Connection> connectionFromSession = providerSignInUtils.getConnectionFromSession(new ServletWebRequest(request));

userInfo.setHeadImg(connectionFromSession.getImageUrl());

userInfo.setNickname(connectionFromSession.getDisplayName());

userInfo.setProviderId(connectionFromSession.getKey().getProviderId());

userInfo.setProviderUserId(connectionFromSession.getKey().getProviderUserId());

socialRedisHelper.saveConnectionData(uuid, connectionFromSession.createData());

response.sendRedirect(bindUrl + "?mkey=" + uuid);

}

5、资源服务的配置

@Autowired

private SpringSocialConfigurer awbeciSocialSecurityConfig;

@Override

public void configure(HttpSecurity http) throws Exception {

http

.apply(awbeciSocialSecurityConfig)

.and()

.authorizeRequests()

.anyRequest()

.authenticated()

.and()

.csrf().disable();

}

6、application.properties

ssb.security.social.register-url=/social/signUp

ssb.security.social.filter-processes-url=/social-login

ssb.security.social.bind-url=https://website/social-bind/qq

ssb.security.social.callback-url=https://website/social-login

ssb.security.social.connect-url=https://website/social-connect

#QQ授权

ssb.security.social.qq.app-id=

ssb.security.social.qq.app-secret=

#provider-id是构造访问qq授权地址,如:localhost/auth/qq,如果是微信,localhost/auth/wechat

#provider-id和login-url地址组合成的url应该是你qq互联上面的网站回调域地址,如:/social-login/qq

ssb.security.social.qq.provider-id=qq

#https://www.xfindzp.com/social-bind/qq

ssb.security.social.qq.register-url=https://website/login/regist

#WeChat授权

ssb.security.social.wechat.app-id=

ssb.security.social.wechat.app-secret=

ssb.security.social.wechat.provider-id=wechat

7、测试

8、总结

1、其实还有一种情况就是有系统平台账户,但是没有跟社交账号建立联系,这时候你又没有登录进行绑定,所以当你点击授权的时候,后台可以判断该账号是否已经存在,如果存在就绑定,不存在就注册。

2、QQ的回调URL要加上具体的路径,如:http://website/one/two,但是微信的回调只要域名就好,如:http://website,这也是做的时候发现两者的不同

3、其实我感觉还有优化的空间,有时间我来优化一下代码

java项目关联Q登陆,前后端分离项目 — SpringSocial 社交账号登录与注册相关推荐

  1. 前后端分离项目如何部署_前后端分离项目,如何解决跨域问题?

    跨域资源共享(CORS)是前后端分离项目很常见的问题,本文主要介绍当SpringBoot应用整合SpringSecurity以后如何解决该问题. 01 什么是跨域问题? CORS全称Cross-Ori ...

  2. Vue第三部分(2):Vue-CLI构建前后端分离项目以及打包部署

    通过上一节,我们学习了vue脚手架的相关概念以及构建过程,那么开始我们的前后端项目吧 Vue-CLI构建前后端分离项目 1.项目环境初始化 1.安装 axios vue-axios(记得执行命令要进入 ...

  3. Java项目:仿小米商城系统(前后端分离+java+vue+Springboot+ssm+mysql+maven+redis)

    源码获取:博客首页 "资源" 里下载! 一.项目简述 本系统功能包括: 基于vue + Springboot前后端分离项目精简版仿小米商城 系统,注册登录,首页展示,商品展示,商品 ...

  4. java 同域名下怎么访问同事的项目_喜大普奔,两个开源的前后端分离项目可以在线体验了...

    折腾了一周的域名备案昨天终于搞定了. 松哥第一时间想到赶紧把微人事和V 部落上去,我知道很多小伙伴已经等不及了. 1. 也曾经上过线 其实这两个项目当时刚做好的时候,我就把它们部署到服务器上了,以帮助 ...

  5. Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)十六(商品排序,Thymeleaf快速入门,商品详情页的展示)

    Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)十六(商品详情页的展示) 一.商品排序 1.完善页面信息 这是用来做排序的,默认按照综合排序 ...

  6. 视频教程-SpringBoot实战教程:SpringBoot入门及前后端分离项目开发-Java

    SpringBoot实战教程:SpringBoot入门及前后端分离项目开发 十三,CSDN达人课课程作者,CSDN 博客作者,现就职于某网络科技公司任职高级 Java 开发工程师,13blog.sit ...

  7. 视频教程-Vue、Spring Boot开发小而完整的Web前后端分离项目实战-Java

    Vue.Spring Boot开发小而完整的Web前后端分离项目实战 3年多.net开发经验:5年的java后端开发经验,熟悉行.net,java流行技术,拥有多个.net,java web企业级应; ...

  8. Java精品项目源码前后端分离项目第17期基于遗传算法学校排课系统

    Java精品项目源码前后端分离项目第17期基于遗传算法学校排课系统 大家好,小辰今天给大家介绍一个基于遗传算法学校排课系统,演示视频文章末尾公众号(小辰哥的java)对号查询观看即可 文章目录 Jav ...

  9. Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)二十二(下单和微信支付)

    Java网络商城项目 SpringBoot+SpringCloud+Vue 网络商城(SSM前后端分离项目)二十(下单) 0.学习目标 会调用订单系统接口 实现订单结算功能 实现微信支付功能 1.订单 ...

最新文章

  1. SOA和微服务之间的区别(应用和数据的垂直拆分水平拆分)
  2. db2界面调用存储过程_第三章 操作系统用户界面
  3. flink网页端提交pr-修改文档报错
  4. linux exit 源码,Linux命令——exit、sulogin、rlogin
  5. Unity HDRP中的光照烘焙测试(Mixed Lighing )和间接光
  6. TCP 的有限状态机
  7. 单元测试中Assert类的用法
  8. java spring mvc api_SpringMVC实现REST API
  9. 同学大多数都是上的整个网站重点我的
  10. Python模块之uuid
  11. python统计汉字字数_Python 统计字数的思路详解
  12. Linux/Mac 配置安装scala
  13. sgu 196 Matrix Multiplication
  14. 小游戏策划案例精选_小游戏策划方案.docx
  15. Sailfish OS构建(1)
  16. 常见的图片处理软件你知道多少?分享几款免费的图片处理软件
  17. mysql快照数据_制作mysql数据快照
  18. 清洗outliers
  19. 深入探究802.11ac技术
  20. ElasticSearch入门一(索引CRD和文档的CRUD)

热门文章

  1. 大数据算法(课后答案总汇)
  2. Your branch and 'origin/master' have diverged, and have 1 and 1 different commits each, respectively
  3. 基于Unity3D开发的智能巡逻兵小游戏
  4. Windows自带输入法中英文切换失灵
  5. Validform验证控件使用
  6. #大三狗的日常总结与反思07#
  7. android高仿网易新闻上下拉刷新,仿网易新闻最新版的下拉刷新
  8. 学校网络问题和网址打不开解决
  9. Oracle 表分区的理解整理
  10. 三菱数据移位指令_三菱移位指令的应用