国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【深入淺出Spring Security(三)】默認登錄認證的實現原理

這篇具有很好參考價值的文章主要介紹了【深入淺出Spring Security(三)】默認登錄認證的實現原理。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

一、默認配置登錄認證過程

【深入淺出Spring Security(三)】默認登錄認證的實現原理

二、流程分析

由默認的 SecurityFilterChain 為例(即表單登錄),向服務器請求 /hello 資源Spring Security 的流程分析如下:
【深入淺出Spring Security(三)】默認登錄認證的實現原理

  1. 請求 /hello 接口,在引入 Spring Security 之后會先經過一系列過濾器(一中請求的是 /test 接口);
  2. 在請求到達 FilterSecurityInterceptor 時,發(fā)現請求并未認證。請求被攔截下來,并拋出 AccessDeniedException 異常;
  3. 拋出 AccessDeniedException 的異常會被 ExceptionTranslationFilter 捕獲,這個Filter中會去調用 LoginUrlAuthenticationEntryPoint#commence 方法給客戶端返回 302(暫時重定向),要求客戶端進行重定向到 /login 頁面。
  4. 客戶端發(fā)送 /login 請求;
  5. /login 請求再次當遇到 DefaultLoginPageGeneratingFilter 過濾器時,會返回登錄頁面。

登錄頁面的由來

下面是DefaultLoginPageGeneratingFilter 重寫的doFilter方法,也可以解釋默認配置下為什么會返回登錄頁,登錄頁就由下面的過濾器實現而來。

// DefaultLoginPageGeneratingFilter

	@Override
	public void doFilter(ServletRequest request,
	 ServletResponse response, 
	FilterChain chain)
			throws IOException, ServletException {
		doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
	}

    private void doFilter(HttpServletRequest request, 
    HttpServletResponse response, 
    FilterChain chain) throws IOException, ServletException {
        boolean loginError = this.isErrorPage(request);
        boolean logoutSuccess = this.isLogoutSuccess(request);
        // 判斷是否是登錄請求、登錄錯誤和注銷確認
        // 不是的話給用戶返回登錄界面
        if (!this.isLoginUrlRequest(request) && !loginError && !logoutSuccess) {
            chain.doFilter(request, response);
        } else {
        // generateLoginPageHtml方法中有對頁面登錄代碼進行了字符串拼接
        // 太長了,這里就不給出來了
            String loginPageHtml = this.generateLoginPageHtml(request, loginError, logoutSuccess);
            response.setContentType("text/html;charset=UTF-8");
            response.setContentLength(loginPageHtml.getBytes(StandardCharsets.UTF_8).length);
            response.getWriter().write(loginPageHtml);
        }
    }

表單登錄認證過程(源碼分析)

在重定向到登錄頁面后,會有個疑問,它是怎么校驗的,怎么對用戶名和密碼進行認證的呢?

首先知道默認加載中是開啟了表單認證的,在【深入淺出Spring Security(二)】Spring Security的實現原理 中小編指出了默認加載的過濾器中有一個UsernamePasswordAuthenticationFilter,它是來處理表單請求的,其實它是在調用 HttpSecurity 中的 formLogin 方法配置的過濾器的。

接下來分析一個 UsernamePasswordAuthenticationFilter 干了什么(它不是原生的過濾器,里面是attemptAuthetication進行過濾,而不是doFilter,參數與原生過濾器相比少了個chain):

	@Override
	public Authentication attemptAuthentication(HttpServletRequest request, 
	HttpServletResponse response)
			throws AuthenticationException {
			// 首先是判斷是否是POST請求
		if (this.postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}
		// 獲取用戶名和密碼
		// 這是通過獲取表單輸入框名為username的數據
		String username = obtainUsername(request);
		username = (username != null) ? username.trim() : "";
		// 這是獲取表單輸入框名為password的數據
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
		UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
				password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		// 在一中小編也說了,這是Security中的認證
		// 通過調用AuthenticationManager中的authenticate方法
		// 需要傳遞的參數的Authentication對象,當時是這樣解釋的
		return this.getAuthenticationManager()
		.authenticate(authRequest);
	}

【深入淺出Spring Security(三)】默認登錄認證的實現原理

