開始
SpringSecurity原理(一)——初探 SpringSecurity原理(二)——認證 SpringSecurity原理(三)——授權 SpringSecurity原理(四)——過濾器 SpringSecurity原理(五)——擴展與配置
我們接着之前的認證接着說,我們先通過一個簡單的示例,來說明一下,不通過配置,如何通過從數據庫讀取用戶信息,然後進行認證(Authentication)。
如果下面的示例中有一些不太清楚的概念,也不要着急,我們先看示例,其中涉及到的概念後面詳細介紹。
項目結構基本沒有變:
首先我們需要實現UserDetailsService,來獲取用戶相關的信息封裝類UserDetails。
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class MyUserDetailService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("admin");
// 這裏可以訪問數據庫、緩存等獲取用戶信息,然後構建爲User,User是UserDetails的實現類
return User.builder()
.username("bob")//用戶名
//密碼 111111 通過BCryptPasswordEncoder加密之後的密文
.password("$2a$10$344aKAgXr17q7u.8l5i7Cu8wUJr/cxBIniLsVtf/WwFrPx0khY62K")
.authorities("admin")//權限信息
.build();
}
}
UserDetailsService只做一件事情,就是獲取UserDetails,主要是用戶名、密碼和權限。可以使用Spring Security爲我們提供的實現類User,也可以自己實現UserDetails,按自己需求封裝。
有了怎樣獲取UserDetails的方法,我們當然還要通過SecurityConfig配置的AuthenticationManagerBuilder告訴Spring Security。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import vip.oschool.uinion.security.MyUserDetailService;
import javax.annotation.Resource;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
MyUserDetailService myUserDetailService;
@Bean
BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailService);
}
}
注意,我們同時還添加了一個BCryptPasswordEncoder,這是一個密碼加密器,因爲一般情況數據庫存放的都是密碼的密文,所以,我們還要告訴Spring Security,我們數據庫的密碼的加密方式是什麼,這樣Spring Security才能把從客戶端接收到的密碼使用同樣的方式加密,然後做認證。
當然,你也可以通過下面的方式來設置密碼加密器:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailService).passwordEncoder(new BCryptPasswordEncoder());
}
下面我們把配置文件中的用戶名和密碼註釋掉,重新啓動,然後就可以通過我們在UserDetailsService實現中設置的用戶bob和密碼111111來完成認證。
server:
port: 8081
#spring:
# security:
# user:
# name: tim
# password: 111111
Principal
一個比較抽象的概念,用於唯一標識實體的類。
例如,對於用戶來說:用戶id、手機號、郵箱就可以作爲用戶的principal來標誌用戶。
Credentials
簡單點理解,就可以看做是密碼。
GrantedAuthority
先知道是:角色與權限,具體的後面詳細介紹。
UserDetails
用戶實體的接口,通過這個接口可以獲取:
- getPassword: 獲取密碼
- getUserName: 獲取用戶名
- isEnabled: 賬戶是否可用
- isAccountNonExpired: 賬戶是否過期
- isAccountNonLocked: 賬戶是否被鎖定
- isCredentialsNonExpired: 密碼是否過期
- getAuthorites:獲取用戶權限,本質上是用戶的角色信息
Spring Security中是:org.springframework.security.core.userdetails.UserDetails
Spring Security提供了一個默認的實現類:org.springframework.security.core.userdetails.User
UserDetailsService
這個接口只有一個目的,就是獲取UserDetails,例如,我們需要從數據庫獲取用戶,就需要實現這個接口,並把實現類注入到Spring容器中。
只需要獲取UserDetails,認證的工作Spring Security自己獲通過用戶名和密碼來驗證,中間可能還涉及一下密碼處理的過程。
Authentication
用於存儲用戶認證詳細信息:principal、權限GrantedAuthority等。
自定義的Authentication可以實現Authentication接口,也可以直接繼承AbstractAuthenticationToken。
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
public class JwtAuthenticationToken extends AbstractAuthenticationToken {
public JwtAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
super(authorities);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return null;
}
}
AuthenticationProvider
AuthenticationProvider,顧名思義,認證的提供者,就是用於執行特定類型的身份認證的接口。
我們如果要自定義認證,就需要實現這個接口,例如,現在很多前後端分離項目使用JWT方式來鑑權,那每個請求怎樣校驗Token呢?
我們就可以創建一個JWTAuthenticationProvider實現AuthenticationProvider,來完成JWT token的認證工作。
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
public class JwtAuthenticationProvider implements AuthenticationProvider {
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
return null;
}
@Override
public boolean supports(Class<?> authentication) {
return authentication.isAssignableFrom(JwtAuthenticationToken.class);
}
}
然後可以通過下面的方式把我們自定義的AuthenticationProvider注入。
@EnableWebSecurity()
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider())
.authenticationProvider(jwtAuthenticationProvider());
}
}
前面,我們使用的UserDetailService爲什麼能生效,是因爲做了下面的配置:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService);
}
認證管理器構建者AuthenticationManagerBuilder的userDetailsService會創建一個DaoAuthenticationProvider,DaoAuthenticationProvider使用DelegatingPasswordEncoder的是一個代理,默認使用的是BCryptPasswordEncoder。
AuthenticationManager與ProviderManager
AuthenticationManager,顧名思義,是用來管理Authentication,其實是管理AuthenticationProvider。
ProviderManager是AuthenticationManager的實現類,它持有一個AuthenticationProvider列表,用來完成不同的認證。
它還持有一個AuthenticationManager的引用,作爲其父類。
一般來說我們使用ProviderManager就夠了,一般也只需要一個AuthenticationProvider。
如果要配置多個,可以在SecurityConfig(繼承了WebSecurityConfigurerAdapter)重寫authenticationManager。
@Override
protected AuthenticationManager authenticationManager() {
// ProviderManager可以設置多個AuthenticationProvider
ProviderManager authenticationManager = new ProviderManager(Arrays.asList());
return authenticationManager;
}
PasswordEncoder
簡單的說就是用於對密碼進行加密,存儲用戶密碼的明文絕對不是什麼好的習慣,這是對用戶的不負責任,也是項目存在風險。
所以,我們一般都會對用戶的密碼進行加密之後再存儲,通常的做法是加鹽然後使用sha256再md5之類的算法。
使用Spring Security就需要我們自己實現這些過程了,直接配置PasswordEncoder就可以了。
Spring Security爲PasswordEncoder提供了下面一些實現類。
DelegatingPasswordEncoder
即委託密碼編碼器,兼容多種不同加密方式存儲密碼。主要用於新舊數據的加密方式的兼容,做到平滑遷移,例如舊數據使用NoOpPasswordEncoder,新數據使用BCryptPasswordEncoder加密。
BCryptPasswordEncoder
基於bcrypt算法的編碼器,爲了使其更能抵禦密碼破解,bcrypt故意降低了速度,與其他自適應單向功能一樣,應將其調整爲大約1秒鐘,以驗證系統上的密碼。BCryptPasswordEncoder的默認實現使用強度10。
Argon2PasswordEncoder
基於 Argon2算法的編碼器,Argon2是一種故意慢速的算法,需要大量內存。 與其他自適應單向功能一樣,應將其調整爲大約1秒鐘,以驗證系統上的密碼。 Argon2PasswordEncoder的當前實現需要BouncyCastle。
Pbkdf2PasswordEncoder
基於 PBKDF2算法的編碼器,爲了克服密碼破解問題,PBKDF2是一種故意緩慢的算法。 與其他自適應單向功能一樣,應將其調整爲大約1秒鐘,以驗證系統上的密碼。 當需要FIPS認證時,此算法是一個不錯的選擇。
SCryptPasswordEncoder
基於scrypt算法的編碼器, 爲了克服自定義硬件scrypt上的密碼破解問題,這是一種故意緩慢的算法,需要大量內存。 與其他自適應單向功能一樣,應將其調整爲大約1秒鐘,以驗證系統上的密碼。
添加測試用戶
我們測試的時候,想要添加測試用戶,但是又不想太麻煩的去實現UserDetailsService,也可以通過下面的方式:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("tim")
.password("111111").roles("admin")
.and()
.withUser("allen")
.password("222222")
.roles("user");
}
}
進入inMemoryAuthentication方法,我們可以看到上面的方式等價於:
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("tim").password("111111").roles("admin").build());
manager.createUser(User.withUsername("allen").password("222222").roles("user").build());
return manager;
}
所以,本質上還是相當於創建了一個UserDetailsService,只不過數據保存在內存中。