SpringSecurity怎么解析授權(quán)過程

Spring Security怎么解析授權(quán)過程,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

網(wǎng)站建設(shè)、成都網(wǎng)站制作介紹好的網(wǎng)站是理念、設(shè)計(jì)和技術(shù)的結(jié)合。創(chuàng)新互聯(lián)公司擁有的網(wǎng)站設(shè)計(jì)理念、多方位的設(shè)計(jì)風(fēng)格、經(jīng)驗(yàn)豐富的設(shè)計(jì)團(tuán)隊(duì)。提供PC端+手機(jī)端網(wǎng)站建設(shè),用營(yíng)銷思維進(jìn)行網(wǎng)站設(shè)計(jì)、采用先進(jìn)技術(shù)開源代碼、注重用戶體驗(yàn)與SEO基礎(chǔ),將技術(shù)與創(chuàng)意整合到網(wǎng)站之中,以契合客戶的方式做到創(chuàng)意性的視覺化效果。

Spring Security 解析(一) —— 授權(quán)過程

> ??在學(xué)習(xí)Spring Cloud 時(shí),遇到了授權(quán)服務(wù)oauth 相關(guān)內(nèi)容時(shí),總是一知半解,因此決定先把Spring Security 、Spring Security Oauth3 等權(quán)限、認(rèn)證相關(guān)的內(nèi)容、原理及設(shè)計(jì)學(xué)習(xí)并整理一遍。

> 項(xiàng)目環(huán)境: > - JDK1.8 > - Spring boot 2.x > - Spring Security 5.x

一、 一個(gè)簡(jiǎn)單的Security Demo

1、 自定義的UserDetailsService實(shí)現(xiàn)

??自定義MyUserDetailsUserService類,實(shí)現(xiàn) UserDetailsService 接口的 loadUserByUsername()方法,這里就簡(jiǎn)單的返回一個(gè)Spring Security 提供的 User 對(duì)象。為了后面方便演示Spring Security 的權(quán)限控制,這里使用AuthorityUtils.commaSeparatedStringToAuthorityList("admin")設(shè)置了user賬號(hào)有一個(gè)admin的角色權(quán)限信息。實(shí)際項(xiàng)目中可以在這里通過訪問數(shù)據(jù)庫(kù)獲取到用戶及其角色、權(quán)限信息。

@Component
public class MyUserDetailsUserService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 不能直接使用 創(chuàng)建 BCryptPasswordEncoder 對(duì)象來加密, 這種加密方式 沒有 {bcrypt}  前綴,
        // 會(huì)導(dǎo)致在  matches 時(shí)導(dǎo)致獲取不到加密的算法出現(xiàn)
        // java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"  問題
        // 問題原因是 Spring Security5 使用 DelegatingPasswordEncoder(委托)  替代 NoOpPasswordEncoder,
        // 并且 默認(rèn)使用  BCryptPasswordEncoder 加密(注意 DelegatingPasswordEncoder 委托加密方法BCryptPasswordEncoder  加密前  添加了加密類型的前綴)  https://blog.csdn.net/alinyua/article/details/80219500
        return new User("user",  PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("123456"), AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

??注意Spring Security 5 開始沒有使用 NoOpPasswordEncoder作為其默認(rèn)的密碼編碼器,而是默認(rèn)使用 DelegatingPasswordEncoder作為其密碼編碼器,其 encode 方法是通過 密碼編碼器的名稱作為前綴 + 委托各類密碼編碼器來實(shí)現(xiàn)encode的。

public String encode(CharSequence rawPassword) {
        return "{" + this.idForEncode + "}" + this.passwordEncoderForEncode.encode(rawPassword);
    }

??這里的 idForEncode 就是密碼編碼器的簡(jiǎn)略名稱,可以通過 PasswordEncoderFactories.createDelegatingPasswordEncoder()內(nèi)部實(shí)現(xiàn)看到默認(rèn)是使用的前綴是 bcrypt 也就是 BCryptPasswordEncoder

public class PasswordEncoderFactories {
    public static PasswordEncoder createDelegatingPasswordEncoder() {
        String encodingId = "bcrypt";
        Map<string, passwordencoder> encoders = new HashMap();
        encoders.put(encodingId, new BCryptPasswordEncoder());
        encoders.put("ldap", new LdapShaPasswordEncoder());
        encoders.put("MD4", new Md4PasswordEncoder());
        encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
        encoders.put("noop", NoOpPasswordEncoder.getInstance());
        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());
        encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
        encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
        encoders.put("sha256", new StandardPasswordEncoder());
        return new DelegatingPasswordEncoder(encodingId, encoders);
    }
}
2、 設(shè)置Spring Security配置

