在上一篇文章中 ,我谈到了我面临的第一个挑战是更改数据模型并添加连接框架。 在这里,我想提供有关我如何做的更多细节。 Spring Social项目已经提供了基于jdbc的连接存储库实现,以将用户连接数据持久保存到关系数据库中。 但是,我使用的是MongoDB,因此我需要自定义代码,并且发现这样做相对容易。 用户连接数据将保存为UserSocialConnection
的对象,它是一个MongoDB文档:
@SuppressWarnings('serial')
@Document(collection = 'UserSocialConnection')
public class UserSocialConnection extends BaseEntity {
private String userId;
private String providerId;
private String providerUserId;
private String displayName;
private String profileUrl;
private String imageUrl;
private String accessToken;
private String secret;
private String refreshToken;
private Long expireTime;
//Getter/Setter omitted.
public UserSocialConnection() {
super();
}
public UserSocialConnection(String userId, String providerId, String providerUserId, int rank,
String displayName, String profileUrl, String imageUrl, String accessToken, String secret,
String refreshToken, Long expireTime) {
super();
this.userId = userId;
this.providerId = providerId;
this.providerUserId = providerUserId;
this.displayName = displayName;
this.profileUrl = profileUrl;
this.imageUrl = imageUrl;
this.accessToken = accessToken;
this.secret = secret;
this.refreshToken = refreshToken;
this.expireTime = expireTime;
}
}
BaseEntity
仅具有“ id”。 在Spring Data项目的帮助下,我不需要为UserSocialConnection
编写任何CRUD操作代码,只需扩展MongoRepository
:
public interface UserSocialConnectionRepository extends MongoRepository<UserSocialConnection, String>{
List<UserSocialConnection> findByUserId(String userId);
List<UserSocialConnection> findByUserIdAndProviderId(String userId, String providerId);
List<UserSocialConnection> findByProviderIdAndProviderUserId(String providerId, String providerUserId);
UserSocialConnection findByUserIdAndProviderIdAndProviderUserId(String userId, String providerId, String providerUserId);
List<UserSocialConnection> findByProviderIdAndProviderUserIdIn(String providerId, Collection<String> providerUserIds);
}
在拥有数据库UserSocialConnectionRepository
,我们将实现Spring Social所需的ConnectionRepository
和UsersConnectionRepository
。 我只是从JdbcConnectionRepository
和JdbcUsersConnectionRepository
复制了代码,并创建了自己的MongoConnectionRepository
和MongoUsersConnectionRepository
。
public class MongoUsersConnectionRepository implements UsersConnectionRepository{
private final UserSocialConnectionRepository userSocialConnectionRepository;
private final SocialAuthenticationServiceLocator socialAuthenticationServiceLocator;
private final TextEncryptor textEncryptor;
private ConnectionSignUp connectionSignUp;
public MongoUsersConnectionRepository(UserSocialConnectionRepository userSocialConnectionRepository,
SocialAuthenticationServiceLocator socialAuthenticationServiceLocator, TextEncryptor textEncryptor){
this.userSocialConnectionRepository = userSocialConnectionRepository;
this.socialAuthenticationServiceLocator = socialAuthenticationServiceLocator;
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.
* @see #findUserIdsWithConnection(Connection)
*/
public void setConnectionSignUp(ConnectionSignUp connectionSignUp) {
this.connectionSignUp = connectionSignUp;
}
public List<String> findUserIdsWithConnection(Connection<?> connection) {
ConnectionKey key = connection.getKey();
List<UserSocialConnection> userSocialConnectionList =
this.userSocialConnectionRepository.findByProviderIdAndProviderUserId(key.getProviderId(), key.getProviderUserId());
List<String> localUserIds = new ArrayList<String>();
for (UserSocialConnection userSocialConnection : userSocialConnectionList){
localUserIds.add(userSocialConnection.getUserId());
}
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) {
final Set<String> localUserIds = new HashSet<String>();
List<UserSocialConnection> userSocialConnectionList =
this.userSocialConnectionRepository.findByProviderIdAndProviderUserIdIn(providerId, providerUserIds);
for (UserSocialConnection userSocialConnection : userSocialConnectionList){
localUserIds.add(userSocialConnection.getUserId());
}
return localUserIds;
}
public ConnectionRepository createConnectionRepository(String userId) {
if (userId == null) {
throw new IllegalArgumentException('userId cannot be null');
}
return new MongoConnectionRepository(userId, userSocialConnectionRepository, socialAuthenticationServiceLocator, textEncryptor);
}
}
MongoUsersConnectionRepository
非常类似于JdbcUsersConnectionRepository
。 但是对于MongoConnectionRepository
,我需要进行一些更改:
public class MongoConnectionRepository implements ConnectionRepository {
private final String userId;
private final UserSocialConnectionRepository userSocialConnectionRepository;
private final SocialAuthenticationServiceLocator socialAuthenticationServiceLocator;
private final TextEncryptor textEncryptor;
public MongoConnectionRepository(String userId, UserSocialConnectionRepository userSocialConnectionRepository,
SocialAuthenticationServiceLocator socialAuthenticationServiceLocator, TextEncryptor textEncryptor) {
this.userId = userId;
this.userSocialConnectionRepository = userSocialConnectionRepository;
this.socialAuthenticationServiceLocator = socialAuthenticationServiceLocator;
this.textEncryptor = textEncryptor;
}
public MultiValueMap<String, Connection<?>> findAllConnections() {
List<UserSocialConnection> userSocialConnectionList = this.userSocialConnectionRepository
.findByUserId(userId);
MultiValueMap<String, Connection<?>> connections = new LinkedMultiValueMap<String, Connection<?>>();
Set<String> registeredProviderIds = socialAuthenticationServiceLocator.registeredProviderIds();
for (String registeredProviderId : registeredProviderIds) {
connections.put(registeredProviderId, Collections.<Connection<?>> emptyList());
}
for (UserSocialConnection userSocialConnection : userSocialConnectionList) {
String providerId = userSocialConnection.getProviderId();
if (connections.get(providerId).size() == 0) {
connections.put(providerId, new LinkedList<Connection<?>>());
}
connections.add(providerId, buildConnection(userSocialConnection));
}
return connections;
}
public List<Connection<?>> findConnections(String providerId) {
List<Connection<?>> resultList = new LinkedList<Connection<?>>();
List<UserSocialConnection> userSocialConnectionList = this.userSocialConnectionRepository
.findByUserIdAndProviderId(userId, providerId);
for (UserSocialConnection userSocialConnection : userSocialConnectionList) {
resultList.add(buildConnection(userSocialConnection));
}
return resultList;
}
@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');
}
MultiValueMap<String, Connection<?>> connectionsForUsers = new LinkedMultiValueMap<String, Connection<?>>();
for (Iterator<Entry<String, List<String>>> it = providerUsers.entrySet().iterator(); it.hasNext();) {
Entry<String, List<String>> entry = it.next();
String providerId = entry.getKey();
List<String> providerUserIds = entry.getValue();
List<UserSocialConnection> userSocialConnections =
this.userSocialConnectionRepository.findByProviderIdAndProviderUserIdIn(providerId, providerUserIds);
List<Connection<?>> connections = new ArrayList<Connection<?>>(providerUserIds.size());
for (int i = 0; i < providerUserIds.size(); i++) {
connections.add(null);
}
connectionsForUsers.put(providerId, connections);
for (UserSocialConnection userSocialConnection : userSocialConnections) {
String providerUserId = userSocialConnection.getProviderUserId();
int connectionIndex = providerUserIds.indexOf(providerUserId);
connections.set(connectionIndex, buildConnection(userSocialConnection));
}
}
return connectionsForUsers;
}
public Connection<?> getConnection(ConnectionKey connectionKey) {
UserSocialConnection userSocialConnection = this.userSocialConnectionRepository
.findByUserIdAndProviderIdAndProviderUserId(userId, connectionKey.getProviderId(),
connectionKey.getProviderUserId());
if (userSocialConnection != null) {
return buildConnection(userSocialConnection);
}
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);
}
public void addConnection(Connection<?> connection) {
//check cardinality
SocialAuthenticationService<?> socialAuthenticationService =
this.socialAuthenticationServiceLocator.getAuthenticationService(connection.getKey().getProviderId());
if (socialAuthenticationService.getConnectionCardinality() == ConnectionCardinality.ONE_TO_ONE ||
socialAuthenticationService.getConnectionCardinality() == ConnectionCardinality.ONE_TO_MANY){
List<UserSocialConnection> storedConnections =
this.userSocialConnectionRepository.findByProviderIdAndProviderUserId(
connection.getKey().getProviderId(), connection.getKey().getProviderUserId());
if (storedConnections.size() > 0){
//not allow one providerId connect to multiple userId
throw new DuplicateConnectionException(connection.getKey());
}
}
UserSocialConnection userSocialConnection = this.userSocialConnectionRepository
.findByUserIdAndProviderIdAndProviderUserId(userId, connection.getKey().getProviderId(),
connection.getKey().getProviderUserId());
if (userSocialConnection == null) {
ConnectionData data = connection.createData();
userSocialConnection = new UserSocialConnection(userId, data.getProviderId(), data.getProviderUserId(), 0,
data.getDisplayName(), data.getProfileUrl(), data.getImageUrl(), encrypt(data.getAccessToken()),
encrypt(data.getSecret()), encrypt(data.getRefreshToken()), data.getExpireTime());
this.userSocialConnectionRepository.save(userSocialConnection);
} else {
throw new DuplicateConnectionException(connection.getKey());
}
}
public void updateConnection(Connection<?> connection) {
ConnectionData data = connection.createData();
UserSocialConnection userSocialConnection = this.userSocialConnectionRepository
.findByUserIdAndProviderIdAndProviderUserId(userId, connection.getKey().getProviderId(), connection
.getKey().getProviderUserId());
if (userSocialConnection != null) {
userSocialConnection.setDisplayName(data.getDisplayName());
userSocialConnection.setProfileUrl(data.getProfileUrl());
userSocialConnection.setImageUrl(data.getImageUrl());
userSocialConnection.setAccessToken(encrypt(data.getAccessToken()));
userSocialConnection.setSecret(encrypt(data.getSecret()));
userSocialConnection.setRefreshToken(encrypt(data.getRefreshToken()));
userSocialConnection.setExpireTime(data.getExpireTime());
this.userSocialConnectionRepository.save(userSocialConnection);
}
}
public void removeConnections(String providerId) {
List<UserSocialConnection> userSocialConnectionList = this.userSocialConnectionRepository
.findByUserIdAndProviderId(userId, providerId);
for (UserSocialConnection userSocialConnection : userSocialConnectionList) {
this.userSocialConnectionRepository.delete(userSocialConnection);
}
}
public void removeConnection(ConnectionKey connectionKey) {
UserSocialConnection userSocialConnection = this.userSocialConnectionRepository
.findByUserIdAndProviderIdAndProviderUserId(userId, connectionKey.getProviderId(), connectionKey.getProviderUserId());
this.userSocialConnectionRepository.delete(userSocialConnection);
}
// internal helpers
private Connection<?> buildConnection(UserSocialConnection userSocialConnection) {
ConnectionData connectionData = new ConnectionData(userSocialConnection.getProviderId(),
userSocialConnection.getProviderUserId(), userSocialConnection.getDisplayName(),
userSocialConnection.getProfileUrl(), userSocialConnection.getImageUrl(),
decrypt(userSocialConnection.getAccessToken()), decrypt(userSocialConnection.getSecret()),
decrypt(userSocialConnection.getRefreshToken()), userSocialConnection.getExpireTime());
ConnectionFactory<?> connectionFactory = this.socialAuthenticationServiceLocator.getConnectionFactory(connectionData
.getProviderId());
return connectionFactory.createConnection(connectionData);
}
private Connection<?> findPrimaryConnection(String providerId) {
List<UserSocialConnection> userSocialConnectionList = this.userSocialConnectionRepository
.findByUserIdAndProviderId(userId, providerId);
return buildConnection(userSocialConnectionList.get(0));
}
private <A> String getProviderId(Class<A> apiType) {
return socialAuthenticationServiceLocator.getConnectionFactory(apiType).getProviderId();
}
private String encrypt(String text) {
return text != null ? textEncryptor.encrypt(text) : text;
}
private String decrypt(String encryptedText) {
return encryptedText != null ? textEncryptor.decrypt(encryptedText) : encryptedText;
}
}
首先,我将JdbcTemplate
替换为UserSocialConnectionRepository
以从数据库中检索UserSocialConnection对象。 然后用spring-social-security模块中的SocialAuthenticationServiceLocator
替换ConnectionFactoryLocator
。 最大的变化是addConnection
方法(上面已突出显示),它首先检查连接基数。 如果connectionCardinality
的socialAuthenticationService
是ONE_TO_ONE
(这意味着一个用户id与一个且仅一个对providerId / providerUserId的),或ONE_TO_MANY
(这意味着一个用户id可以连接到一个或多个providerId / providerUserId,但一对providerId / providerUserId的只能连接到一个userId)。
完成所有这些自定义之后,最后一步是在spring config中将它们粘合在一起:
@Configuration
public class SocialAndSecurityConfig {
@Inject
private Environment environment;
@Inject
AccountService accountService;
@Inject
private AuthenticationManager authenticationManager;
@Inject
private UserSocialConnectionRepository userSocialConnectionRepository;
@Bean
public SocialAuthenticationServiceLocator socialAuthenticationServiceLocator() {
SocialAuthenticationServiceRegistry registry = new SocialAuthenticationServiceRegistry();
//add google
OAuth2ConnectionFactory<Google> googleConnectionFactory = new GoogleConnectionFactory(environment.getProperty('google.clientId'),
environment.getProperty('google.clientSecret'));
OAuth2AuthenticationService<Google> googleAuthenticationService = new OAuth2AuthenticationService<Google>(googleConnectionFactory);
googleAuthenticationService.setScope('https://www.googleapis.com/auth/userinfo.profile');
registry.addAuthenticationService(googleAuthenticationService);
//add twitter
OAuth1ConnectionFactory<Twitter> twitterConnectionFactory = new TwitterConnectionFactory(environment.getProperty('twitter.consumerKey'),
environment.getProperty('twitter.consumerSecret'));
OAuth1AuthenticationService<Twitter> twitterAuthenticationService = new OAuth1AuthenticationService<Twitter>(twitterConnectionFactory);
registry.addAuthenticationService(twitterAuthenticationService);
//add facebook
OAuth2ConnectionFactory<Facebook> facebookConnectionFactory = new FacebookConnectionFactory(environment.getProperty('facebook.clientId'),
environment.getProperty('facebook.clientSecret'));
OAuth2AuthenticationService<Facebook> facebookAuthenticationService = new OAuth2AuthenticationService<Facebook>(facebookConnectionFactory);
facebookAuthenticationService.setScope('');
registry.addAuthenticationService(facebookAuthenticationService);
return registry;
}
/**
* Singleton data access object providing access to connections across all users.
*/
@Bean
public UsersConnectionRepository usersConnectionRepository() {
MongoUsersConnectionRepository repository = new MongoUsersConnectionRepository(userSocialConnectionRepository,
socialAuthenticationServiceLocator(), Encryptors.noOpText());
repository.setConnectionSignUp(autoConnectionSignUp());
return repository;
}
/**
* Request-scoped data access object providing access to the current user's connections.
*/
@Bean
@Scope(value = 'request', proxyMode = ScopedProxyMode.INTERFACES)
public ConnectionRepository connectionRepository() {
UserAccount user = AccountUtils.getLoginUserAccount();
return usersConnectionRepository().createConnectionRepository(user.getUsername());
}
/**
* A proxy to a request-scoped object representing the current user's primary Google account.
*
* @throws NotConnectedException
* if the user is not connected to Google.
*/
@Bean
@Scope(value = 'request', proxyMode = ScopedProxyMode.INTERFACES)
public Google google() {
Connection<Google> google = connectionRepository().findPrimaryConnection(Google.class);
return google != null ? google.getApi() : new GoogleTemplate();
}
@Bean
@Scope(value='request', proxyMode=ScopedProxyMode.INTERFACES)
public Facebook facebook() {
Connection<Facebook> facebook = connectionRepository().findPrimaryConnection(Facebook.class);
return facebook != null ? facebook.getApi() : new FacebookTemplate();
}
@Bean
@Scope(value='request', proxyMode=ScopedProxyMode.INTERFACES)
public Twitter twitter() {
Connection<Twitter> twitter = connectionRepository().findPrimaryConnection(Twitter.class);
return twitter != null ? twitter.getApi() : new TwitterTemplate();
}
@Bean
public ConnectionSignUp autoConnectionSignUp() {
return new AutoConnectionSignUp(accountService);
}
@Bean
public SocialAuthenticationFilter socialAuthenticationFilter() {
SocialAuthenticationFilter filter = new SocialAuthenticationFilter(authenticationManager, accountService,
usersConnectionRepository(), socialAuthenticationServiceLocator());
filter.setFilterProcessesUrl('/signin');
filter.setSignupUrl(null);
filter.setConnectionAddedRedirectUrl('/myAccount');
filter.setPostLoginUrl('/myAccount');
return filter;
}
@Bean
public SocialAuthenticationProvider socialAuthenticationProvider(){
return new SocialAuthenticationProvider(usersConnectionRepository(), accountService);
}
@Bean
public LoginUrlAuthenticationEntryPoint socialAuthenticationEntryPoint(){
return new LoginUrlAuthenticationEntryPoint('/signin');
}
}
accountService
是我自己的用户帐户服务,用于提供与帐户相关的功能,它实现了SocialUserDetailsService
, UserDetailsService
, UserIdExtractor
。
还有很多地方需要改进,例如重构MongoConnectionRepository
和MongoUsersConnectionRepository
以使用Spring Data Repository接口实现抽象的社交连接存储库实现。 而且我发现有人已经对此提出了一个问题: 利用Spring Data for UsersConnectionRepository 。
参考:来自我们的JCG合作伙伴 Yuan Ji在Jiwhiz博客上为MongoDB定制Spring Social Connect Framework 。
翻译自: https://www.javacodegeeks.com/2013/03/customize-spring-social-connect-framework-for-mongodb.html