spring security

前言

本章內(nèi)容:

??????Spring Security介紹

??????使用Servlet規(guī)范中的Filter保護Web應(yīng)用

??????基于數(shù)據(jù)庫和LDAP進行認證

????Spring Security是為基于Spring的應(yīng)用程序提供聲明式安全保護的安全性框架。Spring Security提供了完整的安全性解決方案,它能夠在Web請求級別和方法調(diào)用級別處理身份認證和授權(quán)。因為基于Spring框架,所以Spring Security充分利用了依賴注入(dependency injection,DI)和面向切面的技術(shù)。

????最初,Spring Security被稱為Accegi Security。到了2.0版本,Accegi Security更名為Spring Security。但是2.0發(fā)布版本所帶來的不僅僅是表面上名字的變化。為了在Spring中配置安全性,Spring Security引入了一個全新的、與安全性相關(guān)的XML命名空間。這個新的命名空間連同注解和一些合理的默認設(shè)置,將典型的安全性配置從幾百行XML減少到幾十行。Spring Security 3.0融入了SpEl,這進一步簡化了安全性的配置。

????它的最新版本為3.2,Spring Security從兩個角度來解決安全性問題。它使用Servlet規(guī)范中的Filter保護Web請求并限制URL級別的訪問。Spring Security還能夠使用Spring AOP保護方法調(diào)用---借助于對象代理和使用通知,能夠確保只有具備適當權(quán)限的用戶才能訪問安全保護的方法。

????在本章中,我們將會關(guān)注如何將Spring Security用于Web層的安全性之中。在稍后的第14章中,我們會重新學習Spring Security,了解它如何保護方法的調(diào)用。

理解Spring Security的模塊

????不管你想使用Spring Security保護哪種類型的應(yīng)用程序,第一件需要做的事就是將Spring Security模塊添加到應(yīng)用程序類路徑下。Spring Security 3.2分為11個模塊,如表9.1所示:

??????ACL:支持通過訪問控制列表(access contol list,ACL)為域?qū)ο筇峁┌踩浴?/p>

??????切面(Aspects):一個很小的模塊,當使用Spring Security注解時,會使用基于AspectJ的切面,而不是使用標準的Spring AOP。

??????CAS客戶端(CAS Client):提供與Jasig的中心認證服務(wù)(Central Authentication Service,CAS)進行集成的功能。

??????配置(Configuration):包含通過XML和Java配置Spring Security的功能支持。

??????核心(Core):提供Spring Security基本庫。

??????加密(Cryptography):提供了加密和密碼編碼的功能。

??????LDAP:支持基于OpenID進行集中式認證。

??????Remoting:提供了對Spring Remoting的支持。

??????標簽庫(Tag Library):Spring Security的JSP標簽庫。

??????Web:提供了Spring Security基于Filter的Web安全性支持。

????應(yīng)用程序的類路徑下至少要包含Core和Configuration這兩個模塊。Spring Security經(jīng)常被用于保護Web應(yīng)用,這顯然也是Spittr應(yīng)用的場景,所以我們還需要添加Web模塊。同時我們還會用到Spring? Security的JSP標簽庫,所以我們需要將這個模塊也添加進來。

????現(xiàn)在,我們已經(jīng)為在Spring Security中進行安全性配置做好了準備。讓我們看看如何使用Spring Security的XML命名空間。

過濾Web請求

????Spring Security借助一系列Servlet Filter來提供各種安全性功能。你可能會想,這是否意味著我們需要在web.xml或WebApplicationInitializer中配置多個Filter呢?實際上,借助于Spring的小技巧,我們只需配置一個Filter就可以了。

????DelegatingFilterProxy是一個特殊的Servlet Filter,它本身所做的工作并不多。只是將工作委托給一個javax.servlet.Filter實現(xiàn)類,這個實現(xiàn)類作為一個注冊在Spring應(yīng)用的上下文中,如圖9.1所示。

圖 9.1 DelegatingFilterProxy把Filter的處理邏輯委托給Spring應(yīng)用上下文中所定義的一個代理Filter bean

如果你喜歡在傳統(tǒng)的web.xml中配置Servlet和Filter的話,可以使用元素,如下所示:

pom.xml