??定義SpringSecurityConfig 配置類,并繼承WebSecurityConfigurerAdapter覆蓋其configure(HttpSecurity http) 方法。

@Configuration
@EnableWebSecurity //1
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()  //2
            .and()
                .authorizeRequests() //3
                .antMatchers("/index","/").permitAll() //4
                .anyRequest().authenticated(); //6
    }
}

配置解析:

  • @EnableWebSecurity 查看其注解源碼,主要是引用WebSecurityConfiguration.class 和 加入了@EnableGlobalAuthentication 注解 ,這里就不介紹了,我們只要明白添加 @EnableWebSecurity 注解將開啟 Security 功能。

  • formLogin() 使用表單登錄(默認(rèn)請(qǐng)求地址為 /login),在Spring Security 5 里其實(shí)已經(jīng)將舊版本默認(rèn)的 httpBasic() 更換成 formLogin() 了,這里為了表明表單登錄還是配置了一次。

  • authorizeRequests() 開始請(qǐng)求權(quán)限配置

  • antMatchers() 使用Ant風(fēng)格的路徑匹配,這里配置匹配 / 和 /index

  • permitAll() 用戶可任意訪問

  • anyRequest() 匹配所有路徑

  • authenticated() 用戶登錄后可訪問


3、 配置html 和測(cè)試接口

?? 在 resources/static 目錄下新建 index.html , 其內(nèi)部定義一個(gè)訪問測(cè)試接口的按鈕

<meta charset="UTF-8">
    <title>歡迎</title>


        Spring Security 歡迎你!
        <p> <a href="/get_user/test">測(cè)試驗(yàn)證Security 權(quán)限控制</a></p>

??創(chuàng)建 rest 風(fēng)格的獲取用戶信息接口

@RestController
public class TestController {

    @GetMapping("/get_user/{username}")
    public String getUser(@PathVariable  String username){
        return username;
    }
}
4、 啟動(dòng)項(xiàng)目測(cè)試

1、訪問 localhost:8080 無任何阻攔直接成功

Spring Security怎么解析授權(quán)過程

2、點(diǎn)擊測(cè)試驗(yàn)證權(quán)限控制按鈕 被重定向到了 Security默認(rèn)的登錄頁(yè)面 Spring Security怎么解析授權(quán)過程

3、使用 MyUserDetailsUserService定義的默認(rèn)賬戶 user : 123456 進(jìn)行登錄后成功跳轉(zhuǎn)到 /get_user 接口

Spring Security怎么解析授權(quán)過程


二、 @EnableWebSecurity 配置解析

?? 還記得之前講過 @EnableWebSecurity 引用了 WebSecurityConfiguration 配置類 和 @EnableGlobalAuthentication 注解嗎? 其中 WebSecurityConfiguration 就是與授權(quán)相關(guān)的配置,@EnableGlobalAuthentication 配置了 認(rèn)證相關(guān)的我們下節(jié)再細(xì)討。

?? 首先我們查看 WebSecurityConfiguration 源碼,可以很清楚的發(fā)現(xiàn) springSecurityFilterChain()方法。

    @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = webSecurityConfigurers != null
				&amp;&amp; !webSecurityConfigurers.isEmpty();
		if (!hasConfigurers) {
			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
			webSecurity.apply(adapter);
		}
		return webSecurity.build(); //1
	}

??這個(gè)方法首先會(huì)判斷 webSecurityConfigurers 是否為空,為空加載一個(gè)默認(rèn)的 WebSecurityConfigurerAdapter對(duì)象,由于自定義的 SpringSecurityConfig 本身是繼承 WebSecurityConfigurerAdapter對(duì)象 的,所以我們自定義的 Security 配置肯定會(huì)被加載進(jìn)來的(如果想要了解如何加載進(jìn)來可以看下WebSecurityConfiguration.setFilterChainProxySecurityConfigurer() 方法)。

