最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解SpringSecurity中的Authentication信息與登錄流程

 更新時(shí)間:2020年09月09日 10:06:31   作者:天喬巴夏丶  
這篇文章主要介紹了SpringSecurity中的Authentication信息與登錄流程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

Authentication

使用SpringSecurity可以在任何地方注入Authentication進(jìn)而獲取到當(dāng)前登錄的用戶信息,可謂十分強(qiáng)大。

在Authenticaiton的繼承體系中,實(shí)現(xiàn)類UsernamePasswordAuthenticationToken 算是比較常見(jiàn)的一個(gè)了,在這個(gè)類中存在兩個(gè)屬性:principal和credentials,其實(shí)分別代表著用戶和密碼?!井?dāng)然其他的屬性存在于其父類中,如authoritiesdetails?!?/p>

我們需要對(duì)這個(gè)對(duì)象有一個(gè)基本地認(rèn)識(shí),它保存了用戶的基本信息。用戶在登錄的時(shí)候,進(jìn)行了一系列的操作,將信息存與這個(gè)對(duì)象中,后續(xù)我們使用的時(shí)候,就可以輕松地獲取這些信息了。

那么,用戶信息如何存,又是如何取的呢?繼續(xù)往下看吧。

登錄流程

一、與認(rèn)證相關(guān)的UsernamePasswordAuthenticationFilter

通過(guò)Servlet中的Filter技術(shù)進(jìn)行實(shí)現(xiàn),通過(guò)一系列內(nèi)置的或自定義的安全Filter,實(shí)現(xiàn)接口的認(rèn)證與授權(quán)。

比如:UsernamePasswordAuthenticationFilter

public Authentication attemptAuthentication(HttpServletRequest request,
			HttpServletResponse response) throws AuthenticationException {
		if (postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException(
					"Authentication method not supported: " + request.getMethod());
		}
		//獲取用戶名和密碼
		String username = obtainUsername(request);
		String password = obtainPassword(request);

		if (username == null) {
			username = "";
		}

		if (password == null) {
			password = "";
		}
		username = username.trim();
		//構(gòu)造UsernamePasswordAuthenticationToken對(duì)象
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
				username, password);

		// 為details屬性賦值
		setDetails(request, authRequest);
		// 調(diào)用authenticate方法進(jìn)行校驗(yàn)
		return this.getAuthenticationManager().authenticate(authRequest);
	}

獲取用戶名和密碼

從request中提取參數(shù),這也是SpringSecurity默認(rèn)的表單登錄需要通過(guò)key/value形式傳遞參數(shù)的原因。

@Nullable
	protected String obtainPassword(HttpServletRequest request) {
		return request.getParameter(passwordParameter);
	}
	@Nullable
	protected String obtainUsername(HttpServletRequest request) {
		return request.getParameter(usernameParameter);
	}

構(gòu)造UsernamePasswordAuthenticationToken對(duì)象

傳入獲取到的用戶名和密碼,而用戶名對(duì)應(yīng)UPAT對(duì)象中的principal屬性,而密碼對(duì)應(yīng)credentials屬性。

UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
 username, password);

//UsernamePasswordAuthenticationToken 的構(gòu)造器
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
 super(null);
 this.principal = principal;
 this.credentials = credentials;
 setAuthenticated(false);
}

為details屬性賦值

// Allow subclasses to set the "details" property 允許子類去設(shè)置這個(gè)屬性
setDetails(request, authRequest);

protected void setDetails(HttpServletRequest request,
    UsernamePasswordAuthenticationToken authRequest) {
 authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}

//AbstractAuthenticationToken 是UsernamePasswordAuthenticationToken的父類
public void setDetails(Object details) {
 this.details = details;
}

 

details屬性存在于父類之中,主要描述兩個(gè)信息,一個(gè)是remoteAddress 和sessionId。

	public WebAuthenticationDetails(HttpServletRequest request) {
		this.remoteAddress = request.getRemoteAddr();

		HttpSession session = request.getSession(false);
		this.sessionId = (session != null) ? session.getId() : null;
	}

調(diào)用authenticate方法進(jìn)行校驗(yàn)

this.getAuthenticationManager().authenticate(authRequest)

