Spring security 定義了一個過濾器鏈, 當認證請求到達這個鏈時, 該請求將會穿過這個鏈條用於認證和受權. 這個鏈上的能夠定義1..N個過濾器, 過濾器的用途是獲取請求中的認證信息, 根據認證方式進行路由, 把認證信息傳遞給對應的認證處理程序進行處理. 下面的示例圖顯示了Spring security中經常使用的認證過濾器.java
不一樣的過濾器處理不一樣的認證信息. 例如react
下圖顯示了多種類型的 AuthenticationToken, 基於不一樣額認證方式, 過濾器會建立不一樣類型的 AuthenticationTokenweb
這裏咱們以最經常使用表單登陸爲例子, 用戶在登陸表單中輸入用戶名和密碼, 並點擊肯定, 瀏覽器提交POST請求到服務器, 穿過過濾器鏈, 被 UsernamePasswordAuthenticationFilter 識別, UsernamePasswordAuthenticationFilter 提取請求中的用戶名和密碼來建立 UsernamePasswordAuthenticationToken 對象.spring
組裝好的 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 獲取的對象是一個 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 (受權過濾器) 進行處理.