?? 我們看下 webSecurity.build() 方法實(shí)現(xiàn) 實(shí)際調(diào)用的是 AbstractConfiguredSecurityBuilder.doBuild() 方法,其方法內(nèi)部實(shí)現(xiàn)如下:

@Override
	protected final O doBuild() throws Exception {
		synchronized (configurers) {
			buildState = BuildState.INITIALIZING;

			beforeInit();
			init();

			buildState = BuildState.CONFIGURING;

			beforeConfigure();
			configure();

			buildState = BuildState.BUILDING;

			O result = performBuild(); // 1 創(chuàng)建 DefaultSecurityFilterChain (Security Filter 責(zé)任鏈 ) 

			buildState = BuildState.BUILT;

			return result;
		}
	}

?? 我們把關(guān)注點(diǎn)放到 performBuild()方法,看其實(shí)現(xiàn)子類 HttpSecurity.performBuild() 方法,其內(nèi)部排序 filters 并創(chuàng)建了 DefaultSecurityFilterChain對(duì)象。

    @Override
	protected DefaultSecurityFilterChain performBuild() throws Exception {
		Collections.sort(filters, comparator);
		return new DefaultSecurityFilterChain(requestMatcher, filters);
	}

?? 查看DefaultSecurityFilterChain 的構(gòu)造方法,我們可以看到有記錄日志。

public DefaultSecurityFilterChain(RequestMatcher requestMatcher, List<filter> filters) {
		logger.info("Creating filter chain: " + requestMatcher + ", " + filters); // 按照正常情況,我們可以看到控制臺(tái)輸出 這條日志 
		this.requestMatcher = requestMatcher;
		this.filters = new ArrayList&lt;&gt;(filters);
	}

?? 我們可以回頭看下項(xiàng)目啟動(dòng)日志??梢钥吹较聢D明顯打印了 這條日志,并且把所有 Filter名都打印出來了。==(請(qǐng)注意這里打印的 filter 鏈,接下來我們的所有授權(quán)過程都是依靠這條filter 鏈展開 )== Spring Security怎么解析授權(quán)過程

??那么還有個(gè)疑問: HttpSecurity.performBuild() 方法中的 filters 是怎么加載的呢? 這個(gè)時(shí)候需要查看 WebSecurityConfigurerAdapter.init() 方法,這個(gè)方法內(nèi)部 調(diào)用 getHttp() 方法返回 HttpSecurity 對(duì)象(看到這里我們應(yīng)該能想到 filters 就是這個(gè)方法中添加好了數(shù)據(jù)),具體如何加載的也就不介紹了。

public void init(final WebSecurity web) throws Exception {
		final HttpSecurity http = getHttp(); // 1 
		web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
			public void run() {
				FilterSecurityInterceptor securityInterceptor = http
						.getSharedObject(FilterSecurityInterceptor.class);
				web.securityInterceptor(securityInterceptor);
			}
		});
	}

?? 用了這么長(zhǎng)時(shí)間解析 @EnableWebSecurity ,其實(shí)最關(guān)鍵的一點(diǎn)就是創(chuàng)建了 DefaultSecurityFilterChain也就是我們常 security filter 責(zé)任鏈,接下來我們圍繞這個(gè) DefaultSecurityFilterChain 中 的 filters 進(jìn)行授權(quán)過程的解析。

三、 授權(quán)過程解析

> ??Security的授權(quán)過程可以理解成各種 filter 處理最終完成一個(gè)授權(quán)。那么我們?cè)倏聪轮?打印的filter 鏈,這里為了方便,再次貼出圖片 Spring Security怎么解析授權(quán)過程

??這里我們只關(guān)注以下幾個(gè)重要的 filter : > - SecurityContextPersistenceFilter > - UsernamePasswordAuthenticationFilter (AbstractAuthenticationProcessingFilter) > - BasicAuthenticationFilter > - AnonymousAuthenticationFilter > - ExceptionTranslationFilter > - FilterSecurityInterceptor

