SpringSecurity原理(二)——認證

開始

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。

Authentication認證繼承結構

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,只不過數據保存在內存中。

文檔

Spring Security官方文檔

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章