????在這里,最重要的是設(shè)置成了SpringSecurityFilterChain。這是因為我們馬上就會將Spring Security配置在Web安全性之中,這里會有一個名為SpringSecurityFilterChain的Filter bean,DelegatingFilterProxy會將過濾邏輯委托給它。

????如果你希望借助WebApplicationInitializer以Java的方式來配置DelegatingFilterProxy的話,那么我們所需要做的就是創(chuàng)建一個擴展的新類:

WebApplicationInitializer

????AbstractSecurityWebApplicationInitializer實現(xiàn)了WebApplicationInitializer,因此Spring會發(fā)現(xiàn)它,并用它在Web容器中注冊DelegatingFilterProxy。盡管我們可以override它的appendFilters()或insertFilters()來注冊資金選擇的Filter,但是要注冊DelegatingFilterProxy的話我們并不需要重載任何方法。

????不管我們通過web.xml還是通過AbstractSecurityWebApplicationInitializer的子類來配置DelegatingFilterProxy,它都會攔截發(fā)往應(yīng)用中的請求,并將請求委托給ID為springSecurityFilterChain的bean。

????springSecurityFilterChain本身是另一個特色的Filter,它也被稱為FilterChainProxy。它可以鏈接任意一個或多個其他的Filter。Spring Security依賴一系列Servlet Filter來提供不同的安全特性。但是,你幾乎不需要知道這些細節(jié),因為你不需要顯式聲明springSecurityFilterChain以及它所鏈接在一起的其他Filter。當我們啟用Web安全性的時候,會自動創(chuàng)建這些Filter。

為了讓Web安全性運行起來,我們創(chuàng)建一個簡單的安全性配置。

編寫簡單的安全性

????在Spring Security的早期版本中(在其還被稱為Accegi Security之時),為了在Web應(yīng)用中啟用簡單的安全功能,我們需要編寫上百行的XML配置。Spring Security 2.0提供了安全性相關(guān)的XML配置命名空間,讓情況有了一些好轉(zhuǎn)。

????Spring 3.2引入了新的Java配置方案,完全不再需要通過XML來配置安全性功能了。如下的程序清單展現(xiàn)了Spring Security最簡單的Java配置。

程序清單9.1

????顧名思義,@EnableWebSecurity注解將會啟用Web安全功能。但它本身并沒有什么用處,Spring Security必須配置在一個實現(xiàn)了WebSecurityConfugurer的bean中,或者(簡單起見)擴展WebSecurityConfigurerAdater。在Spring應(yīng)用上下文中,任何實現(xiàn)了WebSecurityConfigurer的bean都可以用來配置Spring Security,但是最為簡單的方式還是像程序清單9.1那樣擴展WebSecurityConfigurerAdapter類。

????@EnableWebSecurity可以啟用任意Web應(yīng)用的安全性功能,不過,如果你的應(yīng)用碰巧是使用Spring MVC開發(fā)的,那么就應(yīng)該考慮使用@EnableWebMvcSecurity替代它,如程序清單9.1所示。

程序清單9.2

????除了其他的內(nèi)容以外,@EnableWebMvcSecurity注解還配置了一個Spring MVC參數(shù)解析器(argument resolver),這樣的話處理器方法就能夠通過帶有@AuthenticationPrincipal注解的參數(shù)獲得認證用戶的principal(或username)。它同時還配置了一個bean,在使用Spring表單綁定標簽庫來定義表單時,這個bean會自動添加一個隱藏的跨站請求偽造(cross-site request forgery,CSRF)token輸入域。

????看起來似乎并沒有做太多的事情,但程序清單9.1和9.2中的配置類會給應(yīng)用產(chǎn)生很大的影響。其中任何一種配置都會將應(yīng)用嚴格鎖定,導致沒有人能夠進入該系統(tǒng)了!

????盡管部署嚴格要求的,但是我們可能希望指定Web安全的細節(jié),這要通過override WebSecurityConfigurerAdapter中的一個或多個方法來實現(xiàn)。我們可以通過override WebSecurityConfigurerAdapter的三個configure()方法來配置Web安全性,這個過程中會使用傳遞進來的參數(shù)設(shè)置行為。表9.2描述了這三個方法。

三個configure

??????configure(WebSecurity):通過override,配置Spring Security的Filter鏈。

??????configure(HttpSecurity):通過override,配置如何通過攔截器保護請求。

