欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringSecurity?默認(rèn)登錄認(rèn)證的實(shí)現(xiàn)原理解析

 更新時(shí)間:2023年12月05日 09:48:13   作者:假正經(jīng)的小柴  
這篇文章主要介紹了SpringSecurity?默認(rèn)登錄認(rèn)證的實(shí)現(xiàn)原理解析,本文通過圖文示例相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

Spring Security 默認(rèn)登錄認(rèn)證的實(shí)現(xiàn)原理

一、默認(rèn)配置登錄認(rèn)證過程

二、流程分析

由默認(rèn)的 SecurityFilterChain 為例(即表單登錄),向服務(wù)器請求 /hello 資源Spring Security 的流程分析如下:

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

登錄頁面的由來

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

// 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);
        // 判斷是否是登錄請求、登錄錯(cuò)誤和注銷確認(rèn)
        // 不是的話給用戶返回登錄界面
        if (!this.isLoginUrlRequest(request) && !loginError && !logoutSuccess) {
            chain.doFilter(request, response);
        } else {
        // generateLoginPageHtml方法中有對頁面登錄代碼進(jìn)行了字符串拼接
        // 太長了,這里就不給出來了
            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);
        }
    }

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

在重定向到登錄頁面后,會有個(gè)疑問,它是怎么校驗(yàn)的,怎么對用戶名和密碼進(jìn)行認(rèn)證的呢?

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

接下來分析一個(gè) UsernamePasswordAuthenticationFilter 干了什么(它不是原生的過濾器,里面是attemptAuthetication進(jìn)行過濾,而不是doFilter,參數(shù)與原生過濾器相比少了個(gè)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的數(shù)據(jù)
		String username = obtainUsername(request);
		username = (username != null) ? username.trim() : "";
		// 這是獲取表單輸入框名為password的數(shù)據(jù)
		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中的認(rèn)證
		// 通過調(diào)用AuthenticationManager中的authenticate方法
		// 需要傳遞的參數(shù)的Authentication對象,當(dāng)時(shí)是這樣解釋的
		return this.getAuthenticationManager()
		.authenticate(authRequest);
	}

這邊經(jīng)過調(diào)試進(jìn)入到 authenticate 方法觀察如何認(rèn)證的,下面是調(diào)試的認(rèn)證過程:

1.進(jìn)入 authenticate 方法后會調(diào)用 ProviderManager 下的 authenticate 方法,它是重寫 AuthenticationManager 的,第一次 providers 里只有 AnoymousAuthenticationProvider 對象,用來匿名認(rèn)證的,最后會判斷支不支持此認(rèn)證,不支持換Provider;

2.此時(shí)匿名認(rèn)證匹配不了,往下執(zhí)行,由于parent 屬性不為空,所以會調(diào)用 parent 的 authenticate 進(jìn)行認(rèn)證。(其parent也是一個(gè)ProviderManager對象,但其 providers 集合中有且存在 DaoAuthenticationProvider 認(rèn)證對象)。

從這可以間接推出在 UsernamePasswordAuthenticationFilter 中的 AuthenticationManager對象 是通過以下構(gòu)造方法得出來的。

3.既然 provider.supports 方法匹配成功,那就讓provider去驗(yàn)證,然后將驗(yàn)證后的結(jié)果集返回。

DaoAuthenticationProvider 中未重寫 AuthenticationProvider 中的 authenticate 方法,由其抽象父類 AbstractUserDetailsAuthenticationProvider 實(shí)現(xiàn)的。核心方法通過retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);去獲取UserDetails對象,然后結(jié)合一些其他參數(shù)去創(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 對象,顯然沒有,咱剛調(diào)試呢,哪來的緩存
		UserDetails user = this.userCache.getUserFromCache(username);
		if (user == null) {
		// 既然為空呢,就說明這不是從緩存中拿的,調(diào)為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);
			// 這里是驗(yàn)證密碼的,通過子類DaoAuthenticationProvider的這個(gè)方法對密碼去進(jìn)行驗(yàn)證
			// 傳過去的參數(shù)是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);
	}

4.接下來就是核心方法 retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication) 的概述了,它是 DaoAuthenticationProvider 下的一個(gè)方法,用來返回 UserDetails 對象,即用戶的詳細(xì)信息,方便等等封裝到認(rèn)證信息 Authentication 中然后返回結(jié)果,判斷是否認(rèn)證成功。