這邊經過調試進入到 authenticate 方法觀察如何認證的,下面是調試的認證過程:

  1. 進入 authenticate 方法后會調用 ProviderManager 下的 authenticate 方法,它是重寫 AuthenticationManager 的,第一次 providers 里只有 AnoymousAuthenticationProvider 對象,用來匿名認證的,最后會判斷支不支持此認證,不支持換Provider;
    【深入淺出Spring Security(三)】默認登錄認證的實現原理【深入淺出Spring Security(三)】默認登錄認證的實現原理

  2. 此時匿名認證匹配不了,往下執(zhí)行,由于parent 屬性不為空,所以會調用 parent 的 authenticate 進行認證。(其parent也是一個ProviderManager對象,但其 providers 集合中有且存在 DaoAuthenticationProvider 認證對象)。
    【深入淺出Spring Security(三)】默認登錄認證的實現原理從這可以間接推出在 UsernamePasswordAuthenticationFilter 中的 AuthenticationManager對象 是通過以下構造方法得出來的。
    【深入淺出Spring Security(三)】默認登錄認證的實現原理

  3. 既然 provider.supports 方法匹配成功,那就讓provider去驗證,然后將驗證后的結果集返回。
    【深入淺出Spring Security(三)】默認登錄認證的實現原理DaoAuthenticationProvider 中未重寫 AuthenticationProvider 中的 authenticate 方法,由其抽象父類 AbstractUserDetailsAuthenticationProvider 實現的。核心方法通過retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);去獲取UserDetails對象,然后結合一些其他參數去創(chuàng)Authentication對象將其返回。

AbstractUserDetailsAuthenticationProvider下的authenticate方法

	@Override
	public Authentication authenticate(Authentication authentication) 
	throws AuthenticationException {
// 斷言 authentication 是否是UsernamePasswordAuthenticationToken對象
	Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
				() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
						"Only UsernamePasswordAuthenticationToken is supported"));
		// 獲取一下用戶名
		String username = determineUsername(authentication);
		boolean cacheWasUsed = true;
		// 從緩存中拿UserDetails 對象,顯然沒有,咱剛調試呢,哪來的緩存
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
		// 既然為空呢,就說明這不是從緩存中拿的,調為false
			cacheWasUsed = false;
			try {
			// 核心代碼,獲取UserDetails對象去
				user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			}
			catch (UsernameNotFoundException ex) {
				this.logger.debug("Failed to find user '" + username + "'");
				if (!this.hideUserNotFoundExceptions) {
					throw ex;
				}
				throw new BadCredentialsException(this.messages
						.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
			}
			Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
		}
		try {
			this.preAuthenticationChecks.check(user);
			// 這里是驗證密碼的,通過子類DaoAuthenticationProvider的這個方法對密碼去進行驗證
			// 傳過去的參數是user(UserDetails對象)和authentication對象
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		catch (AuthenticationException ex) {
			if (!cacheWasUsed) {
				throw ex;
			}
			// There was a problem, so try again after checking
			// we're using latest data (i.e. not from the cache)
			cacheWasUsed = false;
			user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
			this.preAuthenticationChecks.check(user);
			additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
		}
		this.postAuthenticationChecks.check(user);
		if (!cacheWasUsed) {
			this.userCache.putUserInCache(user);
		}
		Object principalToReturn = user;
		if (this.forcePrincipalAsString) {
			principalToReturn = user.getUsername();
		}
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}
  1. 接下來就是核心方法 retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication) 的概述了,它是 DaoAuthenticationProvider 下的一個方法,用來返回 UserDetails 對象,即用戶的詳細信息,方便等等封裝到認證信息 Authentication 中然后返回結果,判斷是否認證成功。
// 一共兩個參數,一個是用戶名,一個是傳過來的認證信息
	@Override
	protected final UserDetails retrieveUser(String username, 
	UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
		// 核心方法就是這個,通過UserDetatilsService中的loadUserByUsername方法去獲取UserDetails對象
			UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
			if (loadedUser == null) {
				throw new InternalAuthenticationServiceException(
						"UserDetailsService returned null, which is an interface contract violation");
			}
			return loadedUser;
		}
		catch (UsernameNotFoundException ex) {
			mitigateAgainstTimingAttack(authentication);
			throw ex;
		}
		catch (InternalAuthenticationServiceException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
		}
	}

