SpringSecurity专题从入门到源码剖析(三) 核心组件

视频教程地址:  https://www.bilibili.com/video/BV1kT4y1F7Tc

代码地址:     https://gitee.com/crazyliyang/video-teaching

1. SecurityContextHolder

public class SecurityContextHolder {
    // 省略非核心代码 ...
    // ...
    
    public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";   // threadLocal
    public static final String SYSTEM_PROPERTY = "spring.security.strategy";
    
    private static String strategyName = System.getProperty(SYSTEM_PROPERTY);  // 上边的系统变量
    
    private static SecurityContextHolderStrategy  strategy;  // 策略类
    
    public static SecurityContext getContext() {
        return strategy.getContext();
    }

    private static void initialize() {
        if (!StringUtils.hasText(strategyName)) { // 如果没有系统变量的配置
            // Set default
            strategyName = MODE_THREADLOCAL;  // 策略名称使用 MODE_THREADLOCAL就是threadLocal
        }

        if (strategyName.equals(MODE_THREADLOCAL)) { //  mode_threadLocal
            // 创建 ThreadLocal 类型的 SecurityContextHolderStrategy
            strategy = new ThreadLocalSecurityContextHolderStrategy();
        }
        
        // 其实下边的就可以 不用看了, 我们没有设置, 这里其实就是 策略设计模式
        // 默认就是这个ThreadLocalSecurityContextHolderStrategy
        else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
            strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
        }
        else if (strategyName.equals(MODE_GLOBAL)) {
            strategy = new GlobalSecurityContextHolderStrategy();
        }
        else {
            // Try to load a custom strategy
            try {
                Class<?> clazz = Class.forName(strategyName);
                Constructor<?> customStrategy = clazz.getConstructor();
                strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
            }
            catch (Exception ex) {
                ReflectionUtils.handleReflectionException(ex);
            }
        }

        initializeCount++;
    }

    public static void setContext(SecurityContext context) {
        strategy.setContext(context);
    }

    public static SecurityContextHolderStrategy getContextHolderStrategy() {
        return strategy;
    }
}

既然已经知道策略类是 ThreadLocalSecurityContextHolderStrategy  直接上代码

final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
    
    // 看到这里就一目了然了  ThreadLocal 线程变量, 大家一定不陌生
    // ThreadLocal 中存储的变量和当前线程绑定
    private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();

    public void clearContext() {
        contextHolder.remove();
    }

    // 获取ThreadLocal中的内容  SecurityContext
    public SecurityContext getContext() {
        SecurityContext ctx = contextHolder.get();
        if (ctx == null) {
            ctx = createEmptyContext();
            contextHolder.set(ctx);
        }
        return ctx;
    }

    public void setContext(SecurityContext context) {
        Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
        contextHolder.set(context);
    }

}
 
public interface SecurityContext extends Serializable {
    
    // 获取认证 Authentication
    Authentication getAuthentication();

    // 设置认证 Authentication
    void setAuthentication(Authentication authentication);
}


// 只有一个实现类 说白了就是 SecurityContext 中就是持有一个 Authentication
public class SecurityContextImpl implements SecurityContext {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    // 认证 Authentication 抽象
    private Authentication authentication;

    public SecurityContextImpl() {}

    public SecurityContextImpl(Authentication authentication) {
        this.authentication = authentication;
    }

    @Override
    public Authentication getAuthentication() {
        return authentication;
    }

    @Override
    public void setAuthentication(Authentication authentication) {
        this.authentication = authentication;
    }

}

// 下边再讲 Authentication 是啥!
 

那我们在看一下  SecurityContext 是什么

通过以上源码的分析可知,  SecurityContextHolder 用于存储安全上下文   SecurityContext  信息

而这个 SecurityContext 进一步持有 Authentication  即代表的是当前操作的用户所有信息:  用户是谁,该用户是否已经被认证,他拥有哪些角色权限等待…   这些都被保存在 Authentication

SecurityContextHolder  默认使用  ThreadLocal  策略来存储认证信息。当然从源码可以看出策略是可以配置的,

看到 ThreadLocal 也就意味着,这是一种与线程绑定的策略。Spring Security 在用户登录时自动绑定认证信息到当前线程,在用户退出时,自动清除当前线程的认证信息;