1、SecurityContextPersistenceFilter

??SecurityContextPersistenceFilter 這個(gè)filter的主要負(fù)責(zé)以下幾件事:

> - 通過 (SecurityContextRepository)repo.loadContext() 方法從請(qǐng)求Session中獲取 SecurityContext(Security 上下文 ,類似 ApplicaitonContext ) 對(duì)象,如果請(qǐng)求Session中沒有默認(rèn)創(chuàng)建一個(gè) authentication(認(rèn)證的關(guān)鍵對(duì)象,由于本節(jié)只講授權(quán),暫不介紹) 屬性為 null 的 SecurityContext 對(duì)象 > - SecurityContextHolder.setContext() 將 SecurityContext 對(duì)象放入 SecurityContextHolder進(jìn)行管理(SecurityContextHolder默認(rèn)使用ThreadLocal 策略來存儲(chǔ)認(rèn)證信息) > - 由于在 finally 里實(shí)現(xiàn) 會(huì)在最后通過 SecurityContextHolder.clearContext() 將 SecurityContext 對(duì)象 從 SecurityContextHolder中清除 > - 由于在 finally 里實(shí)現(xiàn) 會(huì)在最后通過 repo.saveContext() 將 SecurityContext 對(duì)象 放入Session中

HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
				response);
		//從Session中獲取SecurityContxt 對(duì)象,如果Session中沒有則創(chuàng)建一個(gè) authtication 屬性為 null 的SecurityContext對(duì)象
		SecurityContext contextBeforeChainExecution = repo.loadContext(holder); 

		try {
		    // 將 SecurityContext 對(duì)象放入 SecurityContextHolder進(jìn)行管理 (SecurityContextHolder默認(rèn)使用ThreadLocal 策略來存儲(chǔ)認(rèn)證信息)
			 SecurityContextHolder.setContext(contextBeforeChainExecution);

			 chain.doFilter(holder.getRequest(), holder.getResponse());

		}
		finally {
			SecurityContext contextAfterChainExecution = SecurityContextHolder
					.getContext();
			
			// 將 SecurityContext 對(duì)象 從 SecurityContextHolder中清除
			SecurityContextHolder.clearContext();
			// 將 SecurityContext 對(duì)象 放入Session中
			repo.saveContext(contextAfterChainExecution, holder.getRequest(),
					holder.getResponse());
			request.removeAttribute(FILTER_APPLIED);

			if (debug) {
				logger.debug("SecurityContextHolder now cleared, as request processing completed");
			}
		}

??我們?cè)?SecurityContextPersistenceFilter 中打上斷點(diǎn),啟動(dòng)項(xiàng)目,訪問 localhost:8080 , 來debug看下實(shí)現(xiàn):

Spring Security怎么解析授權(quán)過程 ?? 我們可以清楚的看到創(chuàng)建了一個(gè)authtication 為null 的 SecurityContext對(duì)象,并且可以看到請(qǐng)求調(diào)用的filter鏈具體有哪些。接下來看下 finally 內(nèi)部處理

Spring Security怎么解析授權(quán)過程

?? 你會(huì)發(fā)現(xiàn)這里的SecurityContxt中的 authtication 是一個(gè)名為 anonymousUser (匿名用戶)的認(rèn)證信息,這是因?yàn)?請(qǐng)求調(diào)用到了 AnonymousAuthenticationFilter , Security默認(rèn)創(chuàng)建了一個(gè)匿名用戶訪問。

2、UsernamePasswordAuthenticationFilter (AbstractAuthenticationProcessingFilter)

