美文网首页SpringSecurity
八、社交登录-QQ登录

八、社交登录-QQ登录

作者: 紫荆秋雪_文 | 来源:发表于2020-05-07 21:46 被阅读0次

    摘要

    在做自己的产品时,为了更好的用户体验,在登录注册这一环节,我们现在越来越多的使用社交登录(QQ、微信、FB等)。为了规范社交账号登录出现OAuth协议。

    一、OAuth

    在我们使用一个第三方的APP(Client_App),点击使用社交应用登录时,首先会离开Client_App调到社交应用,在社交应用上会显示一个授权界面,点击确定授权后又会从社交应用调回到Client_App中并且登录成功。这个流程是用户最直接的感受,那么在技术层面有做了哪些事。 OAuth授权.png

    OAuth协议中的授权模式

    • 授权码模式(authorization code)
    • 简化模式(implicit)
    • 密码模式(resource owner password credentials)
    • 客户端模式(client credentials)

    二、SpringSecurity社交原理

    授权码模式流程.png
    社交应用授权流程.png
    • 1.我们需要配置跳转到社交应用服务提供商的认证服务器url
    • 2.在社交应用服务提供商提供的界面进行授权
    • 3.社交应用服务提供商的认证服务器会生成授权码并且返回Client(第三方应用)
    • 4.Client(第三方应用)发送请求Token令牌的请求
    • 5.社交应用服务提供商中的认证服务器会把生成的Token令牌返回给Client(第三方应用)
    • 6.Client(第三方应用)携带Token令牌去社交服务提供商的资源服务器请求用户信息,并且返回给Client(第三方应用)
    • 7.通过用户信息构建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

    // 用来生成授权Token(SocialAuthenticationToken)
    public SocialAuthenticationToken getAuthToken(HttpServletRequest request, HttpServletResponse response) throws SocialAuthenticationRedirectException {
            // 请求中是否携带【授权码】
            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);
                // 跳转到授权界面
                throw new SocialAuthenticationRedirectException(getConnectionFactory().getOAuthOperations().buildAuthenticateUrl(params));
            } else if (StringUtils.hasText(code)) {
                try {
                    // 回调地址
                    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返回数据配置
     * 把QQ返回的数据设置给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

    // 构建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执行

    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));
        }
    
        // 到数据库中查找使用存在用户 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错误

    • 没有从数据库中找到用户的userId
    • 可以定义一个注册或绑定账号的界面 signup.png

    10、UserConnection表中rank字段的问题

    • 创建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的保留字段,所以创建表失败,把rank字段换成grade字段,表虽然创建成功,但是还是问题
    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);
    
    • 在系统中默认操作UserConnection表的JdbcUsersConnectionRepository类中含有大量的rank字段,所有在授权操作中依然不会成功,所以只好自定义系统提供的关于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授权时第三方数据库中没有该用户,解决方案是跳转到界面让用户从新注册用户或者是把当前以后的用户和QQ授权相关联。这样比较麻烦而且也不友好,下面我们直接使用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创建新用户
    /**
     * 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
             * 根据connection信息创建第三方用户
             * 并且返回该用户数据中的唯一id
             * 把id返回,用户和QQ账号关联
             */
            // 返回唯一标识
            return connection.getDisplayName();
        }
    }
    
    • 这样就创建了一个新用户并且已经和QQ账号相关联

    相关文章

      网友评论

        本文标题:八、社交登录-QQ登录

        本文链接:https://www.haomeiwen.com/subject/hukoghtx.html