我們可以看見默認配置下它是一個 InMemoryUserDetailsManager 對象,是一個基于內存的關于UserDetails 的操作對象。【深入淺出Spring Security(三)】默認登錄認證的實現原理簡單看看它里面的loadUserByUsername方法,寫的也是非常簡單,它這里面用戶名不區(qū)分大小寫。
【深入淺出Spring Security(三)】默認登錄認證的實現原理

  1. 再說說密碼驗證,密碼驗證在3源碼里指出了,在獲取UserDetails對象user后,會調用子類的additionalAuthenticationChecks 方法進行密碼驗證。主要就是和輸出框輸入的密碼和那個UserDetails對象中的密碼進行比較,UserDetails 密碼可以理解為是通過 PasswordEncoder 編碼后的密碼(密文),而輸入框輸入的是可以理解為是明文,可以簡單這樣先理解。然后通過 PasswordEncoder 去看看是否匹配。默認是 DelegatingPasswordEncoder 密碼編碼器;
    【深入淺出Spring Security(三)】默認登錄認證的實現原理

三、UserDetailsService

Spring Security 中 UserDetailsService 的實現

【深入淺出Spring Security(三)】默認登錄認證的實現原理

  • UserDetailsManager 在 UserDetailsService 的基礎上,繼續(xù)定義了添加用戶、更新用戶、刪除用戶、修改密碼以及判斷用戶是否存在共 5 種方法。
  • JdbcDaoImpl 在 UserDetailsService 的基礎上,通過 spring-jdbc 實現了從數據庫中查詢用戶的方法。
  • InMemoryUserDetailsManager 實現了 UserDetailsManager 中關于用戶的增刪改查方法,不過都是基于內存的操作,數據并沒有持久化。
  • JdbcUserDetailsManager 繼承自 JdbcDaoImpl 同時又實現了 UserDetailsManager 接口,因此可以通過 JdbcUserDetailsManager 實現對用戶的增刪改查操作,這些操作都會持久化到數據庫中。不過 JdbcUserDetailsManager 有一個局限性,就是操作數據庫中用戶的 SQL 都是提前寫好的,不夠靈活,因此在實際開發(fā)中 JdbcUserDetailsManager 使用并不多。
  • CachingUserDetailsService 的特點是會將 UserDetailsService 緩存起來。
  • UserDetailsServiceDelegator 則是提供了 UserDetailsService 的懶加載功能。
  • ReactiveUserDetailsServiceAdapter 是 webflux-web-security 模塊定義的 UserDetailsService 的實現。

默認的 UserDetailsService 配置(源碼分析)

關于UserDetailsService的默認配置在UserDetailsServiceAutoConfiguration自動配置類中。(由于代碼很長,這里只提取核心部分)

@AutoConfiguration
@ConditionalOnClass(AuthenticationManager.class)
@ConditionalOnBean(ObjectPostProcessor.class)
@ConditionalOnMissingBean(
		value = { AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class,
				AuthenticationManagerResolver.class },
		type = { "org.springframework.security.oauth2.jwt.JwtDecoder",
				"org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector",
				"org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
				"org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository" })
public class UserDetailsServiceAutoConfiguration {

	@Bean
	@Lazy
	public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
			ObjectProvider<PasswordEncoder> passwordEncoder) {
			// 這里是從SecurityProperties中獲取User對象(這里的User對象是SecurityProperties的靜態(tài)內部類)
		SecurityProperties.User user = properties.getUser();
		List<String> roles = user.getRoles();
		// 然后創(chuàng)建InMemoryUserDetailsManager對象返回
		// 交給Spring容器管理
		return new InMemoryUserDetailsManager(User.withUsername(user.getName())
			.password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
			.roles(StringUtils.toStringArray(roles))
			.build());
	}
	}

觀察 UserDetailsServiceAutoConfiguration 上的注解 @ConditionalOnMissingBean ,聯想到啥?自動化配置 SecurityFilterChain 遇到過。
上面配置意思的,要想使用默認配置,得先滿足容器中不含 AuthenticationManager、AuthenticationProvider、UserDetailsService、AuthenticationManagerResolver實例這個條件。

默認用戶名和密碼

從上面自動化配置 UserDetailsService 中,我們也發(fā)現了使用的User對象是從 SecurityProperties 中獲取的,那咱看一下是怎么個 User 對象吧。