二、ProviderManager的校驗(yàn)邏輯

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()) {
 //獲取Class,判斷當(dāng)前provider是否支持該authentication
 if (!provider.supports(toTest)) {
  continue;
 }
 //如果支持,則調(diào)用provider的authenticate方法開(kāi)始校驗(yàn)
 result = provider.authenticate(authentication);
 
		//將舊的token的details屬性拷貝到新的token中。
 if (result != null) {
  copyDetails(authentication, result);
  break;
 }
 }
 //如果上一步的結(jié)果為null,調(diào)用provider的parent的authenticate方法繼續(xù)校驗(yàn)。
 if (result == null && parent != null) {
 result = parentResult = parent.authenticate(authentication);
 }

 if (result != null) {
 if (eraseCredentialsAfterAuthentication
  && (result instanceof CredentialsContainer)) {
  //調(diào)用eraseCredentials方法擦除憑證信息
  ((CredentialsContainer) result).eraseCredentials();
 }
 if (parentResult == null) {
  //publishAuthenticationSuccess將登錄成功的事件進(jìn)行廣播。
  eventPublisher.publishAuthenticationSuccess(result);
 }
 return result;
 }
}

獲取Class,判斷當(dāng)前provider是否支持該authentication。

如果支持,則調(diào)用provider的authenticate方法開(kāi)始校驗(yàn),校驗(yàn)完成之后,返回一個(gè)新的Authentication。

將舊的token的details屬性拷貝到新的token中。

如果上一步的結(jié)果為null,調(diào)用provider的parent的authenticate方法繼續(xù)校驗(yàn)。

調(diào)用eraseCredentials方法擦除憑證信息,也就是密碼,具體來(lái)說(shuō)就是讓credentials為空。

publishAuthenticationSuccess將登錄成功的事件進(jìn)行廣播。

三、AuthenticationProvider的authenticate

public Authentication authenticate(Authentication authentication)
		throws AuthenticationException {
 //從Authenticaiton中提取登錄的用戶名。
	String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
			: authentication.getName();
 //返回登錄對(duì)象
	user = retrieveUser(username,(UsernamePasswordAuthenticationToken) authentication);
 //校驗(yàn)user中的各個(gè)賬戶狀態(tài)屬性是否正常
	preAuthenticationChecks.check(user);
 //密碼比對(duì)
	additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken) authentication);
 //密碼比對(duì)
	postAuthenticationChecks.check(user);
	Object principalToReturn = user;
 //表示是否強(qiáng)制將Authentication中的principal屬性設(shè)置為字符串
	if (forcePrincipalAsString) {
		principalToReturn = user.getUsername();
	}
 //構(gòu)建新的UsernamePasswordAuthenticationToken
	return createSuccessAuthentication(principalToReturn, authentication, user);
}

從Authenticaiton中提取登錄的用戶名。retrieveUser方法將會(huì)調(diào)用loadUserByUsername方法,這里將會(huì)返回登錄對(duì)象。preAuthenticationChecks.check(user);校驗(yàn)user中的各個(gè)賬戶狀態(tài)屬性是否正常,如賬號(hào)是否被禁用,賬戶是否被鎖定,賬戶是否過(guò)期等。additionalAuthenticationChecks用于做密碼比對(duì),密碼加密解密校驗(yàn)就在這里進(jìn)行。postAuthenticationChecks.check(user);用于密碼比對(duì)。forcePrincipalAsString表示是否強(qiáng)制將Authentication中的principal屬性設(shè)置為字符串,默認(rèn)為false,也就是說(shuō)默認(rèn)登錄之后獲取的用戶是對(duì)象,而不是username。構(gòu)建新的UsernamePasswordAuthenticationToken。

用戶信息保存

