Spring Security: 認證架構(流程)

clipboard.png

接收HTTP請求

Spring security 定義了一個過濾器鏈, 當認證請求到達這個鏈時, 該請求將會穿過這個鏈條用於認證和受權. 這個鏈上的能夠定義1..N個過濾器, 過濾器的用途是獲取請求中的認證信息, 根據認證方式進行路由, 把認證信息傳遞給對應的認證處理程序進行處理. 下面的示例圖顯示了Spring security中經常使用的認證過濾器.java

clipboard.png

不一樣的過濾器處理不一樣的認證信息. 例如react

  • HTTP Basic 認證請經過過濾器鏈, 到達 BasicAuthenticationFilter
  • HTTP Digest 認證被 DigestAuthenticationFilter 識別,攔截並處理.
  • 表單登陸認證被 UsernamePasswordAuthenticationFilter 識別,攔截並處理.
  • X.509 認證被 X509AuthenticationFilter 識別,攔截並處理.

基於用戶憑證建立 AuthenticationToken

下圖顯示了多種類型的 AuthenticationToken, 基於不一樣額認證方式, 過濾器會建立不一樣類型的 AuthenticationToken
clipboard.pngweb

這裏咱們以最經常使用表單登陸爲例子, 用戶在登陸表單中輸入用戶名和密碼, 並點擊肯定, 瀏覽器提交POST請求到服務器, 穿過過濾器鏈, 被 UsernamePasswordAuthenticationFilter 識別, UsernamePasswordAuthenticationFilter 提取請求中的用戶名和密碼來建立 UsernamePasswordAuthenticationToken 對象.spring

把組裝好的 AuthenticationToken 傳遞給 AuthenticationManagager

組裝好的 UsernamePasswordAuthenticationToken 對象被傳遞給 AuthenticationManagager authenticate 方法進行認證決策.瀏覽器

public interface AuthenticationManager
{
  Authentication authenticate(Authentication authentication)throws AuthenticationException;
}
AuthenticationManager 只是一個接口, 實際的實現是 ProviderManager

ProviderManager 有一個配置好的認證提供者列表(AuthenticationProvider), ProviderManager 會把收到的 UsernamePasswordAuthenticationToken 對象傳遞給列表中的每個 AuthenticationProvider 進行認證.安全

進行認證處理

AuthenticationProvider 接口的定義以下:服務器

public interface AuthenticationProvider {
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
    
    boolean supports(Class<?> authentication);
}

框架提供了一部分現有的實現類:框架

  • CasAuthenticationProvider
  • JaasAuthenticationProvider
  • DaoAuthenticationProvider
  • OpenIDAuthenticationProvider
  • RememberMeAuthenticationProvider
  • LdapAuthenticationProvider
上面咱們說了, ProviderManager 會把收到的 UsernamePasswordAuthenticationToken 對象傳遞給列表中的每個 AuthenticationProvider 進行認證.
那到底 UsernamePasswordAuthenticationToken 會被哪個接收和處理呢?
注意觀察 AuthenticationProvider 接口的 supports 方法!

UserDetailsService

部分認證提供者會使用 UserDetailsService 去獲取用戶信息. UserDetailsService 獲取的對象是一個 UserDetails. 框架中自帶一個 User 實現, 可是通常咱們須要對 UserDetails 進行定製, 內置的 User 太過簡單實際項目沒法知足須要.ide

下面是一個基於JPA的 UserDetailsService 實現:this

package com.example.demowebfluxsecurity.authentication;

import com.example.demowebfluxsecurity.entity.Role;
import com.example.demowebfluxsecurity.entity.User;
import com.example.demowebfluxsecurity.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
public class JpaReactiveUserDetailsService implements ReactiveUserDetailsService {
    private UserRepository userRepository;

    @Autowired
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    /**
     * @param s 用戶名
     * @return Mono<UserDetails>
     */
    @Override
    public Mono<UserDetails> findByUsername(String s) {
        // 從用戶Repository中獲取一個User Jpa實體對象
        Optional<User> optionalUser = userRepository.findByUsername(s);
        if (!optionalUser.isPresent()) {
            return Mono.empty();
        }
        User user = optionalUser.get();
        
        // 填充權限
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : user.getRoles()) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        
        // 返回 UserDetails
        return Mono.just(new org.springframework.security.core.userdetails.User(
            user.getUsername(), user.getPassword(), authorities
        ));
    }
}

用戶 DAO

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByEmail(String email);

    @Override
    void delete(User user);

    Optional<User> findByUsername(String username);

}

認證結果處理

若是認證成功(用戶名,密碼徹底正確), AuthenticationProvider 將會返回一個徹底有效的 Authentication 對象(UsernamePasswordAuthenticationToken). 不然拋出 AuthenticationException 異常.

徹底有效的 Authentication 對象定義以下:

  • authenticated屬性爲 true
  • 已受權的權限列表(GrantedAuthority列表)
  • 用戶憑證(僅用戶名)

若是拋出異常, 將會被對應的 AuthenticationEntryPoint 處理.

可參考 UsernamePasswordAuthenticationToken 類以及抽象父類 AbstractAuthenticationToken

認證完成

認證完成後, AuthenticationManager 將會返回該認證對象(UsernamePasswordAuthenticationToken)返回給過濾器

存儲認證對象

SecurityContextHolder.getContext().setAuthentication(authentication);

相關的過濾器得到一個認證對象後, 把它存儲在安全上下文中(SecurityContext) 用於後續的受權判斷(好比查詢,修改等操做).

受權由 Authorization Filters (受權過濾器) 進行處理.

認證的過程回顧

clipboard.png

相關文章
相關標籤/搜索