首先是調用的 getUser 去獲取的,而這個user 就一直接 new 的一個User對象,它是一個靜態(tài)內部類實例。
【深入淺出Spring Security(三)】默認登錄認證的實現原理
看下面靜態(tài)內部類User屬性可以看見,其用戶名name是"user",而密碼則是一個UUID字符串,roles是一個list集合,可以指定多個。
【深入淺出Spring Security(三)】默認登錄認證的實現原理注意:下面的 getter、setter 方法沒有截取出來。

那可不可以自己配置用戶名和密碼呢?
當然是可以滴。
【深入淺出Spring Security(三)】默認登錄認證的實現原理可以看見,SecurityProperties@ConfigurationProperties 注解修飾了(這里得知道SecurityProperties是由Spring容器管理的一個對象)。

而 @ConfigurationProperties 注解是通過 setter 注入的方式,將配置文件配置的值,映射到被該注解修飾的對象中。

所以我們可以在配置文件中進行自己的配置,可以配置自己的用戶名和密碼。

比如我這么配置:

# application.yml
spring:
  security:
    user:
      name: xxx
      password: 123

用戶名、密碼就被更改。

【深入淺出Spring Security(三)】默認登錄認證的實現原理文章來源地址http://www.zghlxwxcb.cn/news/detail-484753.html

四、總結

  • AuthenticationManager、ProviderManager、AuthenticationProvider關系。
    【深入淺出Spring Security(三)】默認登錄認證的實現原理
  • 得知道 DaoAuthenticationProvider retrieveUser 方法和 additionalAuthenticationChecks 方法(這倆方法分別應用了UserDetailsService和PasswordEncoder對象)。UsernamePasswordAuthenticationFilter 最后也是去通過 ProviderManager 中的 authenticate 去認證,最后還是調到 DaoAuthenticationProvider 的父類 AbstractUserDetailsAuthenticationProvider 的 authenticate 去認證,我們得清楚這個流程和這些類、方法,方便后期需要以及調試可用。
  • 我們可以通過去實現 UserDetailsService 接口(自定義UserDetailsService),然后將實現類實例交給 Spring 容器管理,這樣就不會用默認實現了,而是用我們的自定義實現。
  • UserDetails 是用戶的詳情對象,里面封裝了用戶名、密碼、權限等信息。也是 UserDetailsService 的返回值,這些都是可以自定義的。

到了這里,關于【深入淺出Spring Security(三)】默認登錄認證的實現原理的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。如若轉載,請注明出處: 如若內容造成侵權/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經查實,立即刪除!

領支付寶紅包贊助服務器費用