??看filter字面意思就知道這是一個(gè)通過獲取請(qǐng)求中的賬戶密碼來進(jìn)行授權(quán)的filter,按照慣例,整理了這個(gè)filter的職責(zé): > - 通過 requiresAuthentication()判斷 是否以POST 方式請(qǐng)求 /login > - 調(diào)用 attemptAuthentication() 方法進(jìn)行認(rèn)證,內(nèi)部創(chuàng)建了 authenticated 屬性為 false(即未授權(quán))的UsernamePasswordAuthenticationToken 對(duì)象, 并傳遞給 AuthenticationManager().authenticate() 方法進(jìn)行認(rèn)證,認(rèn)證成功后 返回一個(gè) authenticated = true (即授權(quán)成功的)UsernamePasswordAuthenticationToken 對(duì)象 > - 通過 sessionStrategy.onAuthentication() 將 Authentication 放入Session中 > - 通過 successfulAuthentication() 調(diào)用 AuthenticationSuccessHandler 的 onAuthenticationSuccess 接口 進(jìn)行成功處理( 可以 通過 繼承 AuthenticationSuccessHandler 自行編寫成功處理邏輯 )successfulAuthentication(request, response, chain, authResult); > - 通過 unsuccessfulAuthentication() 調(diào)用AuthenticationFailureHandler 的 onAuthenticationFailure 接口 進(jìn)行失敗處理(可以通過繼承AuthenticationFailureHandler 自行編寫失敗處理邏輯 )

??我們?cè)倏聪鹿俜皆创a的處理邏輯:

// 1 AbstractAuthenticationProcessingFilter 的 doFilter 方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {

		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

        // 2 判斷請(qǐng)求地址是否是  /login 和 請(qǐng)求方式為 POST  (UsernamePasswordAuthenticationFilter 構(gòu)造方法 確定的)
		if (!requiresAuthentication(request, response)) {
			chain.doFilter(request, response);
			return;
		}
		Authentication authResult;
		try {
		    
		    // 3 調(diào)用 子類  UsernamePasswordAuthenticationFilter 的 attemptAuthentication 方法
		    // attemptAuthentication 方法內(nèi)部創(chuàng)建了 authenticated 屬性為 false (即未授權(quán))的 UsernamePasswordAuthenticationToken 對(duì)象, 并傳遞給 AuthenticationManager().authenticate() 方法進(jìn)行認(rèn)證,
		    //認(rèn)證成功后 返回一個(gè) authenticated = true (即授權(quán)成功的) UsernamePasswordAuthenticationToken 對(duì)象 
			authResult = attemptAuthentication(request, response);
			if (authResult == null) {
				return;
			}
			// 4 將認(rèn)證成功的 Authentication 存入Session中
			sessionStrategy.onAuthentication(authResult, request, response);
		}
		catch (InternalAuthenticationServiceException failed) {
		     // 5 認(rèn)證失敗后 調(diào)用 AuthenticationFailureHandler 的 onAuthenticationFailure 接口 進(jìn)行失敗處理( 可以 通過 繼承 AuthenticationFailureHandler 自行編寫失敗處理邏輯 )
			unsuccessfulAuthentication(request, response, failed);
			return;
		}
		catch (AuthenticationException failed) {
		    // 5 認(rèn)證失敗后 調(diào)用 AuthenticationFailureHandler 的 onAuthenticationFailure 接口 進(jìn)行失敗處理( 可以 通過 繼承 AuthenticationFailureHandler 自行編寫失敗處理邏輯 )
			unsuccessfulAuthentication(request, response, failed);
			return;
		}
		
        ......
         // 6 認(rèn)證成功后 調(diào)用 AuthenticationSuccessHandler 的 onAuthenticationSuccess 接口 進(jìn)行失敗處理( 可以 通過 繼承 AuthenticationSuccessHandler 自行編寫成功處理邏輯 )
		successfulAuthentication(request, response, chain, authResult);
	}

??從源碼上看,整個(gè)流程其實(shí)是很清晰的:從判斷是否處理,到認(rèn)證,最后判斷認(rèn)證結(jié)果分別作出認(rèn)證成功和認(rèn)證失敗的處理。

??debug 調(diào)試下看 結(jié)果,這次我們請(qǐng)求 localhast:8080/get_user/test , 由于沒權(quán)限會(huì)直接跳轉(zhuǎn)到登錄界面,我們先輸入錯(cuò)誤的賬號(hào)密碼,看下認(rèn)證失敗是否與我們總結(jié)的一致。

Spring Security怎么解析授權(quán)過程

??結(jié)果與預(yù)想時(shí)一致的,也許你會(huì)奇怪這里的提示為啥時(shí)中文,這就不得不說Security 5 開始支持 中文,說明咋中國(guó)程序員在世界上越來越有地位了!??!