我們來(lái)到UsernamePasswordAuthenticationFilter 的父類AbstractAuthenticationProcessingFilter 中,

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
		throws IOException, ServletException {
	HttpServletRequest request = (HttpServletRequest) req;
	HttpServletResponse response = (HttpServletResponse) res;
	Authentication authResult;
	try {
 //實(shí)際觸發(fā)了上面提到的attemptAuthentication方法
		authResult = attemptAuthentication(request, response);
		if (authResult == null) {
			return;
		}
		sessionStrategy.onAuthentication(authResult, request, response);
	}
 //登錄失敗
	catch (InternalAuthenticationServiceException failed) {
		unsuccessfulAuthentication(request, response, failed);
		return;
	}
	catch (AuthenticationException failed) {
		unsuccessfulAuthentication(request, response, failed);
		return;
	}
	if (continueChainBeforeSuccessfulAuthentication) {
		chain.doFilter(request, response);
	}
 //登錄成功
	successfulAuthentication(request, response, chain, authResult);
}

關(guān)于登錄成功調(diào)用的方法:

protected void successfulAuthentication(HttpServletRequest request,
		HttpServletResponse response, FilterChain chain, Authentication authResult)
		throws IOException, ServletException {
 //將登陸成功的用戶信息存儲(chǔ)在SecurityContextHolder.getContext()中
	SecurityContextHolder.getContext().setAuthentication(authResult);
	rememberMeServices.loginSuccess(request, response, authResult);
	// Fire event
	if (this.eventPublisher != null) {
		eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
				authResult, this.getClass()));
	}
 //登錄成功的回調(diào)方法
	successHandler.onAuthenticationSuccess(request, response, authResult);
}

我們可以通過(guò)SecurityContextHolder.getContext().setAuthentication(authResult);得到兩點(diǎn)結(jié)論:

  • 如果我們想要獲取用戶信息,我們只需要調(diào)用SecurityContextHolder.getContext().getAuthentication()即可。
  • 如果我們想要更新用戶信息,我們只需要調(diào)用SecurityContextHolder.getContext().setAuthentication(authResult);即可。

用戶信息的獲取

前面說(shuō)到,我們可以利用Authenticaiton輕松得到用戶信息,主要有下面幾種方法:

通過(guò)上下文獲取。

SecurityContextHolder.getContext().getAuthentication();

直接在Controller注入Authentication。

@GetMapping("/hr/info")
public Hr getCurrentHr(Authentication authentication) {
 return ((Hr) authentication.getPrincipal());
}

為什么多次請(qǐng)求可以獲取同樣的信息

前面已經(jīng)談到,SpringSecurity將登錄用戶信息存入SecurityContextHolder 中,本質(zhì)上,其實(shí)是存在ThreadLocal中,為什么這么說(shuō)呢?

原因在于,SpringSecurity采用了策略模式,在SecurityContextHolder 中定義了三種不同的策略,而如果我們不配置,默認(rèn)就是MODE_THREADLOCAL模式。

public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";
public static final String SYSTEM_PROPERTY = "spring.security.strategy";
private static String strategyName = System.getProperty(SYSTEM_PROPERTY);

private static void initialize() {
 if (!StringUtils.hasText(strategyName)) {
 // Set default
 strategyName = MODE_THREADLOCAL;
 }
 if (strategyName.equals(MODE_THREADLOCAL)) {
 strategy = new ThreadLocalSecurityContextHolderStrategy();
 } 
}

private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();

了解這個(gè)之后,又有一個(gè)問(wèn)題拋出:ThreadLocal能夠保證同一線程的數(shù)據(jù)是一份,那進(jìn)進(jìn)出出之后,線程更改,又如何保證登錄的信息是正確的呢。

這里就要說(shuō)到一個(gè)比較重要的過(guò)濾器:SecurityContextPersistenceFilter,它的優(yōu)先級(jí)很高,僅次于WebAsyncManagerIntegrationFilter。也就是說(shuō),在進(jìn)入后面的過(guò)濾器之前,將會(huì)先來(lái)到這個(gè)類的doFilter方法。