// 一共兩個(gè)參數(shù),一個(gè)是用戶名,一個(gè)是傳過來的認(rèn)證信息
	@Override
	protected final UserDetails retrieveUser(String username, 
	UsernamePasswordAuthenticationToken authentication)
			throws AuthenticationException {
		prepareTimingAttackProtection();
		try {
		// 核心方法就是這個(gè),通過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);
		}
	}

我們可以看見默認(rèn)配置下它是一個(gè) InMemoryUserDetailsManager 對象,是一個(gè)基于內(nèi)存的關(guān)于UserDetails 的操作對象。

簡單看看它里面的loadUserByUsername方法,寫的也是非常簡單,它這里面用戶名不區(qū)分大小寫。

6.再說說密碼驗(yàn)證,密碼驗(yàn)證在3源碼里指出了,在獲取UserDetails對象user后,會調(diào)用子類的additionalAuthenticationChecks 方法進(jìn)行密碼驗(yàn)證。主要就是和輸出框輸入的密碼和那個(gè)UserDetails對象中的密碼進(jìn)行比較,UserDetails 密碼可以理解為是通過 PasswordEncoder 編碼后的密碼(密文),而輸入框輸入的是可以理解為是明文,可以簡單這樣先理解。然后通過 PasswordEncoder 去看看是否匹配。默認(rèn)是 DelegatingPasswordEncoder 密碼編碼器;

三、UserDetailsService

Spring Security 中 UserDetailsService 的實(shí)現(xiàn)

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

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

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

@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)內(nè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 ,聯(lián)想到啥?自動(dòng)化配置 SecurityFilterChain 遇到過。
上面配置意思的,要想使用默認(rèn)配置,得先滿足容器中不含 AuthenticationManager、AuthenticationProvider、UserDetailsService、AuthenticationManagerResolver實(shí)例這個(gè)條件。

默認(rèn)用戶名和密碼

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

首先是調(diào)用的 getUser 去獲取的,而這個(gè)user 就一直接 new 的一個(gè)User對象,它是一個(gè)靜態(tài)內(nèi)部類實(shí)例。

看下面靜態(tài)內(nèi)部類User屬性可以看見,其用戶名name是"user",而密碼則是一個(gè)UUID字符串,roles是一個(gè)list集合,可以指定多個(gè)。

注意:下面的 getter、setter 方法沒有截取出來。

那可不可以自己配置用戶名和密碼呢?
當(dāng)然是可以滴。

可以看見,SecurityProperties@ConfigurationProperties 注解修飾了(這里得知道SecurityProperties是由Spring容器管理的一個(gè)對象)。

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

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

比如我這么配置:

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

用戶名、密碼就被更改。

請?zhí)砑訄D片描述

四、總結(jié)

AuthenticationManager、ProviderManager、AuthenticationProvider關(guān)系。

  • 得知道 DaoAuthenticationProvider retrieveUser 方法和 additionalAuthenticationChecks 方法(這倆方法分別應(yīng)用了UserDetailsService和PasswordEncoder對象)。UsernamePasswordAuthenticationFilter 最后也是去通過 ProviderManager 中的 authenticate 去認(rèn)證,最后還是調(diào)到 DaoAuthenticationProvider 的父類 AbstractUserDetailsAuthenticationProvider 的 authenticate 去認(rèn)證,我們得清楚這個(gè)流程和這些類、方法,方便后期需要以及調(diào)試可用。
  • 我們可以通過去實(shí)現(xiàn) UserDetailsService 接口(自定義UserDetailsService),然后將實(shí)現(xiàn)類實(shí)例交給 Spring 容器管理,這樣就不會用默認(rèn)實(shí)現(xiàn)了,而是用我們的自定義實(shí)現(xiàn)。
  • UserDetails 是用戶的詳情對象,里面封裝了用戶名、密碼、權(quán)限等信息。也是 UserDetailsService 的返回值,這些都是可以自定義的。