??????configure(AuthenticationManagerBuilder):通過override,配置user-detail服務(wù)。

????讓我們重新看一下程序清單9.2,可以看到它沒有override上述三個configure()方法中的任何一個,這就說明了為什么應(yīng)用現(xiàn)狀是被鎖定的。盡管對于我們的需求來講默認的Filter鏈是不錯的,但是默認的configure(HttpSecurity)實際上等同于如下所示。

默認實現(xiàn)

????這個簡單的默認配置指定了該如何保護HTTP請求,以及客戶端認證用戶的方案。通過調(diào)用authorizeRequests()和anyRequest().authenticated()就會要求所有進入應(yīng)用的HTTP請求都要進行認證。它也配置Spring Security支持基于表單的登錄以及HTTP Basic方式的認證。

????同時,因為沒有override configure(AuthenticationManagerBuilder)方法,所以沒有用戶存儲支持認證過程。沒有用戶存儲。實際上就等于沒有用戶。所以,在這里所有的請求都需要認證,但是沒有人能夠登錄成功。

為了讓Spring Security滿足我們應(yīng)用的需求,還需要再添加一點配置。具體來講,我們需要:

??????配置用戶存儲;

??????指定哪些請求需要認證,哪些請求不需要認證,以及所需要的權(quán)限;

??????提供一個自定義的登錄頁面,替代原來簡單的默認登錄頁。

????除了Spring Security的這些功能,我們可能還希望基于安全限制,有選擇性地在Web視圖上顯示特定的內(nèi)容。

但首先,我們看一下如何在認證的過程中配置訪問用戶數(shù)據(jù)的服務(wù)。

選擇查詢用戶詳細的服務(wù)

????我們所需要的是用戶存儲,也就是用戶名、密碼以及其他信息存儲的地方,在進行認證決策的時候,會對其進行檢索。

????好消息是,Spring Security非常靈活、能夠基于各種數(shù)據(jù)存儲來認證用戶,在進行了多種常見用戶存儲場景,如內(nèi)存、關(guān)系型數(shù)據(jù)庫以及LDAP。但我們也可以編寫并插入自定義的用戶存儲實現(xiàn)。

????借助Spring Security的Java配置,我們能夠很容易地配置一個或多個數(shù)據(jù)存儲方案。那我們就從最簡單的開始:在內(nèi)存中維護用戶存儲。

使用基于內(nèi)存的用戶存儲

????因為我們的安全配置擴展了WebSecurityConfigurerAdapter,因此配置用戶存儲的最簡單方式就是overrideconfigure()方法,并以AuthenticationManagerBuilder作為傳入?yún)?shù)。AuthenticationManagerBuilder有多個方法可以用來配置Spring Security對認證的支持。通過inMemoryAuthentication()方法,我們可以啟用、配置并任意填充基于內(nèi)存的用戶存儲。

????例如,在如程序清單9.3中,SecurityConfig override了configure()方法,并使用兩個用戶來配置內(nèi)存用戶存儲。

程序清單9.3 使用內(nèi)存用戶存儲

操作補充:

測試接口
調(diào)用測試

????我們可以看到configure()方法中的AuthenticationManagerBuilder使用構(gòu)造者風格的接口來構(gòu)建風格的接口來構(gòu)建認證配置。通過簡單地調(diào)用inMemoryAuthentication()就能啟用內(nèi)存用戶存儲。但是我們還需要有一些用戶,否則的話,這和沒有用戶并沒有什么區(qū)別。

源碼

????因此,我們需要調(diào)用withUser()方法返回的是UserDetailsManagerConfigurer.UserDetailsBuilder,這個對象提供了多個進一步配置用戶的方法,包括設(shè)置用戶密碼的password()方法以及為給定用戶授予一個或多個角色權(quán)限的roles()方法。

????在代碼清單9.3中,我們添加了兩個用戶,“user”和“admin”,密碼均為“password”?!皍ser”用戶具有USER角色,而“admin”用戶具有ADMIN和USER兩個角色。我們可以看到,and()方法能夠?qū)⒍鄠€用戶的配置連接起來。

????除了password()、roles()和and()方法以外,還有其他的幾個方法可以用來配置內(nèi)存用戶存儲中的用戶信息。以下描述了UserDetailsManagerCondigurer.UserDetailsBuilder對象所有可用的方法。

