Spring Security 授权体系结构

news2024/10/3 3:44:46

目录

1、Authorities 授权(AuthorizationFilter 过滤器)

2、AuthorizationManager 授权管理器

3、角色的层次化(Roles)


1、Authorities 授权(AuthorizationFilter 过滤器)

        通过 Authentication.getAuthorities() 可获取已通过验证的用户授权信息(GrantedAuthority),GrantedAuthority 对象由 AuthenticationManager 插入到 Authentication 对象中,然后在做出授权决策时由 AuthorizationManager 实例读取

        AuthenticationManager 的常用实现为 ProviderManager,ProviderManager.authenticate 在进行身份验证完成后,会填充 Authentication 对象,其中就包括对象的授权信息(GrantedAuthority)。

//具体授权的Provider列表
private List<AuthenticationProvider> providers;

//授权方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //省略...

        //1-定义身份验证对象
        Authentication result = null;
        Authentication parentResult = null;
        int currentPosition = 0;
        int size = this.providers.size();
        Iterator var9 = this.getProviders().iterator();

        while(var9.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var9.next();
            if (provider.supports(toTest)) {
                //省略...

                try {

                    //2-通过用户验证后,在这里返回填充好的Authentication对象
                    result = provider.authenticate(authentication);

                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var14) {
                    this.prepareException(var14, authentication);
                    throw var14;
                } catch (AuthenticationException var15) {
                    lastException = var15;
                }
            }
        }

        //省略...

        if (result != null) {
            if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) {
                ((CredentialsContainer)result).eraseCredentials();
            }

            if (parentResult == null) {
                this.eventPublisher.publishAuthenticationSuccess(result);
            }
            //3-在这里将封装的对象返回
            return result;
        } else {
            if (lastException == null) {
                lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}"));
            }

            if (parentException == null) {
                this.prepareException((AuthenticationException)lastException, authentication);
            }
            throw lastException;
        }
    }

        ​​​AuthenticationProvider 的实现有很多,这里使用常用的 AbstractUserDetailsAuthenticationProvider 来看一下用户权限的填充步骤。

    //AbstractUserDetailsAuthenticationProvider.authenticate方法
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        //省略...

        //1-获取用户名称
        String username = this.determineUsername(authentication);

        //2-从缓存中获取用户信息(UserDetails)
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;

            try {
                //3-缓存中无用户信息,再去查找其他UserDetails的实现
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                //错误处理,省略...
        }

        //省略..

        //4-最后这个返回,就是返回封装好的Authentication对象
        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }

    //封装Authentication对象
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {

        //在这里设置了用户的权限->user.getAuthorities() -> GrantedAuthority
        UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));

        result.setDetails(authentication.getDetails());
        this.logger.debug("Authenticated user");
        return result;
    }

        用户权限填充完了,什么时候取用呢?权限信息由授权过滤器 AuthorizationFilter 在进行授权的时候取用。

//授权过滤器
public class AuthorizationFilter extends OncePerRequestFilter {

    //授权管理器
    private final AuthorizationManager<HttpServletRequest> authorizationManager;

    public AuthorizationFilter(AuthorizationManager<HttpServletRequest> authorizationManager) {
        Assert.notNull(authorizationManager, "authorizationManager cannot be null");
        this.authorizationManager = authorizationManager;
    }

    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        // verify方法进行授权验证,this::getAuthentication 获取权限信息
        this.authorizationManager.verify(this::getAuthentication, request);

        filterChain.doFilter(request, response);
    }

    private Authentication getAuthentication() {
        //从SecurityContextHolder中获取权限信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication == null) {
            throw new AuthenticationCredentialsNotFoundException("An Authentication object was not found in the SecurityContext");
        } else {
            return authentication;
        }
    }
}

2、AuthorizationManager 授权管理器

        AuthorizationManager,用来确定 Authentication 是否有权访问特定对象。

        在 Spring Security 中 AuthorizationManager 取代了 AccessDecisionManager AccessDecisionVoter

        Spring 官方鼓励自定义 AccessDecisionManager 或 AccessDecisionVoter 的应用程序改为使用 AuthorizationManager

        AuthorizationManager 由 AuthorizationFilter 调用,负责做出最终的访问控制决策。AuthorizationManager 接口包含两个方法://函数式接口

@FunctionalInterface
public interface AuthorizationManager<T> {

    //确定是否为特定Authentication或对象授予访问权限。
    default void verify(Supplier<Authentication> authentication, T object) {
        AuthorizationDecision decision = this.check(authentication, object);
        if (decision != null && !decision.isGranted()) {
            throw new AccessDeniedException("Access Denied");
        }
    }

