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

相关文章
相关标签/搜索