?? 這次輸入正確的密碼, 看下返回的Authtication 對(duì)象信息:

Spring Security怎么解析授權(quán)過程

?? 可以看到這次成功返回一個(gè) authticated = ture ,沒有密碼的 user賬戶信息,而且還包含我們定義的一個(gè)admin權(quán)限信息。放開斷點(diǎn),由于Security默認(rèn)的成功處理器是SimpleUrlAuthenticationSuccessHandler ,這個(gè)處理器會(huì)重定向到之前訪問的地址,也就是 localhast:8080/get_user/test。 至此整個(gè)流程結(jié)束。不,我們還差一個(gè),Session,我們從瀏覽器Cookie中看到 Session:

Spring Security怎么解析授權(quán)過程

3、BasicAuthenticationFilter

??BasicAuthenticationFilter 與UsernameAuthticationFilter類似,不過區(qū)別還是很明顯,BasicAuthenticationFilter 主要是從Header 中獲取 Authorization 參數(shù)信息,然后調(diào)用認(rèn)證,認(rèn)證成功后最后直接訪問接口,不像UsernameAuthticationFilter過程一樣通過AuthenticationSuccessHandler 進(jìn)行跳轉(zhuǎn)。這里就不在貼代碼了,想了解的同學(xué)可以直接看源碼。不過有一點(diǎn)要注意的是,BasicAuthenticationFilter 的 onSuccessfulAuthentication() 成功處理方法是一個(gè)空方法。

?? 為了試驗(yàn)BasicAuthenticationFilter, 我們需要將 SpringSecurityConfig 中的formLogin()更換成httpBasic()以支持BasicAuthenticationFilter,重啟項(xiàng)目,同樣訪問 localhast:8080/get_user/test,這時(shí)由于沒權(quán)限訪問這個(gè)接口地址,頁(yè)面上會(huì)彈出一個(gè)登陸框,熟悉Security4的同學(xué)一定很眼熟吧,同樣,我們輸入賬戶密碼后,看下debug數(shù)據(jù):

Spring Security怎么解析授權(quán)過程

?? 這時(shí),我們就能夠獲取到 Authorization 參數(shù),進(jìn)而解析獲取到其中的賬戶和密碼信息,進(jìn)行認(rèn)證,我們查看認(rèn)證成功后返回的Authtication對(duì)象信息其實(shí)是和UsernamePasswordAuthticationFilter中的一致,最后再次調(diào)用下一個(gè)filter,由于已經(jīng)認(rèn)證成功了會(huì)直接進(jìn)入FilterSecurityInterceptor 進(jìn)行權(quán)限驗(yàn)證。

4、AnonymousAuthenticationFilter

??這里為什么要提下 AnonymousAuthenticationFilter呢,主要是因?yàn)樵赟ecurity中不存在沒有賬戶這一說法(這里可能描述不是很清楚,但大致意思是這樣的),針對(duì)這個(gè)Security官方專門指定了這個(gè)AnonymousAuthenticationFilter ,用于前面所有filter都認(rèn)證失敗的情況下,自動(dòng)創(chuàng)建一個(gè)默認(rèn)的匿名用戶,擁有匿名訪問權(quán)限。還記得 在講解 SecurityContextPersistenceFilter 時(shí)我們看到得匿名 autication信息么?如果不記得還得回頭看下哦,這里就不再敘述了。

5、ExceptionTranslationFilter

??ExceptionTranslationFilter 其實(shí)沒有做任何過濾處理,但別小看它得作用,它最大也最牛叉之處就在于它捕獲AuthenticationException 和AccessDeniedException,如果發(fā)生的異常是這2個(gè)異常 會(huì)調(diào)用 handleSpringSecurityException()方法進(jìn)行處理。 我們模擬下 AccessDeniedException(無權(quán)限,禁止訪問異常)情況,首先我們需要修改下 /get_user 接口:

  • 在Controller 上添加 @EnableGlobalMethodSecurity(prePostEnabled =true) 啟用Security 方法級(jí)別得權(quán)限控制

  • 在 接口上添加 @PreAuthorize("hasRole('user')") 只允許有user角色得賬戶訪問(還記得我們默認(rèn)得user 賬戶時(shí)admin角色么?)