但这一切的前提,是你在 web 场景下使用 Spring Security,而如果是 Swing 界面,Spring 也提供了支持,SecurityContextHolder 的策略则需要被替换,鉴于我的初衷是基于 web 来介绍 Spring Security,所以这里以及后续,非 web 的相关的内容都会忽略。

 

2. Authentication

直接先上代码:

package org.springframework.security.core;
import java.security.Principal;  // 注意这里是 java.security 包, 可见这里等级之高

public interface Authentication extends Principal, Serializable {

    // 权限s   集合中放的是 GrantedAuthority 的子类
    Collection<? extends GrantedAuthority> getAuthorities();

    Object getCredentials(); // 获取凭证

    Object getDetails(); // 获取详细信息 eg. IP address

    Object getPrincipal();  // 获取主体 Principal, 注意返回的是 Object

    boolean isAuthenticated();  // 是否已认证

    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; // set 方法
}

 

Authentication 是在   org.springframework.security.core   核心包中的接口

直接继承自 Principal 类  Principal单词含义 (  adj. 主要的,首要的 ) , 而 Principal 是位于 java.security 包中的

可以见得 Authentication 在 SpringSecurity 体系中是最高级别的身份认证的抽象

由这个顶级的权限认证接口Authentication我们可以获取很多信息, 我们逐一来分析:

2.1 getAuthorities()

权限信息列表,默认是 GrantedAuthority 接口的一些实现类,通常是代表权限信息的一系列字符串。

2.2 getCredentials()

凭证信息,  一般是密码信息,密码就是这个用户登录的凭证,

用户输入的密码字符串,在认证过后通常会被移除,用于保障安全。

2.3 getDetails()

细节信息,web 应用中的实现接口通常为 WebAuthenticationDetails,

它记录了访问者的 ip 地址和 sessionId 的值等信息

2.4 getPrincipal()

最重要的身份信息,大部分情况下返回的是 UserDetails 接口的实现类,也是框架中的常用接口之一

      下边的章节会重点讲到

 

3. GrantedAuthority

上边 身份认证最高级别接口 Authentication 中的 getAuthorities()

就是要获取 GrantedAuthority的子类集合,  GrantedAuthority 就代表了 '权限'

其实就是一种标识, 这个标识代表了某种权限, e.g 管理员角色权限, 普通用户角色权限等, 依赖的是角色标识;

更加细粒度的权限码, e.g 对于用户模块有一个删除用户的接口, 操作该接口需要的权限码是 'sys.user.delete'

只有访问的用户具有该权限标识码时, 才能访问成功, 否则失败;

package org.springframework.security.core;
public interface GrantedAuthority extends Serializable {

    String getAuthority();  // 获取权限标识,  注意这里是String 字符串
}
 

这里贴出 GrantedAuthority 的一个框架提供的实现类 SimpleGrantedAuthority

package org.springframework.security.core.authority;
public final class SimpleGrantedAuthority implements GrantedAuthority {

    private final  String role;  // 属性是 字符串 角色role

    public SimpleGrantedAuthority(String role) {
        this.role = role;
    }

    @Override
    public String getAuthority() {
        return role;
    }
}
 

看代码可知和我们预想的一样

解释一下, 相当于说  Authentication#getAuthorities()  得到的是一个 角色集合:  

                                                                                                                    String role

Collection< ? extends GrantedAuthority> getAuthorities()   ->    Collection<SimpleGrantedAuthority>

 

 

4. UserDetails

表示对用户概览的抽象, 代码中的注释已经写明其含义

package org.springframework.security.core.userdetails;

public interface UserDetails extends Serializable {

    Collection<? extends GrantedAuthority> getAuthorities();  // 上问已经分析, 权限标识集合

    String getPassword(); // 密码

    String getUsername(); // 用户名
    
    boolean isAccountNonExpired(); // 是否未过期

    boolean isAccountNonLocked(); // 是否未锁定

    boolean isCredentialsNonExpired();  // 凭证是否未过期 
    
    boolean isEnabled(); // 是否可用
}

5. UserDetailsService  

用户详细信息服务接口    通过 username 查询 UerDetails