public class SecurityContextPersistenceFilter extends GenericFilterBean {
	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;
 if (request.getAttribute(FILTER_APPLIED) != null) {
			// 確保這個(gè)過(guò)濾器只應(yīng)對(duì)一個(gè)請(qǐng)求
			chain.doFilter(request, response);
			return;
		}
 //分岔路口之后,表示應(yīng)對(duì)多個(gè)請(qǐng)求
		HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
				response);
 //用戶信息在 session 中保存的 value。
		SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
		try {
  //將當(dāng)前用戶信息存入上下文
			SecurityContextHolder.setContext(contextBeforeChainExecution);
			chain.doFilter(holder.getRequest(), holder.getResponse());
		}
		finally {
  //收尾工作,獲取SecurityContext
			SecurityContext contextAfterChainExecution = SecurityContextHolder
					.getContext();
  //清空SecurityContext
			SecurityContextHolder.clearContext();
  //重新存進(jìn)session中
			repo.saveContext(contextAfterChainExecution, holder.getRequest(),
					holder.getResponse());
		}
	}
}
  • SecurityContextPersistenceFilter 繼承自 GenericFilterBean,而 GenericFilterBean 則是 Filter 的實(shí)現(xiàn),所以 SecurityContextPersistenceFilter 作為一個(gè)過(guò)濾器,它里邊最重要的方法就是 doFilter 了。
  • doFilter 方法中,它首先會(huì)從 repo 中讀取一個(gè) SecurityContext 出來(lái),這里的 repo 實(shí)際上就是 HttpSessionSecurityContextRepository,讀取 SecurityContext 的操作會(huì)進(jìn)入到 readSecurityContextFromSession(httpSession) 方法中。
  • 在這里我們看到了讀取的核心方法 Object contextFromSession = httpSession.getAttribute(springSecurityContextKey);,這里的 springSecurityContextKey 對(duì)象的值就是 SPRING_SECURITY_CONTEXT,讀取出來(lái)的對(duì)象最終會(huì)被轉(zhuǎn)為一個(gè) SecurityContext 對(duì)象。
  • SecurityContext 是一個(gè)接口,它有一個(gè)唯一的實(shí)現(xiàn)類 SecurityContextImpl,這個(gè)實(shí)現(xiàn)類其實(shí)就是用戶信息在 session 中保存的 value。
  • 在拿到 SecurityContext 之后,通過(guò) SecurityContextHolder.setContext 方法將這個(gè) SecurityContext 設(shè)置到 ThreadLocal 中去,這樣,在當(dāng)前請(qǐng)求中,Spring Security 的后續(xù)操作,我們都可以直接從 SecurityContextHolder 中獲取到用戶信息了。
  • 接下來(lái),通過(guò) chain.doFilter 讓請(qǐng)求繼續(xù)向下走(這個(gè)時(shí)候就會(huì)進(jìn)入到 UsernamePasswordAuthenticationFilter 過(guò)濾器中了)。
  • 在過(guò)濾器鏈走完之后,數(shù)據(jù)響應(yīng)給前端之后,finally 中還有一步收尾操作,這一步很關(guān)鍵。這里從 SecurityContextHolder 中獲取到 SecurityContext,獲取到之后,會(huì)把 SecurityContextHolder 清空,然后調(diào)用 repo.saveContext 方法將獲取到的 SecurityContext 存入 session 中。

總結(jié):

每個(gè)請(qǐng)求到達(dá)服務(wù)端的時(shí)候,首先從session中找出SecurityContext ,為了本次請(qǐng)求之后都能夠使用,設(shè)置到SecurityContextHolder 中。

當(dāng)請(qǐng)求離開(kāi)的時(shí)候,SecurityContextHolder 會(huì)被清空,且SecurityContext 會(huì)被放回session中,方便下一個(gè)請(qǐng)求來(lái)獲取。

資源放行的兩種方式

用戶登錄的流程只有走過(guò)濾器鏈,才能夠?qū)⑿畔⒋嫒雜ession中,因此我們配置登錄請(qǐng)求的時(shí)候需要使用configure(HttpSecurity http),因?yàn)檫@個(gè)配置會(huì)走過(guò)濾器鏈。

http.authorizeRequests()
 .antMatchers("/hello").permitAll()
 .anyRequest().authenticated()

而 configure(WebSecurity web)不會(huì)走過(guò)濾器鏈,適用于靜態(tài)資源的放行。

@Override
public void configure(WebSecurity web) throws Exception {
 	web.ignoring().antMatchers("/index.html","/img/**","/fonts/**","/favicon.ico");
}

