springsocial/oauth2---第三方登錄之QQ登錄8【註冊邏輯之免註冊+springsocial源碼解讀④】


項目源碼地址 https://github.com/nieandsun/security

1 概述

正如上文所訴,現實中,咱們確定碰到過不少網站或APP,雖然以前咱們從未訪問過這些網站,可是當咱們用QQ或微信等進行登錄時,卻能夠直接登錄成功,根本不會被提醒讓咱們再註冊一個新的用戶。其實我以爲這樣體驗好像反而更好一些☺。java

在springsocial/springsecurity的邏輯裏,這要如何去實現呢?咱們必須仍是要從springsocial/springsecurity的源碼裏去探尋答案。git


2 從源碼中探尋跳過註冊邏輯的實現方法

在《springsocial/oauth2—第三方登錄之QQ登錄6【註冊邏輯之/signup錯誤解決+springsocial源碼解讀②】》那篇文章裏咱們講到了SocialAuthenticationProvider中的authenticate方法,其部分源碼以下:github

Connection<?> connection = authToken.getConnection();
		//拿着Connection對象去userconnection表裏去查相應的userId,因爲咱們的系統裏暫時尚未一個用戶,
		//因此查到的userId確定爲null----》爲null就會報出BadCredentialsException---》而後就會回到上面的類
		//讓你跳轉到一個XXX/signup的url----》其實說白了就是引導用戶進入註冊用戶的頁面(這一塊須要咱們本身實現) 
		String userId = toUserId(connection);
		if (userId == null) {
			throw new BadCredentialsException("Unknown access token");
		}

當時文章只是說因爲咱們的系統裏尚未一個用戶,因此查到的userId確定爲null,實際上深刻該代碼深處,能夠看到其邏輯不止這麼簡單,而本篇文章標題所說的免註冊的解決方法也正是隱藏於此。web

  • 先看一下toUserId的具體代碼:
protected String toUserId(Connection<?> connection) {
	//拿着Connection對象去找userIds
	List<String> userIds = usersConnectionRepository.findUserIdsWithConnection(connection);
	// only if a single userId is connected to this providerUserId
	return (userIds.size() == 1) ? userIds.iterator().next() : null;
}
  • 再看一下findUserIdsWithConnection的具體代碼(代碼所在類JdbcUsersConnectionRepository):
public List<String> findUserIdsWithConnection(Connection<?> connection) {
		ConnectionKey key = connection.getKey();
		//經過providerId和providerUserId(openId)去userconnection表裏查找useIds
		List<String> localUserIds = jdbcTemplate.queryForList("select userId from " + tablePrefix + "UserConnection where providerId = ? and providerUserId = ?", String.class, key.getProviderId(), key.getProviderUserId());	
		//若是沒找到useId(沒關聯過QQ確定找不到userId)且connectionSignUp不爲null 
		if (localUserIds.size() == 0 && connectionSignUp != null) {
			//調用connectionSignUp的execute方法生成一個useId
			String newUserId = connectionSignUp.execute(connection);
			//若是生成成功
			if (newUserId != null)
			{	//則將生成的userId連同Connection對象,插入到userconnection表,即利用代碼「偷偷地」註冊一個用戶
				createConnectionRepository(newUserId).addConnection(connection);
				//將生成的useId返回 
				return Arrays.asList(newUserId);
			}
		}
		//走到這裏說明經過providerId和providerUserId(openId)找到了useId
		return localUserIds;
	}

經過上面的源代碼解讀,我想你們確定已經知道,只要咱們在JdbcUsersConnectionRepository類裏注入一個 connectionSignUp ,並實現其 execute 方法就可讓用戶在進行第三方登錄的過程當中,再也不走註冊新用戶的邏輯,而直接登錄咱們的網站了。spring


3 具體代碼實現

3.1 編寫ConnectionSignUp的具體實現

package com.nrsc.security.server.security;

import org.springframework.social.connect.Connection;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.stereotype.Component;

/** * @author : Sun Chuan * @date : 2019/9/14 23:29 * Description:ConnectionSignUp類,須要注入到JdbcUsersConnectionRepository類裏, * 以實現第三方登錄時免註冊的邏輯功能 */
@Component
public class DemoConnectionSignUp implements ConnectionSignUp {
    @Override
    public String execute(Connection<?> connection) {
        //真實項目中: TODO
        //這裏其實應該向咱們的用戶業務表(user表)裏插入一條數據
        //能夠將插入user數據後的主鍵做爲userId進行返回

        //這裏爲了簡單起見,我就直接將connection對象中的displayName做爲useId進行返回了
        return connection.getDisplayName();
    }
}