????????????方法????????????????????????????????????????????????描述

accountExpired(boolean)????????????????定義賬號是否已經(jīng)過期

accountLocked(boolean)????????????????定義賬號是否已經(jīng)鎖定

and() ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?用來連接配置

authorities(GrantedAuthority...)????授予某個用戶一項或多項權(quán)限

authorities(List)????????????????????????????????授予某個用戶一項或多項權(quán)限

authorities(String...)????????????????????????授予某個用戶一項或多項權(quán)限

credentialsExpired(boolean) ? ? ? ? ? 定義憑證是否已經(jīng)過期

disabled(boolean)?? ? ? ? ? ? ? ? ? ? ? ? ? ? 定義賬號是否已被禁用

password(String)? ? ? ? ? ? ? ? ? ? ? ? ? ? ?定義用戶的密碼

username(String)? ? ? ? ? ? ? ? ? ? ? ? ? ? ?定義用戶的用戶名

roles(String...)?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 授予某個用戶一項或多項角色

源碼

????對于調(diào)試和開發(fā)人員測試來講,基于內(nèi)存的用戶存儲是很有用的,但是對于生成級別的應(yīng)用來講,這就不是最理想的可選方案了。為了用于生產(chǎn)環(huán)境,通常最好講用戶數(shù)據(jù)報錯在某種類型的數(shù)據(jù)庫之中。

基于數(shù)據(jù)庫表進行認證

????用戶數(shù)據(jù)通常會存儲在關(guān)系型數(shù)據(jù)庫中,并通過JDBC進行訪問。為了配置Spring Security使用以JDBC為支撐的用戶存儲,我們可以使用jdbcAuthentication()方法,所需的最少配置如下所示:

使用jdbcAuthentication()方法

????我們必須要配置的只是一個DataSource,這樣的話,就能訪問關(guān)系型數(shù)據(jù)庫了。在這里,DataSource是通過自動裝配的技巧得到的。

重寫默認的用戶查詢功能

????盡管默認的最少配置能夠讓一切運轉(zhuǎn)起來,但是它對我們的數(shù)據(jù)庫模式有一些要求,它預期存在某些存儲用戶數(shù)據(jù)的表。下面的代碼片段來源于Spring Security內(nèi)部,這塊代碼展現(xiàn)了當查找用戶信息時所執(zhí)行的SQL查詢語句:

默認sql

????在第一個查詢中,我們獲取了用戶的用戶名、密碼以及是否啟用的信息,這些信息會用來進行用戶認證。接下來的查詢查找了用戶所授予的權(quán)限,用來進行鑒權(quán),最后一個查詢中,查找了用戶作為群組成員所授予的權(quán)限。

補充:源碼中的建表SQL

建表sql

????如果你能夠在數(shù)據(jù)庫中定義和填充滿足這些查詢的表,那么基本上就不需要你再做什么額外的事情了。但是,也有可能你的數(shù)據(jù)庫與上面所述并不一致,那么你就會希望在查詢上有更多的控制權(quán)。如果是這樣的話,我們可以按照如下的方式配置自己的查詢:

數(shù)據(jù)庫查詢

????在本例中,我們只重寫了認證和基本權(quán)限的查詢語句,但是通過調(diào)用groupAuthoritiesByUsername()方法,我們也能夠講群組權(quán)限重寫為自定義的查詢語句。

????將默認的SQL查詢替換為自定義的設(shè)計時,很重要的一點就是要遵循查詢的基本協(xié)議。所有查詢都將用戶名作為唯一的參數(shù)。認證查詢會選取用戶名、密碼以及啟用狀態(tài)信息。權(quán)限查詢會選取零行或多行數(shù)據(jù),每行數(shù)據(jù)中都會包含群組ID、群組名稱以及權(quán)限。

補充:

權(quán)限表
user 表


程序啟動
調(diào)用測試

使用轉(zhuǎn)碼后的密碼

????看一下上面的認證查詢,它會預期用戶密碼存儲在了數(shù)據(jù)庫之中。這里唯一的問題在于如果密碼明文存儲的話,會很容易受到黑客的竊取。但是,如果數(shù)據(jù)庫中的密碼進行了轉(zhuǎn)碼的話,那么認證就會失敗,因為它與用戶提交的明文密碼并不匹配。

