SpringBoot集成Spring Security的方法
至今Java能夠如此的火爆Spring做出了很大的貢獻(xiàn),它的出現(xiàn)讓Java程序的編寫更為簡單靈活,而Spring如今也形成了自己的生態(tài)圈,今天咱們探討的是Spring旗下的一個(gè)款認(rèn)證工具:SpringSecurity,如今認(rèn)證框架主流“shiro”和“SpringSecurity”,由于和Spring的無縫銜接,使用SpringSecurity的企業(yè)也越來越多。
1、Spring Security介紹
Spring security,是一個(gè)強(qiáng)大的和高度可定制的身份驗(yàn)證和訪問控制框架。它是確?;赟pring的應(yīng)用程序的標(biāo)準(zhǔn)——來自官方參考手冊
Spring security 和 shiro 一樣,具有認(rèn)證、授權(quán)、加密等用于權(quán)限管理的功能。和 shiro 不同的是,Spring security擁有比shiro更豐富的功能,并且,對于Springboot而言,Spring Security比Shiro更合適一些,因?yàn)槎际荢pring家族成員。今天,我們來為SpringBoot項(xiàng)目集成Spring Security。
本文所使用的版本:
SpringBoot : 2.2.6.RELEASE
Spring Security : 5.2.2.RELEASE
2、配置Spring Security
在SpringBoot中集成Spring Security很簡單,只需要在pom.xml中添加下面代碼就行:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
這里可以不指定Spring Security的版本號(hào),它會(huì)根據(jù)SpringBoot的版本來匹配對應(yīng)的版本,SpringBoot版本是 2.2.6.RELEASE,對應(yīng)Spring Security的版本是5.2.2.RELEASE。
然后,我們就可以將springboot啟動(dòng)了。
當(dāng)我們嘗試訪問項(xiàng)目時(shí),它會(huì)跳轉(zhuǎn)到這個(gè)界面來:
對!在此之前,你什么也不用做。這就是Spring Security的優(yōu)雅之處。你只需要引入Spring Security的包,它就能在你的項(xiàng)目中工作。因?yàn)樗呀?jīng)幫你實(shí)現(xiàn)了一個(gè)簡單的登陸界面。根據(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ì)就這樣拿給你的用戶使用。那么,接下來,讓我們把它配置成我們想要的樣子。
要實(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 @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { }
這里使用了@EnableWebSecurity注解,這個(gè)注解是Spring Security用于啟用web安全的注解。具體實(shí)現(xiàn),這里就不深入了。
要實(shí)現(xiàn)自定義攔截配置,首先得告訴Spring Security,用戶信息從哪里獲取,以及用戶對應(yīng)的角色等信息。這里就需要重寫WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder auth)方法了。這個(gè)方法將指使Spring Security去找到用戶列表,然后再與想要通過攔截器的用戶進(jìn)行比對,再進(jìn)行下面的步驟。
Spring Security的用戶存儲(chǔ)配置有多個(gè)方案可以選擇,包括:
- 內(nèi)存用戶存儲(chǔ)
- 數(shù)據(jù)庫用戶存儲(chǔ)
- LDAP用戶存儲(chǔ)
- 自定義用戶存儲(chǔ)
我們分別來看看這幾種用戶存儲(chǔ)的配置方法:
1.內(nèi)存用戶存儲(chǔ)
此配置方式是直接將用戶信息存儲(chǔ)在內(nèi)存中,這種方式在速度上無疑是最快的。但只適用于有限個(gè)用戶數(shù)量,且這些用戶幾乎不會(huì)發(fā)生改變。我們來看看配置方法:
@Override protected 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)造者方式來構(gòu)建的。在上面方法中,先調(diào)用了inMemoryAuthentication()方法,它來指定用戶存儲(chǔ)在內(nèi)存中。接下來又調(diào)用了passwordEncoder()方法,這個(gè)方法的作用是告訴Spring Security認(rèn)證密碼的加密方式。因?yàn)樵赟pring security5過后,必須指定某種加密方式,不然程序會(huì)報(bào)錯(cuò)。接下來調(diào)用的withUser()、password()、authorities()方法,分別是在指定用戶的賬號(hào)、密碼以及權(quán)限名。在添加完一個(gè)用戶后,要使用and()方法來連接下一個(gè)用戶的添加。
如果使用這種配置方法,你會(huì)發(fā)現(xiàn),在修改用戶時(shí),就必須修改代碼。對于絕大多數(shù)項(xiàng)目來說,這種方式是滿足不了需求的,至少我們需要一個(gè)注冊功能。
2.數(shù)據(jù)庫用戶存儲(chǔ)
將用戶信息存儲(chǔ)在數(shù)據(jù)庫中,讓我們可以很方便地對用戶信息進(jìn)行增刪改查。并且還可以為用戶添加除認(rèn)證信息外的附加信息,這樣的設(shè)計(jì)也是我們很多小心應(yīng)用所采取的方式。讓我們來實(shí)現(xiàn)以下:
@Autowired private DataSource dataSource; @Override protected 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()來告訴Spring Security使用jdbc的方式來查詢用戶和權(quán)限,dataSource()方法指定數(shù)據(jù)庫連接信息,passwordEncoder()指定密碼加密規(guī)則,用戶的密碼數(shù)據(jù)應(yīng)該以同樣的方式進(jìn)行加密存儲(chǔ),不然,兩個(gè)加密方式不同的密碼,匹配補(bǔ)上。usersByUsernameQuery()和authoritiesByUsernameQuery()方法分別定義了查詢用戶和權(quán)限信息的sql語句。其實(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)鍵性的字段必須和語句中的一致。
使用數(shù)據(jù)庫來存儲(chǔ)用戶和權(quán)限等信息已經(jīng)可以滿足大部分的需求。但是Spring security還為我們提供了另外一種配置方式,讓我們來看一下。
3.LDAP用戶存儲(chǔ)
LDAP:輕型目錄訪問協(xié)議,是一個(gè)開放的,中立的,工業(yè)標(biāo)準(zhǔn)的應(yīng)用協(xié)議,通過IP協(xié)議提供訪問控制和維護(hù)分布式信息的目錄信息。簡單來說,就是將用戶信息存放在另外一臺(tái)服務(wù)器中(當(dāng)然,也可以在同一臺(tái)服務(wù)器,但我們一般不這么做),通過網(wǎng)絡(luò)來進(jìn)行訪問的技術(shù)。
我們來簡單配置一下:
@Override protected 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è)置的是用戶和群組的過濾條件,而userSearchBase()和groupSearchBase()設(shè)置了搜索起始位置,contextSource().url()設(shè)置LDAP服務(wù)器的地址。如果沒有遠(yuǎn)程的服務(wù)器可以使用contextSource().root()來使用嵌入式LDAP服務(wù)器,此方式將使用項(xiàng)目中的用戶數(shù)據(jù)文件來提供認(rèn)證服務(wù)。
如果以上幾種方式還不能滿足我們的需求,我們可以用自定義的方式來配置。
4.自定義用戶存儲(chǔ)
自定義用戶存儲(chǔ),就是自行使用認(rèn)證名稱來查找對應(yīng)的用戶數(shù)據(jù),然后交給Spring Security使用。我們需要定義一個(gè)實(shí)現(xiàn)UserDetailsService的service類:
@Service public class MyUserDetailsService implements UserDetailsService{ @Autowired private UserMapper userMapper; @Override public 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()。該方法需要做的是使用傳過來的username來匹配一個(gè)帶有密碼等信息的用戶實(shí)體。需要注意的是這里的User類需要實(shí)現(xiàn)UserDetails,也就是說,查到的信息里,必須得有Spring Security所需要的信息。
下面,讓我們來繼續(xù)配置:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MyUserDetailsService userDetailsService; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); } @Bean private PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }
這樣的配置方法就很簡單了,只需要告訴Spring Security你的UserDetailsService實(shí)現(xiàn)類是哪個(gè)就可以了,它會(huì)去調(diào)用loadUserByUsername()來查找用戶。
以上就是Spring Security所提供的4種用戶存儲(chǔ)方式,接下來,需要考慮的是,怎么攔截請求。
3、請求攔截
1.安全規(guī)則
Spring Security的請求攔截配置方法是用戶存儲(chǔ)配置方法的重載方法,我們先來簡單配置一下:
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/user", "/menu") .hasRole("ADMIN") .antMatchers("/", "/**").permitAll(); } }
調(diào)用authorizeRequests()方法后,就可以添加自定義攔截路徑了。antMatchers()方法配置了請求路徑,hasRole()和permitAll()指定了訪問規(guī)則,分別表示擁有“ADMIN”權(quán)限的用戶才能訪問、所有用戶可以訪問。
需要注意的是:這里的配置需要成對出現(xiàn),并且配置的順序也很重要。聲明在前面的規(guī)則擁有更高的優(yōu)先級(jí)。也就是說,如果我們將.antMatchers("/", "/").permitAll()**放到了最前面,像這樣:
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/**").permitAll() .antMatchers("/user", "/menu") .hasRole("ADMIN"); }
那么,下面的"/user"和 "/menu"的配置是徒勞,因?yàn)榍懊娴囊?guī)則已經(jīng)指明所有路徑能被所有人訪問。當(dāng)然權(quán)限的規(guī)則方法還有很多,我這里只列舉了兩個(gè)。以下為常見的內(nèi)置表達(dá)式:
表達(dá) | 描述 |
---|---|
hasRole(String role) | 返回true當(dāng)前委托人是否具有指定角色。例如, hasRole('admin')默認(rèn)情況下,如果提供的角色不是以“ ROLE_”開頭,則會(huì)添加該角色。可以通過修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler。 |
hasAnyRole(String… roles) | 返回true當(dāng)前委托人是否具有提供的任何角色(以逗號(hào)分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user')默認(rèn)情況下,如果提供的角色不是以“ ROLE_”開頭,則會(huì)添加該角色??梢酝ㄟ^修改defaultRolePrefixon來自定義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 | 允許直接訪問代表當(dāng)前用戶的主體對象 |
authentication | 允許直接訪問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用戶是否可以訪問給定權(quán)限的給定目標(biāo)。例如,hasPermission(domainObject, 'read') |
hasPermission(Object targetId, String targetType, Object permission) | 返回true用戶是否可以訪問給定權(quán)限的給定目標(biāo)。例如,hasPermission(1, 'com.example.domain.Message', 'read') |
除此之外,還有一個(gè)支持SpEL表達(dá)式計(jì)算的方法,它的使用方法如下:
@Override protected 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ì)添加該角色??梢酝ㄟ^修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler。 |
hasAnyRole(String… roles) | 返回true當(dāng)前委托人是否具有提供的任何角色(以逗號(hào)分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user')默認(rèn)情況下,如果提供的角色不是以“ ROLE_”開頭,則會(huì)添加該角色??梢酝ㄟ^修改defaultRolePrefixon來自定義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 | 允許直接訪問代表當(dāng)前用戶的主體對象 |
authentication | 允許直接訪問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用戶是否可以訪問給定權(quán)限的給定目標(biāo)。例如,hasPermission(domainObject, 'read') |
hasPermission(Object targetId, String targetType, Object permission) | 返回true用戶是否可以訪問給定權(quán)限的給定目標(biāo)。例如,hasPermission(1, 'com.example.domain.Message', 'read') |
2.登錄
如果此時(shí),我們有自己的登錄界面,需要替換掉Spring Security所提供的默認(rèn)的界面,這時(shí)可以用fromLogin()和loginPage()方法來實(shí)現(xiàn):
@Override protected 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í)用戶登錄過后,將跳轉(zhuǎn)到主頁來。
下面,我們來看看登出。
3.登出
和登錄類似的,可以使用logout()和logoutSuccessUrl()方法來實(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)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Java WebSocket獲取客戶端IP地址的示例代碼
在開發(fā)Web應(yīng)用程序時(shí),我們通常需要獲取客戶端的 IP 地址用于日志記錄、身份驗(yàn)證、限制訪問等操作,本文將介紹如何使用Java WebSocket API獲取客戶端IP地址,以及如何在常見的WebSocket框架中獲得客戶端 IP地址,需要的朋友可以參考下2023-11-11MyBatis一級(jí)與二級(jí)緩存相關(guān)配置
mybatis-plus是一個(gè)Mybatis的增強(qiáng)工具,在Mybatis的基礎(chǔ)上只做增強(qiáng)不做改變,為簡化開發(fā)、提高效率而生,這篇文章帶你了解Mybatis的一級(jí)和二級(jí)緩存2023-01-01Java通過SSLEngine與NIO實(shí)現(xiàn)HTTPS訪問的操作方法
這篇文章主要介紹了Java通過SSLEngine與NIO實(shí)現(xiàn)HTTPS訪問,需要在Connect操作、Connected操作、Read和Write操作中加入SSL相關(guān)的處理即可,需要的朋友可以參考下2021-08-08Java中新建一個(gè)文件、目錄及路徑操作實(shí)例
這篇文章主要給大家介紹了關(guān)于Java中新建一個(gè)文件、目錄及路徑操作的相關(guān)資料,新建文件、目錄及路徑是我們?nèi)粘i_發(fā)中經(jīng)常會(huì)遇到的一個(gè)需求,本文通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12修改Android應(yīng)用的樣式的一些關(guān)鍵點(diǎn)解析
這篇文章主要介紹了修改Android應(yīng)用的樣式的一些關(guān)鍵點(diǎn),即對影響外觀的theme跟style的相關(guān)修改,需要的朋友可以參考下2015-12-12