package org.springframework.security.core.userdetails;

public interface UserDetailsService {

    // 通过 username 查询 UerDetails
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
 

6. AuthenticationManager

认证管理器, 代码如下:

package org.springframework.security.authentication;

import org.springframework.security.core.Authentication;  // 认证接口

public interface AuthenticationManager {
    
    // authenticate  vt. 鉴定;证明…是真实的   含义: 对传入的 Authentication 进行认证
    Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
AuthenticationManager(接口)是认证相关的核心接口,也是发起认证的出发点.

 

7. ProviderManager

 

package org.springframework.security.authentication;

public class ProviderManager implements AuthenticationManager, MessageSourceAware,

    // 省略非主要部分 ...
       ...

    // 持有 AuthenticationProvider 的集合
    private List<AuthenticationProvider> providers = Collections.emptyList();

    private AuthenticationManager parent;  // 父级的管理器

    // 构造
    public ProviderManager(List<AuthenticationProvider> providers) {
        this(providers, null);
    }

    public ProviderManager(List<AuthenticationProvider> providers,AuthenticationManager parent) {
        this.providers = providers; // AuthenticationProvider的集合
        this.parent = parent;  // 父级
    }

    public List<AuthenticationProvider> getProviders() {
        return providers;
    }
    
    
    // 重点在这里认证方法
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class<? extends Authentication> toTest = authentication.getClass();
        
        Authentication result = null;  // 认证的结果
        
        // 循环遍历 属性  List<AuthenticationProvider> providers
        for (AuthenticationProvider provider : getProviders()) {
            
            if (!provider.supports(toTest)) { // 获取每一个 AuthenticationProvider 判断是否支持
                continue;  // 不支持跳过继续遍历
            }
            
            try {
                
                // 重点 调用 AuthenticationProvider 的 authenticate
                result = provider.authenticate(authentication);

                if (result != null) {
                    copyDetails(authentication, result);
                    break;
                }
            }
            catch (AccountStatusException | InternalAuthenticationServiceException e) {
                prepareException(e, authentication);
                // SEC-546: Avoid polling additional providers if auth failure is due to
                // invalid account status
                throw e;
            } catch (AuthenticationException e) {
                lastException = e;
            }
        }

        throw lastException;
    }

        // 省略非主要部分 ...
        ...

}

 

 

 

8. AuthenticationProvider

    接口代码如下:  

package org.springframework.security.authentication;

public interface AuthenticationProvider {

    // 认证 Authentication
    Authentication authenticate(Authentication authentication) throws AuthenticationException;

    // 是否支持传入的  authentication
    boolean supports(Class<?> authentication);
}

 

看到这里大家可能一面懵逼, 什么东西?  

一个认证而已  又是AuthenticationManager,又是 ProviderManager ,又是 AuthenticationProvider  

搞这么复杂 ???!     what the fu**k !

其实我一开始阅读到这里也是心里一万头 艹尼玛呼啸而过. 但冷静下来仔细想了一下, 梳理一通之后, 突然想到Spring 架构中惯用的设计模式:   委托者模式Delegate,   好像开始理解设计者的用意了 :

因为实际项目中, 我们的登录认证方式可以  用户名 + 密码,  手机号+ 密码,   邮箱+密码,  甚至人脸登录等等

现实这么多登录方式, 框架也不知道用户究竟是使用哪一种,  所以说 AuthenticationManager 一般不直接认证,

AuthenticationManager 接口的实现类 ProviderManager 内部会维护一个 List<AuthenticationProvider> 列表,列表里存放多种认证方式, 遍历这个列表哪一个支持该认证方式, 就使用哪一个AuthenticationProvider来执行认证,   真正去执行具体认证的就是是这个AuthenticationProvider的实现类;

实际上这就是 委托者模式(Delegate) 的应用。 哈哈,  是不是突然豁然开朗 !!!

 

这里我自己画了一个流程图供大家参考,  链接:     认证流程图

 

6. UML架构图

 

 

spring-security-architecture.png

 

 

 

核心组件    思维导图 源码查看组件关系:      

https://www.processon.com/diagraming/5f9bdcea0791295c2ac17ad9

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值