到此這篇關(guān)于SpringSecurity 默認(rèn)登錄認(rèn)證的實(shí)現(xiàn)原理的文章就介紹到這了,更多相關(guān)SpringSecurity登錄認(rèn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java操作cookie示例(刪除cookie)

    java操作cookie示例(刪除cookie)

    這篇文章主要介紹了java操作cookie示例,包括設(shè)置Cookie、讀取Cookie、刪除Cookie,需要的朋友可以參考下
    2014-02-02
  • Spring細(xì)數(shù)兩種代理模式之靜態(tài)代理和動(dòng)態(tài)代理概念及使用

    Spring細(xì)數(shù)兩種代理模式之靜態(tài)代理和動(dòng)態(tài)代理概念及使用

    代理是一種設(shè)計(jì)模式,提供了對目標(biāo)對象另外的訪問方式,即通過代理對象訪問目標(biāo)對象??梢圆恍薷哪繕?biāo)對象,對目標(biāo)對象功能進(jìn)行拓展。在我們學(xué)習(xí)Spring的時(shí)候就會發(fā)現(xiàn),AOP(面向切面編程)的底層就是代理
    2023-02-02
  • 基于Java設(shè)計(jì)一個(gè)高并發(fā)的秒殺系統(tǒng)

    基于Java設(shè)計(jì)一個(gè)高并發(fā)的秒殺系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了如何基于Java設(shè)計(jì)一個(gè)高并發(fā)的秒殺系統(tǒng),文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下
    2023-10-10
  • Maven打包jar包沒有主屬性問題解決方案

    Maven打包jar包沒有主屬性問題解決方案

    這篇文章主要介紹了Maven打包jar包沒有主屬性問題解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • Java數(shù)組,去掉重復(fù)值、增加、刪除數(shù)組元素的實(shí)現(xiàn)方法

    Java數(shù)組,去掉重復(fù)值、增加、刪除數(shù)組元素的實(shí)現(xiàn)方法

    下面小編就為大家?guī)硪黄狫ava數(shù)組,去掉重復(fù)值、增加、刪除數(shù)組元素的實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-08-08
  • java在文件尾部追加內(nèi)容的簡單實(shí)例

    java在文件尾部追加內(nèi)容的簡單實(shí)例

    下面小編就為大家?guī)硪黄猨ava在文件尾部追加內(nèi)容的簡單實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2016-12-12
  • SpringBoot整合SSO(single sign on)單點(diǎn)登錄

    SpringBoot整合SSO(single sign on)單點(diǎn)登錄

    這篇文章主要介紹了SpringBoot整合SSO(single sign on)單點(diǎn)登錄,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • JavaMail入門教程之發(fā)送郵件(3)

    JavaMail入門教程之發(fā)送郵件(3)

    這篇文章主要為大家詳細(xì)介紹了JavaMail入門教程之發(fā)送郵件的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-11-11
  • Java中PrintWriter使用方法介紹

    Java中PrintWriter使用方法介紹

    這篇文章主要介紹了Java中PrintWriter使用方法介紹,文章圍繞主題展開PrintWriter使用方法的詳細(xì)介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參考一下
    2022-06-06
  • 深入理解Java設(shè)計(jì)模式之備忘錄模式

    深入理解Java設(shè)計(jì)模式之備忘錄模式

    這篇文章主要介紹了JAVA設(shè)計(jì)模式之備忘錄模式的的相關(guān)資料,文中示例代碼非常詳細(xì),供大家參考和學(xué)習(xí),感興趣的朋友可以了解
    2021-11-11

最新評論