今天咱們就來說解下最後一篇如何使用SpringSocial來處理相似微信、QQ社交帳號登陸本身的平臺,也就是你們說的第三方登陸,獲取社交帳戶所在平臺的用戶信息,與本身平臺信息作個綁定的操做,兩個系統之間是經過UserId交換信息的,這點必定要注意,平臺用戶表(Users)之間是社交用戶表(UserConnection)之間關係以下所示:html
id | name | age |
---|---|---|
1 | David | 18 |
2 | Sam | 22 |
3 | Tom | 30 |
userId | providerId | providerUserId | accessToken | secret |
---|---|---|---|---|
1 | xxxxxxxxx | |||
2 | xxxxxxxxx | |||
3 | sian | xxxxxxxxx |
這裏我要提一下,在你沒有登陸你本身的平臺系統以前你點擊微信登陸的時候要進行判斷是否已經綁定,判斷的依據是經過providerId和providerUserId從UserConnection表裏面查詢出社交帳戶信息,而後經過對應的userId再經過SocialUserDetailsService提供的loadUserByUserId方法查詢出系統平臺的User用戶信息。
一、若是已經綁定(說明上面查詢到了)就直接生成token並返回給前端;
二、若是沒有(說明上面沒有查詢到)就須要註冊並綁定了(往兩張表中插入數據並創建關係)。前端
首先咱們來看看QQ的實現,我把代碼都貼出來,遇到關鍵的地方我會講解一下,其它的本身看看就明白了java
上面是具體的代碼目錄結構,你不必和我如出一轍,只要能實現就好。web
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; } }
public interface QQ { QQUserInfo getUserInfo(); }
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); } } }
@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); } }
/** * 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(); }
public class QQAdapter implements ApiAdapter<QQ> { 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) { } }
public class QQConnectionFactory extends OAuth2ConnectionFactory<QQ> { public QQConnectionFactory(String providerId, String appId, String appSecret) { super(providerId, new QQServiceProvider(appId, appSecret), new QQAdapter()); } }
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<String, String> 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; } }
public class QQServiceProvider extends AbstractOAuth2ServiceProvider<QQ> { //獲取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); } }
public interface Wechat { WechatUserInfo getUserInfo(String openId); }
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<HttpMessageConverter<?>> getMessageConverters() { List<HttpMessageConverter<?>> 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; } }
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、4六、6四、9六、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; } }
@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); } }
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; } }
public class WechatAdapter implements ApiAdapter<Wechat> { 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 } }
public class WechatConnectionFactory extends OAuth2ConnectionFactory<Wechat> { /** * @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<Wechat> createConnection(AccessGrant accessGrant) { return new OAuth2Connection<Wechat>(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<Wechat> createConnection(ConnectionData data) { return new OAuth2Connection<Wechat>(data, getOAuth2ServiceProvider(), getApiAdapter(data.getProviderUserId())); } private ApiAdapter<Wechat> getApiAdapter(String providerUserId) { return new WechatAdapter(providerUserId); } private OAuth2ServiceProvider<Wechat> getOAuth2ServiceProvider() { return (OAuth2ServiceProvider<Wechat>) getServiceProvider(); } }
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<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); logger.info("----------- accessTokenRequestUrl = {} ------------",accessTokenRequestUrl); 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); } 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; } }
public class WechatServiceProvider extends AbstractOAuth2ServiceProvider<Wechat> { /** * 微信獲取受權碼的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); } }
@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); //一、認證失敗跳轉註冊頁面 // 跳轉到signUp controller,從session中獲取用戶信息並經過生成的uuid保存到redis裏面,而後跳轉bind頁面 // 前端綁定後發送用戶信息到後臺bind controller,1)保存到本身系統用戶;2)保存一份userconnection表數據,Spring Social經過這裏面表數據進行判斷是否綁定 configurer.signupUrl(registerUrl); //二、認證成功跳轉後處理器,跳轉帶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(); } }
public class SsbSpringSocialConfigurer extends SpringSocialConfigurer { private SocialAuthenticationFilterPostProcessor socialAuthenticationFilterPostProcessor; // 設置自定義url private String filterProcessesUrl; public SsbSpringSocialConfigurer(String filterProcessesUrl) { this.filterProcessesUrl = filterProcessesUrl; } /** * 重寫qq登陸url * * @param object * @param <T> * @return */ @Override protected <T> 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; } }
public interface SocialAuthenticationFilterPostProcessor { void process(SocialAuthenticationFilter socialAuthenticationFilter); }
@Component public class SsbSocialAuthenticationFilterPostProcessor implements SocialAuthenticationFilterPostProcessor { @Autowired private AuthenticationSuccessHandler jsAuthenticationSuccessHandler; // 後處理器 public void process(SocialAuthenticationFilter socialAuthenticationFilter) { socialAuthenticationFilter.setAuthenticationSuccessHandler(jsAuthenticationSuccessHandler); } }
/** * 將第三方用戶信息保存到redis裏面 */ @Component public class SocialRedisHelper { @Autowired private RedisTemplate<Object, Object> 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; } }
@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) }; } }
@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")); } }
@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); }
@Autowired private SpringSocialConfigurer awbeciSocialSecurityConfig; @Override public void configure(HttpSecurity http) throws Exception { http .apply(awbeciSocialSecurityConfig) .and() .authorizeRequests() .anyRequest() .authenticated() .and() .csrf().disable(); }
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
一、其實還有一種狀況就是有系統平臺帳戶,可是沒有跟社交帳號創建聯繫,這時候你又沒有登陸進行綁定,因此當你點擊受權的時候,後臺能夠判斷該帳號是否已經存在,若是存在就綁定,不存在就註冊。
二、QQ的回調URL要加上具體的路徑,如:http://website/one/two,可是微信的回調只要域名就好,如:http://website,這也是作的時候發現二者的不一樣
三、其實我感受還有優化的空間,有時間我來優化一下代碼redis