到此這篇關(guān)于SpringSecurity中的Authentication信息與登錄流程的文章就介紹到這了,更多相關(guān)SpringSecurity登錄流程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java中l(wèi)ist使用時(shí)需避免的場(chǎng)景總結(jié)

    java中l(wèi)ist使用時(shí)需避免的場(chǎng)景總結(jié)

    眾所周知,Java為開(kāi)發(fā)者提供了多種集合類的實(shí)現(xiàn),其中幾乎所有業(yè)務(wù)代碼都需要用到List,但List的錯(cuò)誤使用也會(huì)導(dǎo)致諸多問(wèn)題,所以本文我們就來(lái)看一看幾個(gè)錯(cuò)誤使用List的場(chǎng)景吧
    2023-10-10
  • Java之int和string類型轉(zhuǎn)換詳解

    Java之int和string類型轉(zhuǎn)換詳解

    這篇文章主要介紹了Java之int和string類型轉(zhuǎn)換詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • SpringBoot okhtt工具類封裝方式

    SpringBoot okhtt工具類封裝方式

    本文介紹了如何在SpringBoot項(xiàng)目中使用OkHttp工具類進(jìn)行HTTP請(qǐng)求,并提供了一個(gè)封裝了GET和POST方法的工具類示例,在Controller中使用該工具類時(shí),需要注意OkHttpClient的靜態(tài)初始化和異常處理
    2025-03-03
  • 一篇文章帶你了解JavaSE的數(shù)據(jù)類型

    一篇文章帶你了解JavaSE的數(shù)據(jù)類型

    這篇文章主要給大家介紹了關(guān)于JavaSE的數(shù)據(jù)類型,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-09-09
  • SpringBoot下RabbitMq實(shí)現(xiàn)定時(shí)任務(wù)

    SpringBoot下RabbitMq實(shí)現(xiàn)定時(shí)任務(wù)

    這篇文章主要為大家詳細(xì)介紹了SpringBoot下RabbitMq實(shí)現(xiàn)定時(shí)任務(wù),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-11-11
  • java實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)

    java實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生信息管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • Mybatis mapper.xml使用全局變量的三種實(shí)現(xiàn)方法

    Mybatis mapper.xml使用全局變量的三種實(shí)現(xiàn)方法

    文章介紹了在Mybatis的Mapper.xml文件中使用全局變量來(lái)動(dòng)態(tài)配置數(shù)據(jù)庫(kù)庫(kù)名的實(shí)現(xiàn)方案,包括使用mybaits自帶全局變量、使用@value和mybatis進(jìn)行全局變量定義以及使用@value和mybatis進(jìn)行全局變量定義并減少形參的方案
    2025-02-02
  • MybatisPlus查詢條件為空字符串或null問(wèn)題及解決

    MybatisPlus查詢條件為空字符串或null問(wèn)題及解決

    這篇文章主要介紹了MybatisPlus查詢條件為空字符串或null問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • MyBatis多表查詢和注解開(kāi)發(fā)案例詳解

    MyBatis多表查詢和注解開(kāi)發(fā)案例詳解

    這篇文章主要介紹了MyBatis多表查詢和注解開(kāi)發(fā),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-05-05
  • 解決myBatis中openSession()自動(dòng)提交的問(wèn)題

    解決myBatis中openSession()自動(dòng)提交的問(wèn)題

    在學(xué)習(xí)MySQL過(guò)程中,發(fā)現(xiàn)插入操作自動(dòng)提交,問(wèn)題原因可能是myBatis中的openSession()方法設(shè)置了自動(dòng)提交,或者是MySQL的默認(rèn)引擎設(shè)置為不支持事務(wù)的MyISAM,解決辦法包括更改myBatis的提交設(shè)置或?qū)ySQL表的引擎改為InnoDB
    2024-09-09

最新評(píng)論

吉安县| 哈密市| 运城市| 江川县| 威信县| 凭祥市| 武穴市| 七台河市| 民丰县| 海晏县| 城步| 石屏县| 微山县| 新安县| 顺平县| 德昌县| 昌吉市| 阜宁县| 徐闻县| 定边县| 曲沃县| 利辛县| 汝阳县| 辽阳市| 兴安县| 略阳县| 贵州省| 南汇区| 辉县市| 康定县| 漳浦县| 都安| 迁安市| 乌拉特中旗| 兴和县| 漠河县| 商河县| 元氏县| 衡阳市| 敦化市| 宜阳县|