在上一篇文章中 ,我谈到了我面临的第一个挑战是更改数据模型并添加连接框架。 在这里,我想提供有关我如何做的更多详细信息。 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 cardinalitySocialAuthenticationService<?> 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 userIdthrow 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 helpersprivate 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 {@Injectprivate Environment environment;@InjectAccountService accountService;@Injectprivate AuthenticationManager authenticationManager;@Injectprivate UserSocialConnectionRepository userSocialConnectionRepository;@Beanpublic SocialAuthenticationServiceLocator socialAuthenticationServiceLocator() {SocialAuthenticationServiceRegistry registry = new SocialAuthenticationServiceRegistry();//add googleOAuth2ConnectionFactory<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 twitterOAuth1ConnectionFactory<Twitter> twitterConnectionFactory = new TwitterConnectionFactory(environment.getProperty('twitter.consumerKey'),environment.getProperty('twitter.consumerSecret'));OAuth1AuthenticationService<Twitter> twitterAuthenticationService = new OAuth1AuthenticationService<Twitter>(twitterConnectionFactory);registry.addAuthenticationService(twitterAuthenticationService);//add facebookOAuth2ConnectionFactory<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.*/@Beanpublic 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();}@Beanpublic ConnectionSignUp autoConnectionSignUp() {return new AutoConnectionSignUp(accountService);}@Beanpublic SocialAuthenticationFilter socialAuthenticationFilter() {SocialAuthenticationFilter filter = new SocialAuthenticationFilter(authenticationManager, accountService,usersConnectionRepository(), socialAuthenticationServiceLocator());filter.setFilterProcessesUrl('/signin');filter.setSignupUrl(null); filter.setConnectionAddedRedirectUrl('/myAccount');filter.setPostLoginUrl('/myAccount');return filter;}@Beanpublic SocialAuthenticationProvider socialAuthenticationProvider(){return new SocialAuthenticationProvider(usersConnectionRepository(), accountService);}@Beanpublic LoginUrlAuthenticationEntryPoint socialAuthenticationEntryPoint(){return new LoginUrlAuthenticationEntryPoint('/signin');}}
accountService
是我自己的用户帐户服务,提供与帐户相关的功能,它实现了SocialUserDetailsService
, UserDetailsService
, UserIdExtractor
。
还有很多地方需要改进,例如重构MongoConnectionRepository
和MongoUsersConnectionRepository
以使用Spring Data Repository接口来实现抽象的社交连接存储库实现。 而且我发现有人已经对此提出了一个问题: 为UsersConnectionRepository利用Spring数据 。
参考:来自我们的JCG合作伙伴 Yuan Ji在Jiwhiz博客上为MongoDB定制Spring Social Connect Framework 。
翻译自: https://www.javacodegeeks.com/2013/03/customize-spring-social-connect-framework-for-mongodb.html