Spring security,是一個強大的和高度可定製的身份驗證和訪問控制框架。它是確保基於Spring的應用程序的標準 ——來自官方參考手冊java
Spring security 和 shiro 同樣,具備認證、受權、加密等用於權限管理的功能。和 shiro 不一樣的是,Spring security擁有比shiro更豐富的功能,而且,對於Springboot而言,Spring Security比Shiro更合適一些,由於都是Spring家族成員。今天,咱們來爲SpringBoot項目集成Spring Security。程序員
本文所使用的版本:web
SpringBoot : 2.2.6.RELEASE Spring Security : 5.2.2.RELEASE面試
在SpringBoot中集成Spring Security很簡單,只須要在pom.xml中添加下面代碼就行:spring
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>複製代碼
這裏能夠不指定Spring Security的版本號,它會根據SpringBoot的版原本匹配對應的版本,SpringBoot版本是 2.2.6.RELEASE,對應Spring Security的版本是5.2.2.RELEASE。sql
而後,咱們就能夠將springboot啓動了。數據庫
當咱們嘗試訪問項目時,它會跳轉到這個界面來:安全
對!在此以前,你什麼也不用作。這就是Spring Security的優雅之處。你只須要引入Spring Security的包,它就能在你的項目中工做。由於它已經幫你實現了一個簡單的登錄界面。根據官方介紹,登陸使用的帳號是user,密碼是隨機密碼,這個隨機密碼能夠在控制檯中找到,相似這樣的一句話:springboot
Using generated security password: 1cb77bc5-8d74-4846-9b6c-4813389ce096複製代碼
Using generated security password後面的的就是系統給的隨機密碼,咱們可使用這個密碼進行登陸。隨機密碼在每一次啓動服務後生成(若是你配置了熱部署devtools,你得隨時留意控制檯了,由於每當你修改了代碼,系統會自動重啓,那時隨機密碼就會從新生成)。服務器
固然,這樣的功能必定不是你想要的,也必定不會就這樣拿給你的用戶使用。那麼,接下來,讓咱們把它配置成咱們想要的樣子。
要實現自定義配置,首先要建立一個繼承於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註解,這個註解是Spring Security用於啓用web安全的註解。具體實現,這裏就不深刻了。
要實現自定義攔截配置,首先得告訴Spring Security,用戶信息從哪裏獲取,以及用戶對應的角色等信息。這裏就須要重寫WebSecurityConfigurerAdapter的configure(AuthenticationManagerBuilder auth)方法了。這個方法將指使Spring Security去找到用戶列表,而後再與想要經過攔截器的用戶進行比對,再進行下面的步驟。
Spring Security的用戶存儲配置有多個方案能夠選擇,包括:
咱們分別來看看這幾種用戶存儲的配置方法:
此配置方式是直接將用戶信息存儲在內存中,這種方式在速度上無疑是最快的。但只適用於有限個用戶數量,且這些用戶幾乎不會發生改變。咱們來看看配置方法:
@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使用構造者方式來構建的。在上面方法中,先調用了inMemoryAuthentication()方法,它來指定用戶存儲在內存中。接下來又調用了passwordEncoder()方法,這個方法的做用是告訴Spring Security認證密碼的加密方式。由於在Spring security5事後,必須指定某種加密方式,否則程序會報錯。接下來調用的withUser()、password()、authorities()方法,分別是在指定用戶的帳號、密碼以及權限名。在添加完一個用戶後,要使用and()方法來鏈接下一個用戶的添加。
若是使用這種配置方法,你會發現,在修改用戶時,就必須修改代碼。對於絕大多數項目來講,這種方式是知足不了需求的,至少咱們須要一個註冊功能。
將用戶信息存儲在數據庫中,讓咱們能夠很方便地對用戶信息進行增刪改查。而且還能夠爲用戶添加除認證信息外的附加信息,這樣的設計也是咱們不少當心應用所採起的方式。讓咱們來實現如下:
@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();
}複製代碼
調用jdbcAuthentication()來告訴Spring Security使用jdbc的方式來查詢用戶和權限,dataSource()方法指定數據庫鏈接信息,passwordEncoder()指定密碼加密規則,用戶的密碼數據應該以一樣的方式進行加密存儲,否則,兩個加密方式不一樣的密碼,匹配補上。usersByUsernameQuery()和authoritiesByUsernameQuery()方法分別定義了查詢用戶和權限信息的sql語句。其實,Spring security爲咱們默認了查詢用戶、權限甚至還有羣組用戶受權的sql,這三條默認的sql存放在org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl中,有興趣的小夥伴能夠點進去看看。若是你要使用默認的,那你的表中關鍵性的字段必須和語句中的一致。
使用數據庫來存儲用戶和權限等信息已經能夠知足大部分的需求。可是Spring security還爲咱們提供了另一種配置方式,讓咱們來看一下。
LDAP:輕型目錄訪問協議,是一個開放的,中立的,工業標準的應用協議,經過IP協議提供訪問控制和維護分佈式信息的目錄信息。簡單來講,就是將用戶信息存放在另一臺服務器中(固然,也能夠在同一臺服務器,但咱們通常不這麼作),經過網絡來進行訪問的技術。
咱們來簡單配置一下:
@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()設置的是用戶和羣組的過濾條件,而userSearchBase()和groupSearchBase()設置了搜索起始位置,contextSource().url()設置LDAP服務器的地址。若是沒有遠程的服務器可使用contextSource().root()來使用嵌入式LDAP服務器,此方式將使用項目中的用戶數據文件來提供認證服務。
若是以上幾種方式還不能知足咱們的需求,咱們能夠用自定義的方式來配置。
自定義用戶存儲,就是自行使用認證名稱來查找對應的用戶數據,而後交給Spring Security使用。咱們須要定義一個實現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 {
...
}複製代碼
該類只須要實現一個方法:loadUserByUsername()。該方法須要作的是使用傳過來的username來匹配一個帶有密碼等信息的用戶實體。須要注意的是這裏的User類須要實現UserDetails,也就是說,查到的信息裏,必須得有Spring Security所須要的信息。
下面,讓咱們來繼續配置:
@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實現類是哪一個就能夠了,它會去調用loadUserByUsername()來查找用戶。
以上就是Spring Security所提供的4種用戶存儲方式,接下來,須要考慮的是,怎麼攔截請求。
Spring Security的請求攔截配置方法是用戶存儲配置方法的重載方法,咱們先來簡單配置一下:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user", "/menu")
.hasRole("ADMIN")
.antMatchers("/", "/**").permitAll();
}
}複製代碼
調用authorizeRequests()方法後,就能夠添加自定義攔截路徑了。antMatchers()方法配置了請求路徑,hasRole()和permitAll()指定了訪問規則,分別表示擁有「ADMIN」權限的用戶才能訪問、全部用戶能夠訪問。
須要注意的是:這裏的配置須要成對出現,而且配置的順序也很重要。聲明在前面的規則擁有更高的優先級。也就是說,若是咱們將.antMatchers("/", "/").permitAll()**放到了最前面,像這樣:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/**").permitAll()
.antMatchers("/user", "/menu")
.hasRole("ADMIN");
}複製代碼
那麼,下面的"/user"和 "/menu"的配置是徒勞,由於前面的規則已經指明全部路徑能被全部人訪問。固然權限的規則方法還有不少,我這裏只列舉了兩個。如下爲常見的內置表達式:
表達 | 描述 |
---|---|
hasRole(String role) | 返回true 當前委託人是否具備指定角色。例如, hasRole('admin') 默認狀況下,若是提供的角色不是以「 ROLE_」開頭,則會添加該角色。能夠經過修改defaultRolePrefix on來自定義DefaultWebSecurityExpressionHandler 。 |
hasAnyRole(String… roles) |
返回true 當前委託人是否具備提供的任何角色(以逗號分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user') 默認狀況下,若是提供的角色不是以「 ROLE_」開頭,則會添加該角色。能夠經過修改defaultRolePrefix on來自定義DefaultWebSecurityExpressionHandler 。 |
hasAuthority(String authority) |
返回true 當前委託人是否具備指定權限。例如, hasAuthority('read') |
hasAnyAuthority(String… authorities) |
返回true 若是當前主體具備任何所提供的當局的(給定爲逗號分隔的字符串列表)例如, hasAnyAuthority('read', 'write') |
principal |
容許直接訪問表明當前用戶的主體對象 |
authentication |
容許直接訪問Authentication 從SecurityContext |
permitAll |
始終評估爲 true |
denyAll |
始終評估爲 false |
isAnonymous() |
返回true 當前委託人是否爲匿名用戶 |
isRememberMe() |
返回true 當前主體是不是「記住我」的用戶 |
isAuthenticated() |
true 若是用戶不是匿名的,則返回 |
isFullyAuthenticated() |
返回true 若是用戶不是匿名或記得,個人用戶 |
hasPermission(Object target, Object permission) |
返回true 用戶是否能夠訪問給定權限的給定目標。例如,hasPermission(domainObject, 'read') |
hasPermission(Object targetId, String targetType, Object permission) |
返回true 用戶是否能夠訪問給定權限的給定目標。例如,hasPermission(1, 'com.example.domain.Message', 'read') |
除此以外,還有一個支持SpEL表達式計算的方法,它的使用方法以下:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user", "/menu")
.access("hasRole('ADMIN')")
.antMatchers("/", "/**").permitAll();
}複製代碼
它所實現的規則和上面的方法同樣。Spring Security還提供了其餘豐富的SpEL表達式,如:
表達 | 描述 |
---|---|
hasRole(String role) |
返回true 當前委託人是否具備指定角色。例如, hasRole('admin') 默認狀況下,若是提供的角色不是以「 ROLE_」開頭,則會添加該角色。能夠經過修改defaultRolePrefix on來自定義DefaultWebSecurityExpressionHandler 。 |
hasAnyRole(String… roles) |
返回true 當前委託人是否具備提供的任何角色(以逗號分隔的字符串列表形式)。例如, hasAnyRole('admin', 'user') 默認狀況下,若是提供的角色不是以「 ROLE_」開頭,則會添加該角色。能夠經過修改defaultRolePrefix on來自定義DefaultWebSecurityExpressionHandler 。 |
hasAuthority(String authority) |
返回true 當前委託人是否具備指定權限。例如, hasAuthority('read') |
hasAnyAuthority(String… authorities) |
返回true 若是當前主體具備任何所提供的當局的(給定爲逗號分隔的字符串列表)例如, hasAnyAuthority('read', 'write') |
principal |
容許直接訪問表明當前用戶的主體對象 |
authentication |
容許直接訪問Authentication 從SecurityContext |
permitAll |
始終評估爲 true |
denyAll |
始終評估爲 false |
isAnonymous() |
返回true 當前委託人是否爲匿名用戶 |
isRememberMe() |
返回true 當前主體是不是「記住我」的用戶 |
isAuthenticated() |
true 若是用戶不是匿名的,則返回 |
isFullyAuthenticated() |
返回true 若是用戶不是匿名或記得,個人用戶 |
hasPermission(Object target, Object permission) |
返回true 用戶是否能夠訪問給定權限的給定目標。例如,hasPermission(domainObject, 'read') |
hasPermission(Object targetId, String targetType, Object permission) |
返回true 用戶是否能夠訪問給定權限的給定目標。例如,hasPermission(1, 'com.example.domain.Message', 'read') |
若是此時,咱們有本身的登陸界面,須要替換掉Spring Security所提供的默認的界面,這時能夠用fromLogin()和loginPage()方法來實現:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user", "/menu")
.access("hasRole('ADMIN')")
.antMatchers("/", "/**").permitAll()
.and()
.formLogin()
.loginPage("/login");
}複製代碼
這便將登陸地址指向了「/login」。若是須要指定登陸成功時,跳轉的地址,可使用defaultSuccessUrl()方法:
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")複製代碼
此時用戶登陸事後,將跳轉到主頁來。
下面,咱們來看看登出。
和登陸相似的,可使用logout()和logoutSuccessUrl()方法來實現:
.and()
.logout()
.logoutSuccessUrl("/login")複製代碼
上面例子中,用戶登出後將跳轉到登陸界面。
至此,咱們已基本瞭解了Spring Security配置,能夠將它配置成咱們想要的樣子(基本)。其實Spring Security能作的事還有不少,光看我這篇文章是不夠的。學習它最有效的方法就是閱讀官方文檔。裏面有關於Spring Security最全最新的知識!(官網地址:spring.io/projects/sp…
最後,最近不少小夥伴找我要Linux學習路線圖,因而我根據本身的經驗,利用業餘時間熬夜肝了一個月,整理了一份電子書。不管你是面試仍是自我提高,相信都會對你有幫助!目錄以下:
免費送給你們,只求你們金指給我點個贊!
也但願有小夥伴能加入我,把這份電子書作得更完美!
推薦閱讀: