SpringBoot集成Spring Security的方法
至今Java能夠如此的火爆Spring做出了很大的貢獻(xiàn),它的出現(xiàn)讓Java程序的編寫更為簡(jiǎn)單靈活,而Spring如今也形成了自己的生態(tài)圈,今天咱們探討的是Spring旗下的一個(gè)款認(rèn)證工具:SpringSecurity,如今認(rèn)證框架主流“shiro”和“SpringSecurity”,由于和Spring的無(wú)縫銜接,使用SpringSecurity的企業(yè)也越來(lái)越多。
1、Spring Security介紹
Spring security,是一個(gè)強(qiáng)大的和高度可定制的身份驗(yàn)證和訪問(wèn)控制框架。它是確保基于Spring的應(yīng)用程序的標(biāo)準(zhǔn)——來(lái)自官方參考手冊(cè)
Spring security 和 shiro 一樣,具有認(rèn)證、授權(quán)、加密等用于權(quán)限管理的功能。和 shiro 不同的是,Spring security擁有比shiro更豐富的功能,并且,對(duì)于Springboot而言,Spring Security比Shiro更合適一些,因?yàn)槎际荢pring家族成員。今天,我們來(lái)為SpringBoot項(xiàng)目集成Spring Security。
本文所使用的版本:
SpringBoot : 2.2.6.RELEASESpring Security : 5.2.2.RELEASE
2、配置Spring Security
在SpringBoot中集成Spring Security很簡(jiǎn)單,只需要在pom.xml中添加下面代碼就行:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency>
這里可以不指定Spring Security的版本號(hào),它會(huì)根據(jù)SpringBoot的版本來(lái)匹配對(duì)應(yīng)的版本,SpringBoot版本是 2.2.6.RELEASE,對(duì)應(yīng)Spring Security的版本是5.2.2.RELEASE。
然后,我們就可以將springboot啟動(dòng)了。
當(dāng)我們嘗試訪問(wèn)項(xiàng)目時(shí),它會(huì)跳轉(zhuǎn)到這個(gè)界面來(lái):
對(duì)!在此之前,你什么也不用做。這就是Spring Security的優(yōu)雅之處。你只需要引入Spring Security的包,它就能在你的項(xiàng)目中工作。因?yàn)樗呀?jīng)幫你實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的登陸界面。根據(jù)官方介紹,登錄使用的賬號(hào)是user,密碼是隨機(jī)密碼,這個(gè)隨機(jī)密碼可以在控制臺(tái)中找到,類似這樣的一句話:
Using generated security password: 1cb77bc5-8d74-4846-9b6c-4813389ce096
Using generated security password后面的的就是系統(tǒng)給的隨機(jī)密碼,我們可以使用這個(gè)密碼進(jìn)行登錄。隨機(jī)密碼在每一次啟動(dòng)服務(wù)后生成(如果你配置了熱部署devtools,你得隨時(shí)留意控制臺(tái)了,因?yàn)槊慨?dāng)你修改了代碼,系統(tǒng)會(huì)自動(dòng)重啟,那時(shí)隨機(jī)密碼就會(huì)重新生成)。
當(dāng)然,這樣的功能一定不是你想要的,也一定不會(huì)就這樣拿給你的用戶使用。那么,接下來(lái),讓我們把它配置成我們想要的樣子。
要實(shí)現(xiàn)自定義配置,首先要?jiǎng)?chuàng)建一個(gè)繼承于WebSecurityConfigurerAdapter的配置類:
import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {}
這里使用了@EnableWebSecurity注解,這個(gè)注解是Spring Security用于啟用web安全的注解。具體實(shí)現(xiàn),這里就不深入了。
要實(shí)現(xiàn)自定義攔截配置,首先得告訴Spring Security,用戶信息從哪里獲取,以及用戶對(duì)應(yīng)的角色等信息。這里就需要重寫WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder auth)方法了。這個(gè)方法將指使Spring Security去找到用戶列表,然后再與想要通過(guò)攔截器的用戶進(jìn)行比對(duì),再進(jìn)行下面的步驟。
Spring Security的用戶存儲(chǔ)配置有多個(gè)方案可以選擇,包括:
內(nèi)存用戶存儲(chǔ) 數(shù)據(jù)庫(kù)用戶存儲(chǔ) LDAP用戶存儲(chǔ) 自定義用戶存儲(chǔ)我們分別來(lái)看看這幾種用戶存儲(chǔ)的配置方法:
1.內(nèi)存用戶存儲(chǔ)
此配置方式是直接將用戶信息存儲(chǔ)在內(nèi)存中,這種方式在速度上無(wú)疑是最快的。但只適用于有限個(gè)用戶數(shù)量,且這些用戶幾乎不會(huì)發(fā)生改變。我們來(lái)看看配置方法:
@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().passwordEncoder(passwordEncoder()).withUser('zhangsan').password(passwordEncoder().encode('123456')).authorities('ADMIN').and().withUser('lisi').password(passwordEncoder().encode('123456')).authorities('ORDINARY');}private PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
可以看到,AuthenticationManagerBuilder使用構(gòu)造者方式來(lái)構(gòu)建的。在上面方法中,先調(diào)用了inMemoryAuthentication()方法,它來(lái)指定用戶存儲(chǔ)在內(nèi)存中。接下來(lái)又調(diào)用了passwordEncoder()方法,這個(gè)方法的作用是告訴Spring Security認(rèn)證密碼的加密方式。因?yàn)樵赟pring security5過(guò)后,必須指定某種加密方式,不然程序會(huì)報(bào)錯(cuò)。接下來(lái)調(diào)用的withUser()、password()、authorities()方法,分別是在指定用戶的賬號(hào)、密碼以及權(quán)限名。在添加完一個(gè)用戶后,要使用and()方法來(lái)連接下一個(gè)用戶的添加。
如果使用這種配置方法,你會(huì)發(fā)現(xiàn),在修改用戶時(shí),就必須修改代碼。對(duì)于絕大多數(shù)項(xiàng)目來(lái)說(shuō),這種方式是滿足不了需求的,至少我們需要一個(gè)注冊(cè)功能。
2.數(shù)據(jù)庫(kù)用戶存儲(chǔ)
將用戶信息存儲(chǔ)在數(shù)據(jù)庫(kù)中,讓我們可以很方便地對(duì)用戶信息進(jìn)行增刪改查。并且還可以為用戶添加除認(rèn)證信息外的附加信息,這樣的設(shè)計(jì)也是我們很多小心應(yīng)用所采取的方式。讓我們來(lái)實(shí)現(xiàn)以下:
@Autowiredprivate DataSource dataSource;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.jdbcAuthentication().dataSource(dataSource).passwordEncoder(passwordEncoder()).usersByUsernameQuery('select username, password, status from Users where username = ?').authoritiesByUsernameQuery('select username, authority from Authority where username = ?');}private PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
調(diào)用jdbcAuthentication()來(lái)告訴Spring Security使用jdbc的方式來(lái)查詢用戶和權(quán)限,dataSource()方法指定數(shù)據(jù)庫(kù)連接信息,passwordEncoder()指定密碼加密規(guī)則,用戶的密碼數(shù)據(jù)應(yīng)該以同樣的方式進(jìn)行加密存儲(chǔ),不然,兩個(gè)加密方式不同的密碼,匹配補(bǔ)上。usersByUsernameQuery()和authoritiesByUsernameQuery()方法分別定義了查詢用戶和權(quán)限信息的sql語(yǔ)句。其實(shí),Spring security為我們默認(rèn)了查詢用戶、權(quán)限甚至還有群組用戶授權(quán)的sql,這三條默認(rèn)的sql存放在org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl中,有興趣的小伙伴可以點(diǎn)進(jìn)去看看。如果你要使用默認(rèn)的,那你的表中關(guān)鍵性的字段必須和語(yǔ)句中的一致。
使用數(shù)據(jù)庫(kù)來(lái)存儲(chǔ)用戶和權(quán)限等信息已經(jīng)可以滿足大部分的需求。但是Spring security還為我們提供了另外一種配置方式,讓我們來(lái)看一下。
3.LDAP用戶存儲(chǔ)
LDAP:輕型目錄訪問(wèn)協(xié)議,是一個(gè)開放的,中立的,工業(yè)標(biāo)準(zhǔn)的應(yīng)用協(xié)議,通過(guò)IP協(xié)議提供訪問(wèn)控制和維護(hù)分布式信息的目錄信息。簡(jiǎn)單來(lái)說(shuō),就是將用戶信息存放在另外一臺(tái)服務(wù)器中(當(dāng)然,也可以在同一臺(tái)服務(wù)器,但我們一般不這么做),通過(guò)網(wǎng)絡(luò)來(lái)進(jìn)行訪問(wèn)的技術(shù)。
我們來(lái)簡(jiǎn)單配置一下:
@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> configurer = auth.ldapAuthentication().userSearchBase('ou=people').userSearchFilter('(uid={0})').groupSearchBase('ou=groups').groupSearchFilter('member={0}');configurer.passwordCompare().passwordEncoder(passwordEncoder()).passwordAttribute('passcode');configurer.contextSource().url('ldap://xxxxx.com:33389/dc=xxxxxx,dc=com');}private PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }
userSearchFilter()和groupSearchFilter()設(shè)置的是用戶和群組的過(guò)濾條件,而userSearchBase()和groupSearchBase()設(shè)置了搜索起始位置,contextSource().url()設(shè)置LDAP服務(wù)器的地址。如果沒(méi)有遠(yuǎn)程的服務(wù)器可以使用contextSource().root()來(lái)使用嵌入式LDAP服務(wù)器,此方式將使用項(xiàng)目中的用戶數(shù)據(jù)文件來(lái)提供認(rèn)證服務(wù)。
如果以上幾種方式還不能滿足我們的需求,我們可以用自定義的方式來(lái)配置。
4.自定義用戶存儲(chǔ)
自定義用戶存儲(chǔ),就是自行使用認(rèn)證名稱來(lái)查找對(duì)應(yīng)的用戶數(shù)據(jù),然后交給Spring Security使用。我們需要定義一個(gè)實(shí)現(xiàn)UserDetailsService的service類:
@Servicepublic class MyUserDetailsService implements UserDetailsService{@Autowiredprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userMapper.getUserByUsername(username);return user == null ? new User() : user;}}public class User implements UserDetails { ...}
該類只需要實(shí)現(xiàn)一個(gè)方法:loadUserByUsername()。該方法需要做的是使用傳過(guò)來(lái)的username來(lái)匹配一個(gè)帶有密碼等信息的用戶實(shí)體。需要注意的是這里的User類需要實(shí)現(xiàn)UserDetails,也就是說(shuō),查到的信息里,必須得有Spring Security所需要的信息。
下面,讓我們來(lái)繼續(xù)配置:
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate MyUserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());}@Beanprivate PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }}
這樣的配置方法就很簡(jiǎn)單了,只需要告訴Spring Security你的UserDetailsService實(shí)現(xiàn)類是哪個(gè)就可以了,它會(huì)去調(diào)用loadUserByUsername()來(lái)查找用戶。
以上就是Spring Security所提供的4種用戶存儲(chǔ)方式,接下來(lái),需要考慮的是,怎么攔截請(qǐng)求。
3、請(qǐng)求攔截
1.安全規(guī)則
Spring Security的請(qǐng)求攔截配置方法是用戶存儲(chǔ)配置方法的重載方法,我們先來(lái)簡(jiǎn)單配置一下:
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers('/user', '/menu').hasRole('ADMIN').antMatchers('/', '/**').permitAll();}}
調(diào)用authorizeRequests()方法后,就可以添加自定義攔截路徑了。antMatchers()方法配置了請(qǐng)求路徑,hasRole()和permitAll()指定了訪問(wèn)規(guī)則,分別表示擁有“ADMIN”權(quán)限的用戶才能訪問(wèn)、所有用戶可以訪問(wèn)。
需要注意的是:這里的配置需要成對(duì)出現(xiàn),并且配置的順序也很重要。聲明在前面的規(guī)則擁有更高的優(yōu)先級(jí)。也就是說(shuō),如果我們將.antMatchers('/', '/').permitAll()**放到了最前面,像這樣:
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers('/', '/**').permitAll() .antMatchers('/user', '/menu').hasRole('ADMIN');}
那么,下面的'/user'和 '/menu'的配置是徒勞,因?yàn)榍懊娴囊?guī)則已經(jīng)指明所有路徑能被所有人訪問(wèn)。當(dāng)然權(quán)限的規(guī)則方法還有很多,我這里只列舉了兩個(gè)。以下為常見的內(nèi)置表達(dá)式:
表達(dá) 描述 hasRole(String role) 返回true當(dāng)前委托人是否具有指定角色。例如, hasRole(’admin’)默認(rèn)情況下,如果提供的角色不是以“ ROLE_”開頭,則會(huì)添加該角色。可以通過(guò)修改defaultRolePrefixon來(lái)自定義DefaultWebSecurityExpressionHandler。 hasAnyRole(String… roles) 返回true當(dāng)前委托人是否具有提供的任何角色(以逗號(hào)分隔的字符串列表形式)。例如, hasAnyRole(’admin’, ’user’)默認(rèn)情況下,如果提供的角色不是以“ ROLE_”開頭,則會(huì)添加該角色。可以通過(guò)修改defaultRolePrefixon來(lái)自定義DefaultWebSecurityExpressionHandler。 hasAuthority(String authority) 返回true當(dāng)前委托人是否具有指定權(quán)限。例如, hasAuthority(’read’) hasAnyAuthority(String… authorities) 返回true如果當(dāng)前主體具有任何所提供的當(dāng)局的(給定為逗號(hào)分隔的字符串列表)例如, hasAnyAuthority(’read’, ’write’) principal 允許直接訪問(wèn)代表當(dāng)前用戶的主體對(duì)象 authentication 允許直接訪問(wèn)Authentication從SecurityContext permitAll 始終評(píng)估為 true denyAll 始終評(píng)估為 false isAnonymous() 返回true當(dāng)前委托人是否為匿名用戶 isRememberMe() 返回true當(dāng)前主體是否是“記住我”的用戶 isAuthenticated() true如果用戶不是匿名的,則返回 isFullyAuthenticated() 返回true如果用戶不是匿名或記得,我的用戶 hasPermission(Object target, Object permission) 返回true用戶是否可以訪問(wèn)給定權(quán)限的給定目標(biāo)。例如,hasPermission(domainObject, ’read’) hasPermission(Object targetId, String targetType, Object permission) 返回true用戶是否可以訪問(wèn)給定權(quán)限的給定目標(biāo)。例如,hasPermission(1, ’com.example.domain.Message’, ’read’)
除此之外,還有一個(gè)支持SpEL表達(dá)式計(jì)算的方法,它的使用方法如下:
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers('/user', '/menu').access('hasRole(’ADMIN’)').antMatchers('/', '/**').permitAll();}
它所實(shí)現(xiàn)的規(guī)則和上面的方法一樣。Spring Security還提供了其他豐富的SpEL表達(dá)式,如:
表達(dá) 描述 hasRole(String role) 返回true當(dāng)前委托人是否具有指定角色。例如, hasRole(’admin’)默認(rèn)情況下,如果提供的角色不是以“ ROLE_”開頭,則會(huì)添加該角色。可以通過(guò)修改defaultRolePrefixon來(lái)自定義DefaultWebSecurityExpressionHandler。 hasAnyRole(String… roles) 返回true當(dāng)前委托人是否具有提供的任何角色(以逗號(hào)分隔的字符串列表形式)。例如, hasAnyRole(’admin’, ’user’)默認(rèn)情況下,如果提供的角色不是以“ ROLE_”開頭,則會(huì)添加該角色。可以通過(guò)修改defaultRolePrefixon來(lái)自定義DefaultWebSecurityExpressionHandler。 hasAuthority(String authority) 返回true當(dāng)前委托人是否具有指定權(quán)限。例如, hasAuthority(’read’) hasAnyAuthority(String… authorities) 返回true如果當(dāng)前主體具有任何所提供的當(dāng)局的(給定為逗號(hào)分隔的字符串列表)例如, hasAnyAuthority(’read’, ’write’) principal 允許直接訪問(wèn)代表當(dāng)前用戶的主體對(duì)象 authentication 允許直接訪問(wèn)Authentication從SecurityContext permitAll 始終評(píng)估為 true denyAll 始終評(píng)估為 false isAnonymous() 返回true當(dāng)前委托人是否為匿名用戶 isRememberMe() 返回true當(dāng)前主體是否是“記住我”的用戶 isAuthenticated() true如果用戶不是匿名的,則返回 isFullyAuthenticated() 返回true如果用戶不是匿名或記得,我的用戶 hasPermission(Object target, Object permission) 返回true用戶是否可以訪問(wèn)給定權(quán)限的給定目標(biāo)。例如,hasPermission(domainObject, ’read’) hasPermission(Object targetId, String targetType, Object permission) 返回true用戶是否可以訪問(wèn)給定權(quán)限的給定目標(biāo)。例如,hasPermission(1, ’com.example.domain.Message’, ’read’)
2.登錄
如果此時(shí),我們有自己的登錄界面,需要替換掉Spring Security所提供的默認(rèn)的界面,這時(shí)可以用fromLogin()和loginPage()方法來(lái)實(shí)現(xiàn):
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers('/user', '/menu').access('hasRole(’ADMIN’)').antMatchers('/', '/**').permitAll().and().formLogin().loginPage('/login');}
這便將登錄地址指向了“/login”。如果需要指定登錄成功時(shí),跳轉(zhuǎn)的地址,可以使用defaultSuccessUrl()方法:
.and() .formLogin() .loginPage('/login') .defaultSuccessUrl('/home')
此時(shí)用戶登錄過(guò)后,將跳轉(zhuǎn)到主頁(yè)來(lái)。
下面,我們來(lái)看看登出。
3.登出
和登錄類似的,可以使用logout()和logoutSuccessUrl()方法來(lái)實(shí)現(xiàn):
.and().logout().logoutSuccessUrl('/login')
上面例子中,用戶登出后將跳轉(zhuǎn)到登錄界面。
4、小結(jié)
至此,我們已基本了解了Spring Security配置,可以將它配置成我們想要的樣子(基本)。其實(shí)Spring Security能做的事還有很多,光看我這篇文章是不夠的。學(xué)習(xí)它最有效的方法就是閱讀官方文檔。里面有關(guān)于Spring Security最全最新的知識(shí)!(官網(wǎng)地址:https://spring.io/projects/spring-security)
到此這篇關(guān)于SpringBoot集成Spring Security的文章就介紹到這了,更多相關(guān)SpringBoot集成Spring Security內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!
相關(guān)文章:
1. Python如何實(shí)現(xiàn)感知器的邏輯電路2. JS實(shí)現(xiàn)表單中點(diǎn)擊小眼睛顯示隱藏密碼框中的密碼3. JS錯(cuò)誤處理與調(diào)試操作實(shí)例分析4. asp讀取xml文件和記數(shù)5. python基于scrapy爬取京東筆記本電腦數(shù)據(jù)并進(jìn)行簡(jiǎn)單處理和分析6. 原生js實(shí)現(xiàn)的觀察者和訂閱者模式簡(jiǎn)單示例7. Python ellipsis 的用法詳解8. 在終端啟動(dòng)Python時(shí)報(bào)錯(cuò)的解決方案9. vue 驗(yàn)證兩次輸入的密碼是否一致的方法示例10. xml中的空格之完全解說(shuō)