3.2 將編寫的ConnectionSignUp具體實現類注入到JdbcUsersConnectionRepository

  • 這裏將JdbcUsersConnectionRepository類注入到spring容器的所有代碼貼出以下:
package com.nrsc.security.core.social;

import com.nrsc.security.core.properties.NrscSecurityProperties;
import com.nrsc.security.core.social.jdbc.NrscJdbcUsersConnectionRepository;
import com.nrsc.security.core.social.qq.NrscSpringSocialConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.encrypt.Encryptors;
import org.springframework.social.config.annotation.EnableSocial;
import org.springframework.social.config.annotation.SocialConfigurerAdapter;
import org.springframework.social.connect.ConnectionFactoryLocator;
import org.springframework.social.connect.ConnectionSignUp;
import org.springframework.social.connect.UsersConnectionRepository;
import org.springframework.social.connect.web.ProviderSignInUtils;
import org.springframework.social.security.SpringSocialConfigurer;

import javax.sql.DataSource;

/** * @author : Sun Chuan * @date : 2019/8/7 20:57 * Description: * UsersConnectionRepository的實現類,用來拿着Connection對象查找UserConnection表中是否與之相對應的userId * userId就是咱們系統中的惟一標識,這個應該由各個系統本身根據業務去指定,好比說你係統裏的username是惟一的, * 則這個useId就能夠是username */
@Configuration
@EnableSocial
public class SocialConfig extends SocialConfigurerAdapter {

    @Autowired
    private DataSource dataSource;
    @Autowired
    private NrscSecurityProperties nrscSecurityProperties;
    /** * 並不必定全部的系統都會在用戶進行第三方登錄時「偷偷地」給用戶註冊一個新用戶 * 因此這裏須要標明required = false */
    @Autowired(required = false)
    private ConnectionSignUp connectionSignUp;


    @Override
    public UsersConnectionRepository getUsersConnectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {

        /** * 第二個參數的做用:根據條件查找該用哪一個ConnectionFactory來構建Connection對象 * 第三個參數的做用: 對插入到userconnection表中的數據進行加密和解密 */
        NrscJdbcUsersConnectionRepository repository = new NrscJdbcUsersConnectionRepository(dataSource, connectionFactoryLocator, Encryptors.noOpText());
        //設置userconnection表的前綴
        repository.setTablePrefix("nrsc_");

        if (connectionSignUp != null) {
            //若是有spring容器裏connectionSignUp這個bean時,將其注入到UsersConnectionRepository
            repository.setConnectionSignUp(connectionSignUp);
        }
        return repository;
    }

    /** * 經過apply()該實例,能夠將SocialAuthenticationFilter加入到spring-security的過濾器鏈 */
    @Bean
    public SpringSocialConfigurer nrscSocialSecurityConfig() {
        // 默認配置類,進行組件的組裝
        // 包括了過濾器SocialAuthenticationFilter 添加到security過濾鏈中
        String filterProcessesUrl = nrscSecurityProperties.getSocial().getFilterProcessesUrl();
        NrscSpringSocialConfigurer configurer = new NrscSpringSocialConfigurer(filterProcessesUrl);

        //指定SpringSocial/SpringSecurity跳向註冊頁面時的url爲咱們配置的url
        configurer.signupUrl(nrscSecurityProperties.getBrowser().getSignUpUrl());
        return configurer;
    }

    /** * ProviderSignInUtils有兩個做用: * (1)從session裏獲取封裝了第三方用戶信息的Connection對象 * (2)將註冊的用戶信息與第三方用戶信息(QQ信息)創建關係並將該關係插入到userconnection表裏 * <p> * 須要的兩個參數: * (1)ConnectionFactoryLocator 能夠直接註冊進來,用來定位ConnectionFactory * (2)UsersConnectionRepository----》getUsersConnectionRepository(connectionFactoryLocator) * 即咱們配置的用來處理userconnection表的bean * * @param connectionFactoryLocator * @return */
    @Bean
    public ProviderSignInUtils providerSignInUtils(ConnectionFactoryLocator connectionFactoryLocator) {
        return new ProviderSignInUtils(connectionFactoryLocator,
                getUsersConnectionRepository(connectionFactoryLocator)) {
        };
    }
}

4 測試

(1)QQ登錄以前,userconnection表裏什麼都沒有:
在這裏插入圖片描述
(2)點擊QQ登錄—》進行QQ受權進入到以下斷點中:
在這裏插入圖片描述
(3)將代碼放行發現能夠跳到我設置的主頁,以下:
在這裏插入圖片描述
而且userconnection表裏多了一條數據以下:
在這裏插入圖片描述sql


至此第三方登錄過程當中關於註冊相關的功能開發已經所有介紹完畢!!!微信

相關文章
相關標籤/搜索