????為了解決這個問題,我們需要借助passwordEncoder()方法指定一個密碼轉(zhuǎn)碼器(encoder)。

密碼加密

????passwordEncoder()方法可以接受Spring Security中PasswordEncoder接口的任意實現(xiàn)。Spring Security的加密模塊包括了三個這樣的實現(xiàn):BCryptPasswordEncoder、NoOpPasswordEncoder和StandardPasswordEncoder。

????上述的代碼中使用了StandardPasswordEncoder,但是如果內(nèi)置的實現(xiàn)無法滿足需求時,你可以提供自定義的實現(xiàn)。PasswordEncoder接口非常簡單:

源碼

????不管你使用哪一個密碼轉(zhuǎn)碼器,都需要理解的一點是,數(shù)據(jù)庫中的密碼是永遠不會解碼的。所采取的策略與之相反,用戶在登錄時輸入的密碼會按照相同的算法進行轉(zhuǎn)碼,然后再與數(shù)據(jù)庫中已經(jīng)轉(zhuǎn)碼過的密碼進行對比。這個比對是在PasswordEncoder的matchers()方法中進行的。

配置自定義的用戶服務(wù)

????假設(shè)我們需要認證的用戶存儲在非關(guān)系型數(shù)據(jù)庫中,如Mongo或Neoj4,在這種情況下,我們需要提供一個自定義的UserDetailsService接口實現(xiàn)。

UserDetailsService接口非常簡單:

UserDetailsService接口

????我們所需要做的就是實現(xiàn)loadUserByUsername()方法,根據(jù)給定的用戶名來查找用戶。loadUserByUsername()方法會返回代表給定用戶的UserDetails對象。如下的程序清單展現(xiàn)了一個UserDetailsService的實現(xiàn),它會從給定的SpitterRepository實現(xiàn)中查找用戶。

程序清單9.4 從數(shù)據(jù)庫中查找UserDetails對象

????UserServiceImpl有意思的地方在于它并不知道用戶數(shù)據(jù)存儲在什么地方。設(shè)置進來的userDao能夠從關(guān)系型數(shù)據(jù)庫,文檔數(shù)據(jù)庫或圖數(shù)據(jù)中查找UserEntry對象,甚至可以偽造一個。UserServiceImpl不知道也不會關(guān)心底層所使用的數(shù)據(jù)存儲。它只是獲得UserEntry對象,并使用它來創(chuàng)建User對象。(User是UserDetails的具體實現(xiàn))。

????為了使用UserServiceImpl來認證用戶,我們可以通過userDetailsService()方法將其設(shè)置倒安全配置中:

UserServiceImpl來認證用戶

????userDetailsService()方法(類似于jdbcAuthentication()、ldapAuthentication()以及inMemoryAuthentication())會配置一個用戶存儲。不過,這里所使用的不是Spring所提供的用戶存儲,而是使用UserDetailsService的實現(xiàn)。

????另外一個值得考慮的方案就是修改UserEntry,讓其實現(xiàn)UserDetails。這樣的話loadUserByUsername()就能直接返回UserEntry對象了,而不必再將它的值復制到 User對象中。

攔截請求

????在前面的9.1.3小節(jié)中,我們看到一個特別簡單的Spring Security配置,在這個默認的配置中,回要求所有請求都要經(jīng)過認證。有些人可能會說,過多的安全性總比安全性太少要好。但也有一種說法就是要適量地應(yīng)用安全性。

????在任何應(yīng)用中,并不是所有的請求都需要同等程度地保護。有些請求需要認證,而另一些可能并不需要。有些請求可能只有具備特定權(quán)限的用戶才能訪問,沒有這些權(quán)限的用戶無法訪問。

????例如,考慮Spittr應(yīng)用的請求。首先當然是公開的,不需要進行保護。類似地,因為所有的Spittle都是公開的,所以展現(xiàn)Spittle的頁面不需要安全性。但是,創(chuàng)建Spittle的請求只有認證用戶才能知曉。同樣,盡管用戶基本信息頁面是公開的,不需要認證,但是,如果要處理“/spitter/me”請求,并展現(xiàn)當前用戶的基本信息時,那么就需要進行認證,從而確定要展現(xiàn)誰的信息。

????對每個請求進行細粒度安全性控制的關(guān)鍵在于overrideconfigure(HttpSecurity)方法。如下的代碼片段展現(xiàn)了override的configure(HttpSecurity)方法,它為不同的URL路徑有選擇地應(yīng)用安全性:

overrideconfigure(HttpSecurity)方法

????configure()方法中得到的HttpSecurity對象可以在多個方面配置HTTP的安全性。在這里,我們首先調(diào)用authorizeRequests(),然后調(diào)用該方法所返回的對象的方法來配置請求級別的安全性細節(jié)。其中,第一次調(diào)用antMatchers()更為具體,說明對"security"路徑的請求需要進行認證。第二次調(diào)用antMatchers()更為具體,說明對"/spittle"路徑的HTTP POST請求必須要經(jīng)過認證。最后對anyRequests()的調(diào)用中,說明其他所有的請求都是允許的,不需要認證和任何的權(quán)限。

????antMatchers()方法中設(shè)定的路徑支持Ant風格的通配符。在這里我們并沒有這樣使用,但是也可以使用通配符來指定路徑,如下所示:

????????.antMatchers("/security/**").authenticated();

我們也可以在一個對antMatchers()方法的調(diào)用中指定多個路徑:

????????.antMatchers("/security/**","/security/test").authenticated();

????antMatchers()方法所使用的路徑可能會包括Ant風格的通配符,而regexMatchers()方法則能夠接受正則表達式來定義請求路徑。例如,如下代碼片段所使用的正則表達式與"/security/**"(Ant風格)功能是相同的:

? ? ? ? ? ?.regexMatchers("/security/.**").authenticated();

????除了路徑選擇,我們還通過authenticated()和permitAll()來定義該如何保護路徑。authenticated()要求在執(zhí)行該請求時,必須已經(jīng)登錄了應(yīng)用。如果用戶沒有認證時,permitAll()方法允許請求沒有任何的安全限制。

????除了authenticated()和permitAll()以外,還有其他的一些方法能夠涌來定義該如何保護請求,如下表描述了所有可用的方案。

? ??????????????????????????定義如何保護路徑的配置方法

方法????????????????????????????????????????????????????????能夠做什么

access(String)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 如果給定的SpEL表達式計算結(jié)果為true,就允許訪問

anonymous()????????????????????????????????????????允許匿名用戶訪問

authenticated()????????????????????????????????????允許經(jīng)過認證的用戶訪問

denyAll()????????????????????????????????????????????????無條件拒絕所有訪問

fullyAuthenticated()????????????????????????????如果用戶是完整的話(不是通過Remember-me功能認證的),就允許訪問

hasAnyAuthority(String...)? ? ? ? ? ? ? ? ?如果用戶具備給定權(quán)限中的某一個的話,就允許訪問

hasAnyRole(String...)?? ? ? ? ? ? ? ? ? ? ? ? ?如果用戶具備給定角色中的某一個的話,就允許訪問

hasAuthority(String)? ? ? ? ? ? ? ? ? ? ? ? ? ? 如果用戶具備給定權(quán)限的話,就允許訪問

hasIpAddress(String)?? ? ? ? ? ? ? ? ? ? ? ? ? 如果請求來自給定IP地址的話,就允許訪問

hasRole(String)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?如果用戶具備給定角色的話,就允許訪問

not()? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?對其他訪問方法的結(jié)果球反

permitAll() ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?無條件允許訪問

rememberMe()?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?如果用戶是通過Remember-me功能認證的,就允許訪問

????通過使用上表中的方法,我們所配置的安全性能夠不僅僅限于認證用戶。例如,我們可以修改之前的configure()方法,要求用戶不僅需要認證,還要具備ROLE_SECURITY權(quán)限: ? ?

hasAuthority()方法

作為替代方案,我們還可以使用hasRole()方法,它會自動適應(yīng)“ROLE_”前綴:

hasRole()方法

????我們可以將任意數(shù)量的antMatchers()、regexMatchers()和anyRequest()連起來,以滿足Web應(yīng)用安全規(guī)則的需要。但是,我們需要知道,這些規(guī)則會按照給定的順序發(fā)揮作用。所以說,很重要的一點就是將最為具體的請求路徑放在前面,而最不具體的路徑(anyReuquest())放在最后面。如果不這樣的話,那部具體的路徑配置將會覆蓋掉更為具體的路徑配置。

