1、Spring Security介紹
Spring security,是一個強大的和高度可定制的身份驗證和訪問控制框架,它是確保基于Spring的應用程式的標準 ——來自官方參考手冊
Spring security 和 shiro 一樣,具有認證、授權、加密等用于權限管理的功能,和 shiro 不同的是,Spring security擁有比shiro更豐富的功能,并且,對于Springboot而言,Spring Security比Shiro更合適一些,因為都是Spring家族成員,今天,我們來為SpringBoot專案集成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的版本號,它會根據SpringBoot的版本來匹配對應的版本,SpringBoot版本是 2.2.6.RELEASE,對應Spring Security的版本是5.2.2.RELEASE,
然后,我們就可以將springboot啟動了,
當我們嘗試訪問專案時,它會跳轉到這個界面來:

? 對!在此之前,你什么也不用做,這就是Spring Security的優雅之處,你只需要引入Spring Security的包,它就能在你的專案中作業,因為它已經幫你實作了一個簡單的登陸界面,根據官方介紹,登錄使用的賬號是user,密碼是隨機密碼,這個隨機密碼可以在控制臺中找到,類似這樣的一句話:
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的用戶存盤配置有多個方案可以選擇,包括:
- 記憶體用戶存盤
- 資料庫用戶存盤
- LDAP用戶存盤
- 自定義用戶存盤
? 我們分別來看看這幾種用戶存盤的配置方法:
1.記憶體用戶存盤
? 此配置方式是直接將用戶資訊存盤在記憶體中,這種方式在速度上無疑是最快的,但只適用于有限個用戶數量,且這些用戶幾乎不會發生改變,我們來看看配置方法:
@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()方法來連接下一個用戶的添加,
? 如果使用這種配置方法,你會發現,在修改用戶時,就必須修改代碼,對于絕大多數專案來說,這種方式是滿足不了需求的,至少我們需要一個注冊功能,
2.資料庫用戶存盤
? 將用戶資訊存盤在資料庫中,讓我們可以很方便地對用戶資訊進行增刪改查,并且還可以為用戶添加除認證資訊外的附加資訊,這樣的設計也是我們很多小心應用所采取的方式,讓我們來實作以下:
@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還為我們提供了另外一種配置方式,讓我們來看一下,
3.LDAP用戶存盤
? 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服務器,此方式將使用專案中的用戶資料檔案來提供認證服務,
? 如果以上幾種方式還不能滿足我們的需求,我們可以用自定義的方式來配置,
4.自定義用戶存盤
? 自定義用戶存盤,就是自行使用認證名稱來查找對應的用戶資料,然后交給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種用戶存盤方式,接下來,需要考慮的是,怎么攔截請求,
3、請求攔截
1.安全規則
? 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_”開頭,則會添加該角色,可以通過修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler, |
hasAnyRole(String… roles) |
回傳true當前委托人是否具有提供的任何角色(以逗號分隔的字串串列形式),例如, hasAnyRole('admin', 'user')默認情況下,如果提供的角色不是以“ ROLE_”開頭,則會添加該角色,可以通過修改defaultRolePrefixon來自定義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_”開頭,則會添加該角色,可以通過修改defaultRolePrefixon來自定義DefaultWebSecurityExpressionHandler, |
hasAnyRole(String… roles) |
回傳true當前委托人是否具有提供的任何角色(以逗號分隔的字串串列形式),例如, hasAnyRole('admin', 'user')默認情況下,如果提供的角色不是以“ ROLE_”開頭,則會添加該角色,可以通過修改defaultRolePrefixon來自定義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') |
2.登錄
? 如果此時,我們有自己的登錄界面,需要替換掉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")
? 此時用戶登錄過后,將跳轉到主頁來,
? 下面,我們來看看登出,
3.登出
? 和登錄類似的,可以使用logout()和logoutSuccessUrl()方法來實作:
.and()
.logout()
.logoutSuccessUrl("/login")
? 上面例子中,用戶登出后將跳轉到登錄界面,
4、小結
? 至此,我們已基本了解了Spring Security配置,可以將它配置成我們想要的樣子(基本),其實Spring Security能做的事還有很多,光看我這篇文章是不夠的,學習它最有效的方法就是閱讀官方檔案,里面有關于Spring Security最全最新的知識!(官網地址:https://spring.io/projects/spring-security)
公眾號:良許Linux
有識訓?希望老鐵們來個三連擊,給更多的人看到這篇文章
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/40725.html
標籤:Linux