相關文章

  • 【深入淺出Spring Security(一)】Spring Security的整體架構

    【深入淺出Spring Security(一)】Spring Security的整體架構

    這篇博客所述主要是在讀《 深入淺出Spring Security 》途中所做的筆記(之前有學Spring Security,但了解的比較淺,所以想著看這本書深入一點點,這都是因為上次一個bug調了我?guī)滋欤?這本書的 pdf 網盤鏈接可通過微信掃下方公眾號私信\\\"深入淺出Spring Security\\\"即可獲取。 在 Spring

    2024年02月06日
    瀏覽(26)
  • 【深入淺出 Spring Security(七)】RememberMe的實現原理詳講

    【深入淺出 Spring Security(七)】RememberMe的實現原理詳講

    先看看最簡單用法的默認頁面效果變化。 SecurityConfig 配置類 測試 TestController 代碼 以下是給出的默認的登錄頁面。 觀察頁面源代碼可以發(fā)現,比原先沒配置 RememberMe 之前多了個 name 為 remember-me 的 checkbox 選項。 如果我們勾選了它并且登錄成功后,當我們關閉掉當前瀏覽器,

    2024年02月09日
    瀏覽(25)
  • 【深入淺出 Spring Security(十一)】授權原理分析和持久化URL權限管理

    【深入淺出 Spring Security(十一)】授權原理分析和持久化URL權限管理

    在 【深入淺出Spring Security(一)】Spring Security的整體架構 中小編解釋過授權所用的三大組件,在此再解釋說明一下(三大組件具體指:ConfigAttribute、AccessDecisionManager(決策管理器)、AccessDecisionVoter(決策投票器)) ConfigAttribute 在 Spring Security 中,用戶請求一個資源(通常是

    2024年02月10日
    瀏覽(25)
  • 【C++深入淺出】類和對象中篇(六種默認成員函數、運算符重載)

    【C++深入淺出】類和對象中篇(六種默認成員函數、運算符重載)

    目錄 一. 前言? 二. 默認成員函數 三. 構造函數 3.1 概念 3.2 特性 四. 析構函數 4.1 概念 4.2 特性 五. 拷貝構造函數 5.1 概念 5.2 特性 六. 運算符重載 6.1 引入 6.2 概念 6.3 注意事項 6.4 重載示例 6.5 賦值運算符重載 6.6 前置++和后置++運算符重載 七. const成員函數 7.1 問題引入 7.2 定義

    2024年02月09日
    瀏覽(100)
  • 深入淺出Spring AOP

    深入淺出Spring AOP

    第1章:引言 大家好,我是小黑,咱們今天要聊的是Java中Spring框架的AOP(面向切面編程)。對于程序員來說,理解AOP對于掌握Spring框架來說是超級關鍵的。它像是魔法一樣,能讓咱們在不改變原有代碼的情況下,給程序增加各種功能。 AOP不僅僅是一個編程范式,它更是一種思

    2024年01月20日
    瀏覽(28)
  • 深入淺出 Spring:核心概念和基本用法詳解

    深入淺出 Spring:核心概念和基本用法詳解

    個人主頁:17_Kevin-CSDN博客 收錄專欄;《Java》 在 Java 企業(yè)級應用開發(fā)中,Spring 框架已經成為了事實上的標準。它提供了一種輕量級的解決方案,使得開發(fā)者能夠更輕松地構建靈活、可擴展的應用程序。在本文中,我們將探討 Spring 框架的一些核心概念和基本用法,以此更好地

    2024年03月20日
    瀏覽(22)
  • Spring5深入淺出篇:Spring與工廠設計模式簡介

    Spring5深入淺出篇:Spring與工廠設計模式簡介

    輕量級 JavaEE的解決?案 spring實際上就是對原有設計模式的一種高度封裝和整合 整合設計模式 工廠設計模式 什么是工廠設計模式 當UserServiceImpl發(fā)生變化是會影響到userService等相關聯的類,在線上環(huán)境不利于維護

    2024年01月18日
    瀏覽(33)
  • Spring5深入淺出篇:bean的生命周期

    Spring5深入淺出篇:bean的生命周期

    指的是?個對象創(chuàng)建、存活、消亡的?個完整過程 由Spring負責對象的創(chuàng)建、存活、銷毀,了解?命周期,有利于我們使?好Spring為我們創(chuàng)建的對象 創(chuàng)建階段 Spring??何時創(chuàng)建對象 當bean標簽中增加scope=\\\"singleton\\\"時,當你創(chuàng)建對象所有的引用都是第一個對象的內存地址;sigleton:只

    2024年04月12日
    瀏覽(29)
  • 【深入淺出Spring原理及實戰(zhàn)】「源碼調試分析」深入源碼探索Spring底層框架的的refresh方法所出現的問題和異常

    閱讀Spring官方文檔,了解Spring框架的基本概念和使用方法。 下載Spring源碼,可以從官網或者GitHub上獲取。 閱讀Spring源碼的入口類,了解Spring框架的啟動過程和核心組件的加載順序。 閱讀Spring源碼中的注釋和文檔,了解每個類和方法的作用和用法。 調試Spring源碼,可以通過

    2023年04月23日
    瀏覽(33)
  • Spring高手之路14——深入淺出:SPI機制在JDK與Spring Boot中的應用

    Spring高手之路14——深入淺出:SPI機制在JDK與Spring Boot中的應用

    ?? SPI ( Service Provider Interface ) 是一種服務發(fā)現機制,它允許第三方提供者為核心庫或主框架提供實現或擴展。這種設計允許核心庫/框架在不修改自身代碼的情況下,通過第三方實現來增強功能。 JDK原生的SPI : 定義和發(fā)現 : JDK 的 SPI 主要通過在 META-INF/services/ 目錄下放置

    2024年02月09日
    瀏覽(26)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領取紅包,優(yōu)惠每天領

二維碼1

領取紅包

二維碼2

領紅包