使用Spring表達式進行安全保護

????上表中的大多數(shù)法官法都是一維的,也就是說我們可以使用hasRole()限制某個特定的角色,但是我們不能在相同的路徑上同時通過hasIpAddress()限制特定的IP地址。

????另外,除了上表定義的方法以外,我們沒有方法使用其他的條件。如果我們希望限制某個角色智能在星期二進行訪問的話,該怎么辦呢?

????在第3章中,我們看到了如何使用Spring表達式語言(Spring Expression Language,SpEl),將其作為裝配bean屬性的高級技師。借助access()方法,我們也可以將SpEL作為聲明訪問限制的一種方式。例如,如下就是使用SpEL表達式來聲明聲明具有“ROLE_SECURITY”角色才能訪問"/security/test"URL:

????????????????.antMatchers("/security/test").access("hasRole('ROLE_SECURITY')")

????這個對"/security/test"的安全限制與開始時代效果時等價的,只不過這里使用了SpEL來描述安全規(guī)則。如果當前用戶唄授予了給定角色的話,那hasRole()表達式的計算結(jié)果就為true。

????讓SpEL更強大的原因在于,hasRole()僅是Spring支持的安全相關(guān)表達式中的一種,下表列出了Spring Security支持的所有SpEL表達式。

? ??????????Spring Security通過一些安全性相關(guān)的表達式擴展了Spring表達式語言

安全表達式 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?計算結(jié)果

authentication?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?用戶的認證對象

denyAll? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?結(jié)果始終為false

hasAnyRole(list of roles)? ? ? ? ? ? ? ? ? ? 如果用戶被授予了列表中任意的指定角色,結(jié)果為true

hasRole(role)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 如果用戶被授予了指定的角色,結(jié)果為true

hasIpAddress(IP Address) ?? ? ? ? ? ? ? ?如果請求來自指定IP的話,結(jié)果為true

isAnonymous()? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?如果當前用戶為匿名用戶,結(jié)果為true

isAuthenticated()?? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 如果當前用戶進行了認證的話,結(jié)果為true

isFullyAuthenticated()??? ? ? ? ? ? ? ? ? ? ? 如果當前用戶進行了完整認證的話(不是Remember-me),結(jié)果為true

isRememberMe()? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?如果當前用戶是通過Remember-me自動認證的,結(jié)果為true

permitAll ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 結(jié)果始終為true

principal? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 用戶的principal對象

????在掌握了Spring Security的SpEL表達式后,我們就能夠不再局限于機遇用戶的權(quán)限逆襲訪問限制了。例如,如果你想限制"/security/test" URL的訪問,不僅需要ROLE_SECURITY,還需要來自指定的IP地址,那么我們可以按照如下的方式調(diào)用access()方法。

access()方法

????我們可以使用SpEL實現(xiàn)各種各樣的安全性限制。我敢打賭,你已經(jīng)在想象基于SpEL所能實現(xiàn)的那些有趣的安全性限制了。

但現(xiàn)在,讓我們看一下Spring Security攔截請求的另外一種方式:強制通道的安全性。

強制通道的安全

????使用HTTP提交數(shù)據(jù)是一件具有風險的事情。如果使用HTTP發(fā)送無關(guān)緊要的信息,這可能不是什么大問題。但是如果你通過HTTP發(fā)送諸如密碼和信用卡號這樣的敏感信息的話,那你就是在找麻煩了。通過HTTP發(fā)送的數(shù)據(jù)沒有經(jīng)過加密,黑客就有機會攔截請求并且能夠看到他們想看的數(shù)據(jù)。這就是為什么敏感信息要通過HTTPS來加密發(fā)送的原因。

????使用HTTPS似乎很簡單。你要的事情知識在URL中的HTTP后加上一個字母“s”就可以了。是這樣嗎?

????這是真的,但這是把使用HTTPS通道的責任放在了錯誤的地方。通過添加“s”我們就能很容易地實現(xiàn)頁面的安全性,但是忘記添加“s”同樣也是很容易出現(xiàn)的。如果我們的應(yīng)用中有多個鏈接需要HTTPS,估計在其中的一兩個上忘記添加“s”的概率還是很高的。