@RestController
@EnableGlobalMethodSecurity(prePostEnabled =true)  // 開啟方法級(jí)別的權(quán)限控制
public class TestController {

    @PreAuthorize("hasRole('user')") //只允許user角色訪問
    @GetMapping("/get_user/{username}")
    public String getUser(@PathVariable  String username){
        return username;
    }
}

??重啟項(xiàng)目,重新訪問 /get_user 接口,輸入正確的賬戶密碼,發(fā)現(xiàn)返回一個(gè) 403 狀態(tài)的錯(cuò)誤頁(yè)面,這與我們之前將的流程時(shí)一致的。debug,看下處理:

Spring Security怎么解析授權(quán)過程

??可以明顯的看到異常對(duì)象是 AccessDeniedException ,異常信息是不允許訪問,我們?cè)倏聪?AccessDeniedException 異常后的處理方法accessDeniedHandler.handle(),進(jìn)入到了 AccessDeniedHandlerImpl 的handle()方法,這個(gè)方法會(huì)先判斷系統(tǒng)是否配置了 errorPage (錯(cuò)誤頁(yè)面),沒有的話直接往 response 中設(shè)置403 狀態(tài)碼。

Spring Security怎么解析授權(quán)過程

6、FilterSecurityInterceptor

??FilterSecurityInterceptor 是整個(gè)Security filter鏈中的最后一個(gè),也是最重要的一個(gè),它的主要功能就是判斷認(rèn)證成功的用戶是否有權(quán)限訪問接口,其最主要的處理方法就是 調(diào)用父類(AbstractSecurityInterceptor)的 super.beforeInvocation(fi),我們來梳理下這個(gè)方法的處理流程:

> - 通過 obtainSecurityMetadataSource().getAttributes() 獲取 當(dāng)前訪問地址所需權(quán)限信息 > - 通過 authenticateIfRequired() 獲取當(dāng)前訪問用戶的權(quán)限信息 > - 通過 accessDecisionManager.decide() 使用 投票機(jī)制判權(quán),判權(quán)失敗直接拋出 AccessDeniedException 異常

protected InterceptorStatusToken beforeInvocation(Object object) {
	       
	    ......
	    
	    // 1 獲取訪問地址的權(quán)限信息 
		Collection<configattribute> attributes = this.obtainSecurityMetadataSource()
				.getAttributes(object);

		if (attributes == null || attributes.isEmpty()) {
		
		    ......
		    
			return null;
		}

        ......

        // 2 獲取當(dāng)前訪問用戶權(quán)限信息
		Authentication authenticated = authenticateIfRequired();

	
		try {
		    // 3  默認(rèn)調(diào)用AffirmativeBased.decide() 方法, 其內(nèi)部 使用 AccessDecisionVoter 對(duì)象 進(jìn)行投票機(jī)制判權(quán),判權(quán)失敗直接拋出 AccessDeniedException 異常 
			this.accessDecisionManager.decide(authenticated, object, attributes);
		}
		catch (AccessDeniedException accessDeniedException) {
			publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
					accessDeniedException));

			throw accessDeniedException;
		}

        ......
        return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
					attributes, object);
	}

?? 整個(gè)流程其實(shí)看起來不復(fù)雜,主要就分3個(gè)部分,首選獲取訪問地址的權(quán)限信息,其次獲取當(dāng)前訪問用戶的權(quán)限信息,最后通過投票機(jī)制判斷出是否有權(quán)。

三、 個(gè)人總結(jié)

  整個(gè)授權(quán)流程核心的就在于這幾次核心filter的處理,這里我用序列圖來概況下這個(gè)授權(quán)流程

Spring Security怎么解析授權(quán)過程

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對(duì)創(chuàng)新互聯(lián)的支持。

本文標(biāo)題:SpringSecurity怎么解析授權(quán)過程
網(wǎng)頁(yè)URL:http://bm7419.com/article32/pssgpc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供域名注冊(cè)、關(guān)鍵詞優(yōu)化、移動(dòng)網(wǎng)站建設(shè)、品牌網(wǎng)站建設(shè)、微信小程序網(wǎng)站排名

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都做網(wǎng)站