文章

springsecurity认证流程和核心组件

用户认证

用户认证流程:

image-20220620115942257

1、用户认证核心组件

**我们系统中会有许多用户,确认当前是哪个用户正在使用我们系统就是登录认证的最终目的。这里我们就提取出了一个核心概念:**当前登录用户/当前认证用户。整个系统安全都是围绕当前登录用户展开的,这个不难理解,要是当前登录用户都不能确认了,那A下了一个订单,下到了B的账户上这不就乱套了。这一概念在Spring Security中的体现就是 Authentication,它存储了认证信息,代表当前登录用户。

我们在程序中如何获取并使用它呢?我们需要通过 **SecurityContext 来获取 AuthenticationSecurityContext就是我们的上下文对象!这个上下文对象则是交由 SecurityContextHolder 进行管理,你可以在程序任何地方**使用它:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

SecurityContextHolder原理非常简单,就是使用 ThreadLocal来保证一个线程中传递同一个对象!

现在我们已经知道了Spring Security中三个核心组件:

** **1、Authentication:存储了认证信息,代表当前登录用户

** **2、SeucirtyContext:上下文对象,用来获取 Authentication

** **3、SecurityContextHolder:上下文管理对象,用来在程序任何地方获取 SecurityContext

Authentication中是什么信息呢:

** **1、Principal:用户信息,没有认证时一般是用户名,认证后一般是用户对象

** **2、Credentials:用户凭证,一般是密码

** **3、Authorities:用户权限

2、用户认证

Spring Security是怎么进行用户认证的呢?

AuthenticationManager 就是Spring Security用于执行身份验证的组件,只需要调用它的 authenticate方法即可完成认证。Spring Security默认的认证方式就是在 UsernamePasswordAuthenticationFilter这个过滤器中进行认证的,该过滤器负责认证逻辑。

Spring Security用户认证关键代码如下:

// 生成一个包含账号密码的认证信息
Authentication authenticationToken = new UsernamePasswordAuthenticationToken(username, passwrod);
// AuthenticationManager校验这个认证信息,返回一个已认证的Authentication
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 将返回的Authentication存到上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);

下面我们来分析一下。

2.1、认证接口分析

AuthenticationManager的校验逻辑非常简单:

根据用户名先查询出用户对象(没有查到则抛出异常)将用户对象的密码和传递过来的密码进行校验,密码不匹配则抛出异常。

这个逻辑没啥好说的,再简单不过了。重点是这里每一个步骤Spring Security都提供了组件:

** 1、是谁执行 根据用户名查询出用户对象 逻辑的呢?用户对象数据可以存在内存中、文件中、数据库中,你得确定好怎么查才行。这一部分就是交由 UserDetialsService** 处理,该接口只有一个方法 loadUserByUsername(String username),通过用户名查询用户对象,默认实现是在内存中查询。

** 2、那查询出来的 用户对象 又是什么呢?每个系统中的用户对象数据都不尽相同,咱们需要确认我们的用户数据是啥样的才行。Spring Security中的用户数据则是由 UserDetails** 来体现,该接口中提供了账号、密码等通用属性。

** 3、对密码进行校验大家可能会觉得比较简单,if、else搞定,就没必要用什么组件了吧?但框架毕竟是框架考虑的比较周全,除了 if、else外还解决了密码加密的问题,这个组件就是 PasswordEncoder**,负责密码加密与校验。

我们可以看下 AuthenticationManager校验逻辑的大概源码:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...省略其他代码
​
    // 传递过来的用户名
    String username = authentication.getName();
    // 调用UserDetailService的方法,通过用户名查询出用户对象UserDetail(查询不出来UserDetailService则会抛出异常)
    UserDetails userDetails = this.getUserDetailsService().loadUserByUsername(username);
    String presentedPassword = authentication.getCredentials().toString();
​
    // 传递过来的密码
    String password = authentication.getCredentials().toString();
    // 使用密码解析器PasswordEncoder传递过来的密码是否和真实的用户密码匹配
    if (!passwordEncoder.matches(password, userDetails.getPassword())) {
        // 密码错误则抛出异常
        throw new BadCredentialsException("错误信息...");
    }
​
    // 注意哦,这里返回的已认证Authentication,是将整个UserDetails放进去充当Principal
    UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails,
            authentication.getCredentials(), userDetails.getAuthorities());
    return result;
​
...省略其他代码
}

UserDetialsServiceUserDetailsPasswordEncoder,这三个组件Spring Security都有默认实现,这一般是满足不了我们的实际需求的,所以这里我们自己来实现这些组件!

License:  CC BY 4.0