    //确定是否为特定Authentication或对象授予访问权限。
    @Nullable
    AuthorizationDecision check(Supplier<Authentication> var1, T var2);
}

        用户可以通过实现 AuthorizationManager 接口来自定义授权控制,同时,Spring Security 附带了一个委托 AuthorizationManager,它可以与各个 AuthorizationManager 进行协作。

        RequestMatcherDelegatingAuthorizationManager 将选择最合适的 AuthorizationManager 匹配请求。对于方法的安全控制,可以使用 AuthorizationManagerBeforeMethodInterceptor AuthorizationManagerAfterMethodInterceptor 进行实现。//注意,这两个拦截器在5.6后的版本中才有

    //请求匹配器和授权管理器的集合
    private final Map<RequestMatcher, AuthorizationManager<RequestAuthorizationContext>> mappings;  
  
    //RequestMatcherDelegatingAuthorizationManager.check方法
    public AuthorizationDecision check(Supplier<Authentication> authentication, HttpServletRequest request) {
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(LogMessage.format("Authorizing %s", request));
        }
        //1-获取请求匹配器和授权管理器集合的迭代器
        Iterator var3 = this.mappings.entrySet().iterator();

        Entry mapping;
        MatchResult matchResult;
        do {
            if (!var3.hasNext()) {
                this.logger.trace("Abstaining since did not find matching RequestMatcher");
                return null;
            }
            //2-获取一个Entry
            mapping = (Entry)var3.next();
            //3-获取一个RequestMatcher 
            RequestMatcher matcher = (RequestMatcher)mapping.getKey();
            //4-进行匹配
            matchResult = matcher.matcher(request);
        } while(!matchResult.isMatch());

        //5-如果匹配成功,会选择一个对应的授权器:AuthorizationManager
        AuthorizationManager<RequestAuthorizationContext> manager = (AuthorizationManager)mapping.getValue();
        if (this.logger.isTraceEnabled()) {
            this.logger.trace(LogMessage.format("Checking authorization on %s using %s", request, manager));
        }

        //6-选择特定的授权管理器进行授权操作
        return manager.check(authentication, new RequestAuthorizationContext(request, matchResult.getVariables()));
    }

        AuthorizationManagerBeforeMethodInterceptor: 是一个方法拦截器,它使用 AuthorizationManager 来确定一个 Authentication 是否可以调用给定的 MethodInvocation。//该拦截器在方法调用前进行拦截

    //授权管理器
    private final AuthorizationManager<MethodInvocation> authorizationManager;    
    
    //AuthorizationManagerBeforeMethodInterceptor.attemptAuthorization方法
    //在该拦截器中尝试使用授权管理器进行授权
    private void attemptAuthorization(MethodInvocation mi) {
        this.logger.debug(LogMessage.of(() -> {
            return "Authorizing method invocation " + mi;
        }));
        //1-执行授权操作
        AuthorizationDecision decision = this.authorizationManager.check(this.authentication, mi);

        //2-发布授权事件(Authentication对象,调用方法,授权结果)
        this.eventPublisher.publishAuthorizationEvent(this.authentication, mi, decision);

        if (decision != null && !decision.isGranted()) {

            //3-授权失败,记录日志,抛出异常
            this.logger.debug(LogMessage.of(() -> {
                return "Failed to authorize " + mi + " with authorization manager " + this.authorizationManager + " and decision " + decision;
            }));
            throw new AccessDeniedException("Access Denied");
        } else {

             //4-授权成功,记录日志,后续执行指定方法
            this.logger.debug(LogMessage.of(() -> {
                return "Authorized method invocation " + mi;
            }));
        }
    }

    //AuthorizationManagerBeforeMethodInterceptor.invoke方法
    public Object invoke(MethodInvocation mi) throws Throwable {
        //1-调用拦截器进行授权
        this.attemptAuthorization(mi);

        //2-授权成功后执行指定方法(此时授权没有抛出异常)
        return mi.proceed();
    }

        对应的授权管理器,Spring Security 提供了指定的授权器 PreAuthorizeAuthorizationManager,在该授权器中执行授权相关操作。

        AuthorizationManagerAfterMethodInterceptor:也是一个MethodInterceptor,它可以使用 AuthorizationManager 来确定一个 Authentication 是否可以访问一个 MethodInvocation 的结果。//这个拦截器是在方法调用结束后进行拦截

        授权管理器(AuthorizationManager)的实现类图示:

3、角色的层次化(Roles)

        应用程序中的尝尝指定一个特定的角色会自动“包含”其他的角色。例如,在具有“Admin”和“User”角色概念的应用程序中,希望管理员(“Admin”)能够执行普通用户(“User”)的所有操作。要实现这一需求,就要确保所有管理员角色也被分配了“User”角色。

        角色层次结构允许配置一个角色(或权限)时可以包含其他角色。Spring Security 的 RoleVoter 的扩展版本 RoleHierarchyVoter 配置了一个 RoleHierarchy,它从中获得分配给用户的所有“可访问权限”。//角色选择器

        典型的配置可能是这样的:

@Bean
AccessDecisionVoter hierarchyVoter() {
    RoleHierarchy hierarchy = new RoleHierarchyImpl();
    hierarchy.setHierarchy("ROLE_ADMIN > ROLE_STAFF\n" +
            "ROLE_STAFF > ROLE_USER\n" +
            "ROLE_USER > ROLE_GUEST");
    return new RoleHierarchyVoter(hierarchy);
}

        在上边代码中定义了四个层次结构的角色:

ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST

        使用 ROLE_ADMIN 角色进行身份验证的用户,会同时拥有其他角色的权限,其中 ">" 可以被看成”包括“的意思。

        注意:RoleHierarchy bean 配置还没有移植到 @EnableMethodSecurity。因此,本例使用的是 AccessDecisionVoter。如果需要 RoleHierarchy 来支持方法的安全性,请继续使用@EnableGlobalMethodSecurity,直到这个bug修复完成。//这个配置是有bug的,后续应该可以正常使用

        顺便提一下,Spring Security 也提供了 AccessDecisionManager 和 AccessDecisionVoters 适配 AuthorizationManager 的方式,用来兼容之前的代码,请点击这里。

        至此,Spring Security 的授权架构介绍完毕,在这篇文章中,更多的是方法论,具体的使用细节将在后续文章中进行补充。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/787740.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Spring基础知识讲解

文章目录 Spring是什么&#xff1f;IoC容器与DIDI与IoC的区别 Spring项目的创建配置maven国内源创建Spring项目有关Bean的操作存储Bean使用Bean ApplicationContext和BeanFactory的区别getBean()的三种使用方法 更简单的存储和获取对象类注解方法注解获取Bean对象的简单方法——…

Ubuntu18.04下安装ROS

安装相关依赖 sudo apt install ninja-build exiftool ninja-build protobuf-compiler libeigen3-dev genromfs xmlstarlet libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev python-pip python3-pip ----------------------------------------------------------------…

TCP/IP 介绍

一、TCP/IP 是什么 TCP /IP 是因特网通信协议 &#xff08;注释&#xff1a;通信协议是对计算机必须遵守的规则的描述&#xff0c;只有遵守这些规则&#xff0c;计算机之间才能进行通信。&#xff09; 因特网浏览器和因特网服务器均使用 TCP/IP 来连接因特网。浏览器使用 TCP…

Idea 避免import *

File -> setting -> Editor -> Code Style -> Java -> Imports

详解Mybatis查询之resultType返回值类型问题【4种情况】

编译软件&#xff1a;IntelliJ IDEA 2019.2.4 x64 操作系统&#xff1a;win10 x64 位 家庭版 Maven版本&#xff1a;apache-maven-3.6.3 Mybatis版本&#xff1a;3.5.6 文章目录 引言一、查询单行数据返回单个对象二、查询多行数据返回对象的集合三、 查询单行数据返回Map[Key,…

pandas学习

(个人学习使用) 添加索引 # index是行索引&#xff0c;columns是列索引 pd.DataFrame(score, indexidx, columnscol) 常用属性和方法 data.shape # 形状 data.index # 行索引 data.columns # 列索引 data.values # 里面的值&#xff0c;结果是ndarray类型数组 …

SpringBoot登陆+6套主页-【JSB项目实战】

SpringBoot系列文章目录 SpringBoot知识范围-学习步骤【JSB系列之000】 文章目录 SpringBoot系列文章目录本系列校训 SpringBoot技术很多很多环境及工具&#xff1a;上效果图主页登陆 配置文件设置导数据库项目目录如图&#xff1a;代码部分&#xff1a;控制器过滤器详细的解…

docker Registry私有仓库

一、docker自带的本地私有仓库配置 #首先下载registry镜像 docker pull registry#在daemon.json文件中添加私有仓库地址 vim /etc/docker/daemon.json {"insecure-registries": ["192.168.60.11:5000"], #添加&#xff0c;注意用逗号结尾"registr…

