摘要
在做自己的產(chǎn)品時喊暖,為了更好的用戶體驗惫企,在登錄注冊這一環(huán)節(jié),我們現(xiàn)在越來越多的使用社交登錄(QQ陵叽、微信狞尔、FB等)。為了規(guī)范社交賬號登錄出現(xiàn)OAuth協(xié)議巩掺。
一偏序、OAuth
在我們使用一個第三方的APP(Client_App),點擊使用社交應用登錄時胖替,首先會離開Client_App調(diào)到社交應用研儒,在社交應用上會顯示一個授權(quán)界面,點擊確定授權(quán)后又會從社交應用調(diào)回到Client_App中并且登錄成功独令。這個流程是用戶最直接的感受端朵,那么在技術(shù)層面有做了哪些事。OAuth協(xié)議中的授權(quán)模式
- 授權(quán)碼模式(authorization code)
- 簡化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
二燃箭、SpringSecurity社交原理
- 1.我們需要配置跳轉(zhuǎn)到社交應用服務提供商的認證服務器url
- 2.在社交應用服務提供商提供的界面進行授權(quán)
- 3.社交應用服務提供商的認證服務器會生成授權(quán)碼并且返回Client(第三方應用)
- 4.Client(第三方應用)發(fā)送請求Token令牌的請求
- 5.社交應用服務提供商中的認證服務器會把生成的Token令牌返回給Client(第三方應用)
- 6.Client(第三方應用)攜帶Token令牌去社交服務提供商的資源服務器請求用戶信息冲呢,并且返回給Client(第三方應用)
- 7.通過用戶信息構(gòu)建Authentication,并放入SecurityContext
三招狸、SpringSecurity社交源碼
1敬拓、SocialAuthenticationFilter 過濾器
// 默認攔截的請求url
private static final String DEFAULT_FILTER_PROCESSES_URL = "/auth";
// 默認注冊url
private String signupUrl = "/signup";
// 嘗試認證用戶
private Authentication attemptAuthService(final SocialAuthenticationService<?> authService, final HttpServletRequest request, HttpServletResponse response)
throws SocialAuthenticationRedirectException, AuthenticationException {
final SocialAuthenticationToken token = authService.getAuthToken(request, response);
if (token == null) return null;
Assert.notNull(token.getConnection());
Authentication auth = getAuthentication();
if (auth == null || !auth.isAuthenticated()) {
return doAuthentication(authService, request, token);
} else {
addConnection(authService, request, token, auth);
return null;
}
}
2、OAuth2AuthenticationService
// 用來生成授權(quán)Token(SocialAuthenticationToken)
public SocialAuthenticationToken getAuthToken(HttpServletRequest request, HttpServletResponse response) throws SocialAuthenticationRedirectException {
// 請求中是否攜帶【授權(quán)碼】
String code = request.getParameter("code");
if (!StringUtils.hasText(code)) {
OAuth2Parameters params = new OAuth2Parameters();
params.setRedirectUri(buildReturnToUrl(request));
setScope(request, params);
params.add("state", generateState(connectionFactory, request));
addCustomParameters(params);
// 跳轉(zhuǎn)到授權(quán)界面
throw new SocialAuthenticationRedirectException(getConnectionFactory().getOAuthOperations().buildAuthenticateUrl(params));
} else if (StringUtils.hasText(code)) {
try {
// 回調(diào)地址
String returnToUrl = buildReturnToUrl(request);
// 獲得accessToken
AccessGrant accessGrant = getConnectionFactory().getOAuthOperations().exchangeForAccess(code, returnToUrl, null);
// TODO avoid API call if possible (auth using token would be fine)
Connection<S> connection = getConnectionFactory().createConnection(accessGrant);
return new SocialAuthenticationToken(connection, null);
} catch (RestClientException e) {
logger.debug("failed to exchange for access", e);
return null;
}
} else {
return null;
}
}
3裙戏、OAuth2Connection<A>
public OAuth2Connection(String providerId, String providerUserId, String accessToken, String refreshToken, Long expireTime,
OAuth2ServiceProvider<A> serviceProvider, ApiAdapter<A> apiAdapter) {
super(apiAdapter);
this.serviceProvider = serviceProvider;
initAccessTokens(accessToken, refreshToken, expireTime);
initApi();
initApiProxy();
initKey(providerId, providerUserId);
}
4乘凸、AbstractConnection<A>
private ServiceProviderConnectionValuesImpl setValues() {
ServiceProviderConnectionValuesImpl values = new ServiceProviderConnectionValuesImpl();
apiAdapter.setConnectionValues(getApi(), values);
valuesInitialized = true;
return values;
}
5、RavenQQApiAdapter
/**
* QQ返回數(shù)據(jù)配置
* 把QQ返回的數(shù)據(jù)設置給connect
*/
public class RavenQQApiAdapter implements ApiAdapter<IRavenQQService> {
@Override
public boolean test(IRavenQQService api) {
return true;
}
@Override
public void setConnectionValues(IRavenQQService api, ConnectionValues values) {
RavenQQUserInfo userInfo = api.fetchQQUserInfo();
values.setDisplayName(userInfo.getNickname());
values.setImageUrl(userInfo.getFigureurl_qq_1());
values.setProfileUrl(null);
values.setProviderUserId(userInfo.getOpenId());
}
@Override
public UserProfile fetchUserProfile(IRavenQQService api) {
return null;
}
@Override
public void updateStatus(IRavenQQService api, String message) {
}
}
6挽懦、SocialAuthenticationToken
// 構(gòu)建SocialAuthenticationToken翰意,標識為未認證
public SocialAuthenticationToken(final Connection<?> connection, Map<String, String> providerAccountData) {
super(null);
Assert.notNull(connection);
ConnectionData connectionData = connection.createData();
Assert.notNull(connectionData.getProviderId());
if (connectionData.getExpireTime() != null && connectionData.getExpireTime() < System.currentTimeMillis()) {
throw new IllegalArgumentException("connection.expireTime < currentTime");
}
this.providerId = connectionData.getProviderId();
this.connection = connection;
this.principle = null; //no principal yet
if (providerAccountData != null) {
this.providerAccountData = Collections.unmodifiableMap(new HashMap<String, String>(providerAccountData));
} else {
this.providerAccountData = Collections.emptyMap();
}
super.setAuthenticated(false);
}
7、ProviderManager選擇一個Provider執(zhí)行
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
AuthenticationException parentException = null;
Authentication result = null;
Authentication parentResult = null;
boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
catch (AccountStatusException e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
throw e;
}
catch (AuthenticationException e) {
lastException = e;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parentResult = parent.authenticate(authentication);
}
catch (ProviderNotFoundException e) {
// ignore as we will throw below if no other exception occurred prior to
// calling parent and the parent
// may throw ProviderNotFound even though a provider in the child already
// handled the request
}
catch (AuthenticationException e) {
lastException = parentException = e;
}
}
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
// If the parent AuthenticationManager was attempted and successful than it will publish an AuthenticationSuccessEvent
// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
if (parentResult == null) {
eventPublisher.publishAuthenticationSuccess(result);
}
return result;
}
// Parent was null, or didn't authenticate (or throw an exception).
if (lastException == null) {
lastException = new ProviderNotFoundException(messages.getMessage(
"ProviderManager.providerNotFound",
new Object[] { toTest.getName() },
"No AuthenticationProvider found for {0}"));
}
// If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent
// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
if (parentException == null) {
prepareException(lastException, authentication);
}
throw lastException;
}
8信柿、SocialAuthenticationProvider
// 認證方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(SocialAuthenticationToken.class, authentication, "unsupported authentication type");
Assert.isTrue(!authentication.isAuthenticated(), "already authenticated");
SocialAuthenticationToken authToken = (SocialAuthenticationToken) authentication;
String providerId = authToken.getProviderId();
Connection<?> connection = authToken.getConnection();
String userId = toUserId(connection);
if (userId == null) {
throw new BadCredentialsException("Unknown access token");
}
UserDetails userDetails = userDetailsService.loadUserByUserId(userId);
if (userDetails == null) {
throw new UsernameNotFoundException("Unknown connected account id");
}
return new SocialAuthenticationToken(connection, userDetails, authToken.getProviderAccountData(), getAuthorities(providerId, userDetails));
}
// 到數(shù)據(jù)庫中查找使用存在用戶 userId
protected String toUserId(Connection<?> connection) {
List<String> userIds = usersConnectionRepository.findUserIdsWithConnection(connection);
// only if a single userId is connected to this providerUserId
return (userIds.size() == 1) ? userIds.iterator().next() : null;
}
9冀偶、signup錯誤
- 沒有從數(shù)據(jù)庫中找到用戶的userId
-
可以定義一個注冊或綁定賬號的界面
10、UserConnection表中rank字段的問題
- 創(chuàng)建UserConnection表SQL語句
create table UserConnection (userId varchar(255) not null,
providerId varchar(255) not null,
providerUserId varchar(255),
rank int not null,
displayName varchar(255),
profileUrl varchar(512),
imageUrl varchar(512),
accessToken varchar(512) not null,
secret varchar(512),
refreshToken varchar(512),
expireTime bigint,
primary key (userId, providerId, providerUserId));
create unique index UserConnectionRank on UserConnection(userId, providerId, rank);
- 由于rank是MySQL的保留字段渔嚷,所以創(chuàng)建表失敗进鸠,把rank字段換成grade字段,表雖然創(chuàng)建成功形病,但是還是問題
create table UserConnection (userId varchar(255) not null,
providerId varchar(255) not null,
providerUserId varchar(255),
grade int not null,
displayName varchar(255),
profileUrl varchar(512),
imageUrl varchar(512),
accessToken varchar(512) not null,
secret varchar(512),
refreshToken varchar(512),
expireTime bigint,
primary key (userId, providerId, providerUserId));
create unique index UserConnectionRank on UserConnection(userId, providerId, grade);
- 在系統(tǒng)中默認操作UserConnection表的JdbcUsersConnectionRepository類中含有大量的rank字段客年,所有在授權(quán)操作中依然不會成功,所以只好自定義系統(tǒng)提供的關于UserConnection表操作的類JdbcUsersConnectionRepository和JdbcConnectionRepository
- RavenJdbcConnectionRepository
package com.raven.core.social.jdbc;
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionData;
import org.springframework.social.connect.ConnectionFactory;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionKey;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.connect.DuplicateConnectionException;
import org.springframework.social.connect.NoSuchConnectionException;
import org.springframework.social.connect.NotConnectedException;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
class RavenJdbcConnectionRepository implements ConnectionRepository {
private final String userId;
private final JdbcTemplate jdbcTemplate;
private final ConnectionFactoryLocator connectionFactoryLocator;
private final TextEncryptor textEncryptor;
private final String tablePrefix;
public RavenJdbcConnectionRepository(String userId, JdbcTemplate jdbcTemplate, ConnectionFactoryLocator connectionFactoryLocator, TextEncryptor textEncryptor, String tablePrefix) {
this.userId = userId;
this.jdbcTemplate = jdbcTemplate;
this.connectionFactoryLocator = connectionFactoryLocator;
this.textEncryptor = textEncryptor;
this.tablePrefix = tablePrefix;
}
public MultiValueMap<String, Connection<?>> findAllConnections() {
List<Connection<?>> resultList = jdbcTemplate.query(selectFromUserConnection() + " where userId = ? order by providerId, grade", connectionMapper, userId);
MultiValueMap<String, Connection<?>> connections = new LinkedMultiValueMap<String, Connection<?>>();
Set<String> registeredProviderIds = connectionFactoryLocator.registeredProviderIds();
for (String registeredProviderId : registeredProviderIds) {
connections.put(registeredProviderId, Collections.<Connection<?>>emptyList());
}
for (Connection<?> connection : resultList) {
String providerId = connection.getKey().getProviderId();
if (connections.get(providerId).size() == 0) {
connections.put(providerId, new LinkedList<Connection<?>>());
}
connections.add(providerId, connection);
}
return connections;
}
public List<Connection<?>> findConnections(String providerId) {
return jdbcTemplate.query(selectFromUserConnection() + " where userId = ? and providerId = ? order by grade", connectionMapper, userId, providerId);
}
@SuppressWarnings("unchecked")
public <A> List<Connection<A>> findConnections(Class<A> apiType) {
List<?> connections = findConnections(getProviderId(apiType));
return (List<Connection<A>>) connections;
}
public MultiValueMap<String, Connection<?>> findConnectionsToUsers(MultiValueMap<String, String> providerUsers) {
if (providerUsers == null || providerUsers.isEmpty()) {
throw new IllegalArgumentException("Unable to execute find: no providerUsers provided");
}
StringBuilder providerUsersCriteriaSql = new StringBuilder();
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("userId", userId);
for (Iterator<Entry<String, List<String>>> it = providerUsers.entrySet().iterator(); it.hasNext();) {
Entry<String, List<String>> entry = it.next();
String providerId = entry.getKey();
providerUsersCriteriaSql.append("providerId = :providerId_").append(providerId).append(" and providerUserId in (:providerUserIds_").append(providerId).append(")");
parameters.addValue("providerId_" + providerId, providerId);
parameters.addValue("providerUserIds_" + providerId, entry.getValue());
if (it.hasNext()) {
providerUsersCriteriaSql.append(" or " );
}
}
List<Connection<?>> resultList = new NamedParameterJdbcTemplate(jdbcTemplate).query(selectFromUserConnection() + " where userId = :userId and " + providerUsersCriteriaSql + " order by providerId, grade", parameters, connectionMapper);
MultiValueMap<String, Connection<?>> connectionsForUsers = new LinkedMultiValueMap<String, Connection<?>>();
for (Connection<?> connection : resultList) {
String providerId = connection.getKey().getProviderId();
List<String> userIds = providerUsers.get(providerId);
List<Connection<?>> connections = connectionsForUsers.get(providerId);
if (connections == null) {
connections = new ArrayList<Connection<?>>(userIds.size());
for (int i = 0; i < userIds.size(); i++) {
connections.add(null);
}
connectionsForUsers.put(providerId, connections);
}
String providerUserId = connection.getKey().getProviderUserId();
int connectionIndex = userIds.indexOf(providerUserId);
connections.set(connectionIndex, connection);
}
return connectionsForUsers;
}
public Connection<?> getConnection(ConnectionKey connectionKey) {
try {
return jdbcTemplate.queryForObject(selectFromUserConnection() + " where userId = ? and providerId = ? and providerUserId = ?", connectionMapper, userId, connectionKey.getProviderId(), connectionKey.getProviderUserId());
} catch (EmptyResultDataAccessException e) {
throw new NoSuchConnectionException(connectionKey);
}
}
@SuppressWarnings("unchecked")
public <A> Connection<A> getConnection(Class<A> apiType, String providerUserId) {
String providerId = getProviderId(apiType);
return (Connection<A>) getConnection(new ConnectionKey(providerId, providerUserId));
}
@SuppressWarnings("unchecked")
public <A> Connection<A> getPrimaryConnection(Class<A> apiType) {
String providerId = getProviderId(apiType);
Connection<A> connection = (Connection<A>) findPrimaryConnection(providerId);
if (connection == null) {
throw new NotConnectedException(providerId);
}
return connection;
}
@SuppressWarnings("unchecked")
public <A> Connection<A> findPrimaryConnection(Class<A> apiType) {
String providerId = getProviderId(apiType);
return (Connection<A>) findPrimaryConnection(providerId);
}
@Transactional
public void addConnection(Connection<?> connection) {
try {
ConnectionData data = connection.createData();
int grade = jdbcTemplate.queryForObject("select coalesce(max(grade) + 1, 1) as grade from " + tablePrefix + "UserConnection where userId = ? and providerId = ?", new Object[]{ userId, data.getProviderId() }, Integer.class);
jdbcTemplate.update("insert into " + tablePrefix + "UserConnection (userId, providerId, providerUserId, grade, displayName, profileUrl, imageUrl, accessToken, secret, refreshToken, expireTime) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
userId, data.getProviderId(), data.getProviderUserId(), grade, data.getDisplayName(), data.getProfileUrl(), data.getImageUrl(), encrypt(data.getAccessToken()), encrypt(data.getSecret()), encrypt(data.getRefreshToken()), data.getExpireTime());
} catch (DuplicateKeyException e) {
throw new DuplicateConnectionException(connection.getKey());
}
}
@Transactional
public void updateConnection(Connection<?> connection) {
ConnectionData data = connection.createData();
jdbcTemplate.update("update " + tablePrefix + "UserConnection set displayName = ?, profileUrl = ?, imageUrl = ?, accessToken = ?, secret = ?, refreshToken = ?, expireTime = ? where userId = ? and providerId = ? and providerUserId = ?",
data.getDisplayName(), data.getProfileUrl(), data.getImageUrl(), encrypt(data.getAccessToken()), encrypt(data.getSecret()), encrypt(data.getRefreshToken()), data.getExpireTime(), userId, data.getProviderId(), data.getProviderUserId());
}
@Transactional
public void removeConnections(String providerId) {
jdbcTemplate.update("delete from " + tablePrefix + "UserConnection where userId = ? and providerId = ?", userId, providerId);
}
@Transactional
public void removeConnection(ConnectionKey connectionKey) {
jdbcTemplate.update("delete from " + tablePrefix + "UserConnection where userId = ? and providerId = ? and providerUserId = ?", userId, connectionKey.getProviderId(), connectionKey.getProviderUserId());
}
// internal helpers
private String selectFromUserConnection() {
return "select userId, providerId, providerUserId, displayName, profileUrl, imageUrl, accessToken, secret, refreshToken, expireTime from " + tablePrefix + "UserConnection";
}
private Connection<?> findPrimaryConnection(String providerId) {
List<Connection<?>> connections = jdbcTemplate.query(selectFromUserConnection() + " where userId = ? and providerId = ? order by grade", connectionMapper, userId, providerId);
if (connections.size() > 0) {
return connections.get(0);
} else {
return null;
}
}
private final ServiceProviderConnectionMapper connectionMapper = new ServiceProviderConnectionMapper();
private final class ServiceProviderConnectionMapper implements RowMapper<Connection<?>> {
public Connection<?> mapRow(ResultSet rs, int rowNum) throws SQLException {
ConnectionData connectionData = mapConnectionData(rs);
ConnectionFactory<?> connectionFactory = connectionFactoryLocator.getConnectionFactory(connectionData.getProviderId());
return connectionFactory.createConnection(connectionData);
}
private ConnectionData mapConnectionData(ResultSet rs) throws SQLException {
return new ConnectionData(rs.getString("providerId"), rs.getString("providerUserId"), rs.getString("displayName"), rs.getString("profileUrl"), rs.getString("imageUrl"),
decrypt(rs.getString("accessToken")), decrypt(rs.getString("secret")), decrypt(rs.getString("refreshToken")), expireTime(rs.getLong("expireTime")));
}
private String decrypt(String encryptedText) {
return encryptedText != null ? textEncryptor.decrypt(encryptedText) : encryptedText;
}
private Long expireTime(long expireTime) {
return expireTime == 0 ? null : expireTime;
}
}
private <A> String getProviderId(Class<A> apiType) {
return connectionFactoryLocator.getConnectionFactory(apiType).getProviderId();
}
private String encrypt(String text) {
return text != null ? textEncryptor.encrypt(text) : text;
}
}
- RavenJdbcUsersConnectionRepository
package com.raven.core.social.jdbc;
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.sql.DataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionKey;
import org.springframework.social.connect.ConnectionRepository;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.social.connect.UsersConnectionRepository;
/**
* {@link UsersConnectionRepository} that uses the JDBC API to persist connection data to a relational database.
* The supporting schema is defined in JdbcUsersConnectionRepository.sql.
* @author Keith Donald
*/
public class RavenJdbcUsersConnectionRepository implements UsersConnectionRepository {
private final JdbcTemplate jdbcTemplate;
private final ConnectionFactoryLocator connectionFactoryLocator;
private final TextEncryptor textEncryptor;
private ConnectionSignUp connectionSignUp;
private String tablePrefix = "";
public RavenJdbcUsersConnectionRepository(DataSource dataSource, ConnectionFactoryLocator connectionFactoryLocator, TextEncryptor textEncryptor) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.connectionFactoryLocator = connectionFactoryLocator;
this.textEncryptor = textEncryptor;
}
/**
* The command to execute to create a new local user profile in the event no user id could be mapped to a connection.
* Allows for implicitly creating a user profile from connection data during a provider sign-in attempt.
* Defaults to null, indicating explicit sign-up will be required to complete the provider sign-in attempt.
* @param connectionSignUp a {@link ConnectionSignUp} object
* @see #findUserIdsWithConnection(Connection)
*/
public void setConnectionSignUp(ConnectionSignUp connectionSignUp) {
this.connectionSignUp = connectionSignUp;
}
/**
* Sets a table name prefix. This will be prefixed to all the table names before queries are executed. Defaults to "".
* This is can be used to qualify the table name with a schema or to distinguish Spring Social tables from other application tables.
* @param tablePrefix the tablePrefix to set
*/
public void setTablePrefix(String tablePrefix) {
this.tablePrefix = tablePrefix;
}
public List<String> findUserIdsWithConnection(Connection<?> connection) {
ConnectionKey key = connection.getKey();
List<String> localUserIds = jdbcTemplate.queryForList("select userId from " + tablePrefix + "UserConnection where providerId = ? and providerUserId = ?", String.class, key.getProviderId(), key.getProviderUserId());
if (localUserIds.size() == 0 && connectionSignUp != null) {
String newUserId = connectionSignUp.execute(connection);
if (newUserId != null)
{
createConnectionRepository(newUserId).addConnection(connection);
return Arrays.asList(newUserId);
}
}
return localUserIds;
}
public Set<String> findUserIdsConnectedTo(String providerId, Set<String> providerUserIds) {
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("providerId", providerId);
parameters.addValue("providerUserIds", providerUserIds);
final Set<String> localUserIds = new HashSet<String>();
return new NamedParameterJdbcTemplate(jdbcTemplate).query("select userId from " + tablePrefix + "UserConnection where providerId = :providerId and providerUserId in (:providerUserIds)", parameters,
new ResultSetExtractor<Set<String>>() {
public Set<String> extractData(ResultSet rs) throws SQLException, DataAccessException {
while (rs.next()) {
localUserIds.add(rs.getString("userId"));
}
return localUserIds;
}
});
}
public ConnectionRepository createConnectionRepository(String userId) {
if (userId == null) {
throw new IllegalArgumentException("userId cannot be null");
}
return new RavenJdbcConnectionRepository(userId, jdbcTemplate, connectionFactoryLocator, textEncryptor, tablePrefix);
}
}
四漠吻、QQ直接注冊賬號登錄
在上面的處理中如何使用QQ授權(quán)時第三方數(shù)據(jù)庫中沒有該用戶量瓜,解決方案是跳轉(zhuǎn)到界面讓用戶從新注冊用戶或者是把當前以后的用戶和QQ授權(quán)相關聯(lián)。這樣比較麻煩而且也不友好途乃,下面我們直接使用QQ賬號注冊一個第三方的新用戶绍傲,這樣直接就可以登錄成功。
- 在QQ登錄時首先會從UserConnection表中查找
public List<String> findUserIdsWithConnection(Connection<?> connection) {
ConnectionKey key = connection.getKey();
List<String> localUserIds = jdbcTemplate.queryForList("select userId from " + tablePrefix + "UserConnection where providerId = ? and providerUserId = ?", String.class, key.getProviderId(), key.getProviderUserId());
if (localUserIds.size() == 0 && connectionSignUp != null) {
String newUserId = connectionSignUp.execute(connection);
if (newUserId != null)
{
createConnectionRepository(newUserId).addConnection(connection);
return Arrays.asList(newUserId);
}
}
return localUserIds;
}
- ConnectionSignUp 可以為QQ創(chuàng)建新用戶
/**
* A command that signs up a new user in the event no user id could be mapped from a {@link Connection}.
* Allows for implicitly creating a local user profile from connection data during a provider sign-in attempt.
* @see UsersConnectionRepository#findUserIdsWithConnection(Connection)
* @author Keith Donald
*/
public interface ConnectionSignUp {
/**
* Sign up a new user of the application from the connection.
* @param connection the connection
* @return the new user id. May be null to indicate that an implicit local user profile could not be created.
*/
String execute(Connection<?> connection);
}
- DemoConnectionSignUp
@Component
public class DemoConnectionSignUp implements ConnectionSignUp {
@Override
public String execute(Connection<?> connection) {
/**
* TODO
* 根據(jù)connection信息創(chuàng)建第三方用戶
* 并且返回該用戶數(shù)據(jù)中的唯一id
* 把id返回耍共,用戶和QQ賬號關聯(lián)
*/
// 返回唯一標識
return connection.getDisplayName();
}
}
- 這樣就創(chuàng)建了一個新用戶并且已經(jīng)和QQ賬號相關聯(lián)