另一方面,你可能還會在原本并步需要HTTPS的地方,誤用HTTPS。

????傳遞到configure()方法中的HttpSecurity對象,除了具有authorizeRequests()方法以外,還有requiresChannel()方法,借助這個方法能夠為各種URL模式聲明所要求的通道。

????作為示例,可以參考security應(yīng)用的注冊表單。盡管security應(yīng)用步需要信用卡號、社會保障好或其他敏感的信息,但用戶有可能仍然希望信息是私密的。為了保證注冊表單的數(shù)據(jù)通過HTTPS傳遞,我們可以在配置中添加requiresChannel()方法,如下所示:

添加requiresChannel()方法

????不論何時,只要是對"/security/form"的請求,Spring Security都視為需要安全通道(通過調(diào)用requiresChannel()確定的)并自動將請求重定向到HTTPS上。

????與之相反,有些頁面并不需要通過HTTPS傳送。例如,首頁包含任何敏感信息,因此并步需要通過HTTPS傳送。我們可以使用requiresInsecure()代替requiresSecure()方法,將首頁聲明為始終通過HTTP傳送:

????????????????????.antMatchers("/").requiresInsecure();

????如果通過HTTPS發(fā)送了對“/”的請求,Spring Security將會把請求重定向到不安全的HTTP通道上。

????在強制要求通道時,路徑的選取方案與authorizeRquests()是相同的。使用了antMatchers(),但是我們也可以使用regexMatchers()方法,通過正則表達式選取路徑模式。

使用HTTP Basic認證

????對于應(yīng)用程序的人類用戶來說,基于表單的認證是比較理想的。但是在第16章中,將會看到如何將我們Web應(yīng)用的頁面轉(zhuǎn)化為RESTful API。當應(yīng)用程序的使用者是另外一個應(yīng)用程序的話,使用表單來提示登錄的方式就不太合適了。

????HTTP Basic認證(HTTP Basic Authentication)會直接通過HTTP請求本書,對藥訪問應(yīng)用程序的用戶進行認證。你可能在以前見過HTTP 401響應(yīng),表明必須要在請求中包含一個用戶名和密碼。在REST客戶端向它使用的服務(wù)進行認證的場景中,這種方式比較合適。

????如果要啟用HTTP Basic認證的話,只需在configure()方法所傳入的HttpSecurity對象調(diào)用httpBasic()即可。另外,還可以通過調(diào)用realmName()方法指定域。如下是在Spring Security中啟用HTTP Basic認證的典型配置:

HTTP Basic認證

????注意:和前面意義,在configure()方法中,通過調(diào)用and()方法來將不同的配置指令連接在一起。

spring security 動態(tài)配置url權(quán)限

????這里我們要操作的是FilterSecurityInterceptor這個interceptor,使用withObjectPostProcessor來設(shè)置。

這個filters有幾個要素,如下:

??????????SecurityMetadataSource

? ? ? ? ???AccessDecisionManager

??????????AuthenticaManager

????可以根據(jù)情況自己去重新設(shè)置,這里我們重寫一下SecurityMetadataSource用來動態(tài)獲取url權(quán)限配置,還有AccessDecisionManager來進行權(quán)限判斷。

????這里遍歷判斷url所需的角色看用戶是否具備,有具備則返回,都不具備則AccessDeniedException異常。

MyFilterInvocationSecurityMetadataSource

MyFilterInvocationSecurityMetadataSource

????這里以內(nèi)存的map來展示一下,實際應(yīng)用可以從分布式配置中心或者數(shù)據(jù)庫中讀區(qū),另外循環(huán)遍歷這個可能很消耗性能,必要時得優(yōu)化一下。

MySecurityConfig

MySecurityConfig

異常記錄

??異常:IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

? ? ? spring security 5的密碼存儲格式改了,要求我們提供一個密碼編碼器。我們必須進行如下配置,數(shù)據(jù)庫保存的密碼必須是加密之后的。

添加密碼編碼器

如果需要給我修改意見的發(fā)送郵箱:erghjmncq6643981@163.com

本博客的代碼示例已上傳GitHub:分布式配置中心

資料參考:《Spring in cation》,《Spring Cloud 微服務(wù)實戰(zhàn)》

轉(zhuǎn)發(fā)博客,請注明,謝謝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容