验证码登录如何实现?

手机验证码登录 1、需求分析2、数据模型3、代码开发-交互过程4、代码开发-准备工作5、代码开发-修改LoginCheckFilter6、代码开发-接口开发7、前端代码介绍8、启动测试 1、需求分析 为了方便用户登录&#xff0c;移动端通常都会提供通过手机验证码登录的功能。 手机验证码登录…

Android性能优化之修复游戏中内存泄漏(java层)

游戏在bugly上内存OOM越来越严重&#xff0c;因此&#xff0c;近期对内存进行优化。从java层的内存进行优化开始&#xff0c;通过LeakCannary或者adb shell 获取到内存快照&#xff0c;发现好几处内存泄漏点。 1.单例类持有Activity&#xff1a; 查看内存快照&#xff0c;该闪…

MFC第二十天 数值型关联变量 和单选按钮与复选框的开发应用

文章目录 数值型关联变量数值型关联变量的种类介绍 单选按钮与复选框单选按钮的组内选择原理解析单选按钮和复选框以及应用数值型关联变量的开发CMainDlg.cppCInputDlg.hCInputDlg.cpp 附录 数值型关联变量 数值型关联变量的种类介绍 1、 数值型关联变量&#xff1a; a)控件型…

STM32MP157驱动开发——按键驱动(异步通知)

文章目录 “异步通知 ”机制&#xff1a;信号的宏定义&#xff1a;信号注册 APP执行过程驱动编程做的事应用编程做的事异步通知方式的按键驱动程序(stm32mp157)button_test.cgpio_key_drv.cMakefile修改设备树文件编译测试 “异步通知 ”机制&#xff1a; 信号的宏定义&#x…

请问学JavaScript 前要学html 和css 吗?

前言 html和css可以理解为是一个网站的骨架和皮肤&#xff0c;这两部分做好后整个网站的外观展示的完成度基本就有了个90%左右&#xff0c;所以在学习js前是需要学习html和css 的&#xff0c;这两部分不用花特别多的时间&#xff08;虽然css如果想做一些非常炫酷的效果个人认为…

记一次容器环境下出现 Address not available

作者&#xff1a;郑明泉、余凯 困惑的源地址 pod 创建后一段时间一直是正常运行&#xff0c;突然有一天发现没有新的连接创建了&#xff0c;业务上是通过 pod A 访问 svc B 的 svc name 的方式&#xff0c;进入 pod 手动去 wget 一下&#xff0c;发现报错了 Address not avai…

Roslyn 动态编译

NET 编译器平台&#xff08;.NET Compiler Platform&#xff09; 也称为Roslyn&#xff0c;是Microsoft的一组用于C#和Visual Basic (VB.NET) 语言的开源 编译器和代码分析 API。 该项目特别包括C# 和 VB.NET 编译器的自托管版本——用语言本身编写的编译器。编译器可通过传统…

vue2路由(个人学习笔记四)

目录 友情提醒第一章、路由简介1.1&#xff09;什么是路由1.2&#xff09;安装路由插件 第二章、自定义路由器2.1&#xff09;创建路由器文件index.js文件2.2&#xff09;index.js文件中配置路由信息 第三章、使用路由器3.1&#xff09;在main.js文件中将路由器绑定到Vue对象3.…

查看maven发布时间的方法

查看maven发布时间的方法如下【 打开maven官网 选中Release Notes 即可查看对应版本的发布时间 】

【Linux命令200例】cmp文件比较工具

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;2023年6月csdn上海赛道top4。 &#x1f3c6;本文已收录于专栏&#xff1a;Linux命令大全。 &#x1f3c6;本专栏我们会通过具体的系统的命令讲解加上鲜活的实操案例对各个命令进行深入…

微信小程序开发知识点总结(不断补充)

一、怎样实现小程序与数据库的交互原理 微信小程序开发里面是不能直接写数据库连接文件&#xff0c;如果要实现与数据库的交互&#xff0c;就需要用到中间的接口。&#xff08;这个接口可以理解成springboot项目中controller层的RequestMapping的URL&#xff09;具体用法如下&…

Thunderbird 115 带来了Supernova UI 和先进功能

导读Thunderbird 115 带来了Supernova UI 和先进功能&#xff0c;将电子邮件带入了新的高度。 流行的开源电子邮件客户端 Mozilla Thunderbird 推出了最新版本Thunderbird 115&#xff0c;它带来了一系列令人兴奋的功能和改进。更新带来了令人耳目一新的超新星用户界面(UI)&…