SpringSecurity中文文档(Servlet Authorization Architecture )

Authorization

在确定了用户将如何进行身份验证之后,还需要配置应用程序的授权规则。

Spring Security 中的高级授权功能是其受欢迎的最有说服力的原因之一。无论您选择如何进行身份验证(无论是使用 Spring Security 提供的机制和提供者,还是与容器或其他非 Spring Security 身份验证授权机构集成) ,授权服务都可以在您的应用程序中以一致和简单的方式使用。

您应该考虑附加授权规则来请求 URI 和方法。在这两种情况下,您都可以侦听并响应每个授权检查发布的授权事件。下面还有大量关于 Spring Security 授权如何工作的细节,以及在建立了基本模型之后,如何对其进行微调。

Authorization Architecture

本节描述应用于授权的 SpringSecurity 体系结构。

Authorities

身份验证讨论所有身份验证实现如何存储 GrantedAuthority 对象列表。这些代表授予主体的权限。。GrantedAuthority 对象由 AuthenticationManager 插入到 Authentication 对象中,然后在进行授权决策时由 AccessDecisionManager 实例读取。

GrantedAuthority 接口只有一个方法:

String getAuthority();

AuthorizationManager 实例使用此方法来获取 GrantedAuthority 的精确 String 表示形式。通过返回一个 String 形式的表示,GrantedAuthority 可以被大多数 AuthorizationManager 实现轻松地“读取”。如果 GrantedAuthority 不能精确地表示为 String,那么 GrantedAuthority 被认为是“复杂的”,而 getAuthority ()必须返回 null。

一个复杂的 GrantedAuthority 的示例可能是一个实现,它存储了适用于不同客户账户号码的操作列表和权限阈值。将这种复杂的 GrantedAuthority 表示为一个字符串会相当困难。因此,getAuthority() 方法应该返回 null。这表示任何 AuthorizationManager 需要支持特定的 GrantedAuthority 实现以理解其内容。

Spring Security 包括一个具体的 GrantedAuthority 实现:SimpleGrantedAuthority。这个实现允许任何用户指定的字符串被转换为 GrantedAuthority。安全架构中包含的所有 AuthenticationProvider 实例都使用 SimpleGrantedAuthority 来填充 Authentication 对象。

默认情况下,基于角色的授权规则包括 ROLE_ 作为前缀。这意味着如果有需要安全上下文具有“USER”角色的授权规则,Spring Security 默认会查找返回“ROLE_USER”的 GrantedAuthority#getAuthority。

可以使用 GrantedAuthorityDefauls.GrantedAuthorityDefault 存在来自定义前缀,以便用于基于角色的授权规则。

您可以通过公开 GrantedAuthorityDefault bean 来配置授权规则以使用不同的前缀,如下所示:

Custom MethodSecurityExpressionHandler

@Bean
static GrantedAuthorityDefaults grantedAuthorityDefaults() {
	return new GrantedAuthorityDefaults("MYPREFIX_");
}

使用静态方法公开 GrantedAuthorityDefault,以确保 Spring 在初始化 Spring Security 的方法 Security@Configuration 类之前发布它

Invocation Handling

SpringSecurity 提供了拦截器来控制对安全对象(如方法调用或 Web 请求)的访问。AuthorizationManager 实例对是否允许继续进行调用作出预调用决策。此外,AuthorizationManager 实例在调用后决定是否可以返回给定的值。

The AuthorizationManager

AuthorizationManager 取代 AccessDecisionManager 和 AccessDecisionVoter。

鼓励自定义 AccessDecisionManager 或 AccessDecisionVoter 的应用程序更改为使用 AuthorizationManager。

AuthorizationManager 由 Spring Security 的基于请求、基于方法和基于消息的授权组件调用,并负责制定最终的访问控制决策。AuthorizationManager 接口包含两个方法:

AuthorizationDecision check(Supplier<Authentication> authentication, Object secureObject);

default AuthorizationDecision verify(Supplier<Authentication> authentication, Object secureObject)
        throws AccessDeniedException {
    // ...
}

AuthorizationManager 的 check 方法被传递所有它需要的信息,以便做出授权决策。特别是,传递安全 Object 允许检查实际安全对象调用中的所有参数。例如,假设安全对象是一个 MethodInvocation。很容易查询 MethodInvocation 是否有任何 Customer 参数,然后实现一些安全逻辑在 AuthorizationManager 中,以确保主体被允许对该客户进行操作。实现被期望返回一个正值的 AuthorizationDecision,如果访问被授权;负值的 AuthorizationDecision,如果访问被拒绝;以及一个 null AuthorizationDecision,当不作出决定时。

verify 方法调用 check 方法,并在遇到负值的 AuthorizationDecision 时,随后抛出一个 AccessDeniedException。

Delegate-based AuthorizationManager Implementations

虽然用户可以实现自己的 AuthorizationManager 来控制授权的所有方面,但 Spring Security 提供了一个授权 AuthorizationManager,它可以与各个 AuthorizationManager 协作。

RequestMatchergeneratingAuthorizationManager 将使请求与最合适的委托 AuthorizationManager 匹配。

Authorization Manager Implementations 说明了相关的类。

authorizationhierarchy

*Figure 1. Authorization Manager Implementations*

使用这种方法,可以对授权决策轮询 AuthorizationManager 实现的组合。

AuthorityAuthorizationManager

Spring Security提供的最常见的 AuthorizationManager 是 AuthorityAuthorizationManager。它配置了一组给定的权限来查找当前的Authentication。如果身份验证包含任何配置的权限,它将返回正的 AuthorizationDecision。否则它将返回一个否定的授权决策。

AuthenticatedAuthorizationManager

另一个管理器是 AuthenticatedAuthorizationManager。它可以用来区分匿名、完全认证和记住我认证的用户。许多网站允许在记住我认证下进行某些有限的访问,但要求用户通过登录来确认他们的身份,以便获得完全访问权限。

AuthorizationManagers

AuthorizationManager 中还有一些有用的静态工厂,可以将各个 AuthorizationManager 组合成更复杂的表达式。

Custom Authorization Managers

显然,您也可以实现一个自定义的 AuthorizationManager,并且可以在其中放入您想要的几乎任何访问控制逻辑。它可能与您的应用程序(业务逻辑相关)有关,也可能实现一些安全管理逻辑。例如,您可以创建一个实现,它可以查询 Open Policy Agent 或您自己的授权数据库。

您将在 Spring 网站上找到一篇 blog article ,其中描述了如何使用遗留的 AccessDecisionVoter 来拒绝实时访问那些帐户已被暂停的用户。您可以通过实现 AuthorizationManager 来实现相同的结果。

Adapting AccessDecisionManager and AccessDecisionVoters

在 AuthorizationManager 之前,Spring Security 发布了 AccessDecisionManager 和 AccessDecisionVoter。

在某些情况下,比如迁移较旧的应用程序,可能需要引入一个 AuthorizationManager 来调用 AccessDecisionManager 或 AccessDecisionVoter。

要调用现有的 AccessDecisionManager,可以执行以下操作:

Adapting an AccessDecisionManager

@Component
public class AccessDecisionManagerAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionManager accessDecisionManager;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        try {
            Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
            this.accessDecisionManager.decide(authentication.get(), object, attributes);
            return new AuthorizationDecision(true);
        } catch (AccessDeniedException ex) {
            return new AuthorizationDecision(false);
        }
    }

    @Override
    public void verify(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
        this.accessDecisionManager.decide(authentication.get(), object, attributes);
    }
}

然后连接到你的安全过滤器链。

或者只调用 AccessDecisionVoter,你可以这样做:

Adapting an AccessDecisionVoter

@Component
public class AccessDecisionVoterAuthorizationManagerAdapter implements AuthorizationManager {
    private final AccessDecisionVoter accessDecisionVoter;
    private final SecurityMetadataSource securityMetadataSource;

    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, Object object) {
        Collection<ConfigAttribute> attributes = this.securityMetadataSource.getAttributes(object);
        int decision = this.accessDecisionVoter.vote(authentication.get(), object, attributes);
        switch (decision) {
        case ACCESS_GRANTED:
            return new AuthorizationDecision(true);
        case ACCESS_DENIED:
            return new AuthorizationDecision(false);
        }
        return null;
    }
}

然后连接到你的安全过滤器链。

Hierarchical Roles

应用程序中的特定角色应该自动“包含”其他角色,这是一个常见的要求。例如,在具有“管理员”和“用户”角色概念的应用程序中,您可能希望管理员能够做普通用户能够做的所有事情。要实现这一点,您可以确保所有管理用户也被分配了“用户”角色。或者,您可以修改每个需要“user”角色的访问约束,使其也包含“admin”角色。如果您的应用程序中有许多不同的角色,这可能会变得相当复杂。

角色层次结构的使用允许您配置哪些角色(或权限)应该包括其他角色。

这支持基于过滤器的授权在 HttpSecurity#authorizeHttpRequests 中,以及通过 DefaultMethodSecurityExpressionHandler 进行基于方法的安全授权,SecuredAuthorizationManager 用于 @Secured,Jsr250AuthorizationManager 用于 JSR-250 注解。您可以以以下方式一次性配置它们的行为:

@Bean
static RoleHierarchy roleHierarchy() {
    return RoleHierarchyImpl.withDefaultRolePrefix()
        .role("ADMIN").implies("STAFF")
        .role("STAFF").implies("USER")
        .role("USER").implies("GUEST")
        .build();
}

// and, if using pre-post method security also add
@Bean
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(RoleHierarchy roleHierarchy) {
	DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
	expressionHandler.setRoleHierarchy(roleHierarchy);
	return expressionHandler;
}

在这里,我们有四个角色的层次结构 ROLE_ADMIN ⇒ ROLE_STAFF ⇒ ROLE_USER ⇒ ROLE_GUEST。一个具有 ROLE_ADMIN 认证的用户,在评估任何基于过滤器或方法的安全约束时,将表现得好像他们拥有所有四个角色。

角色层次结构为您的应用程序提供了简化访问控制配置数据的便捷方式,以及减少您需要分配给用户的角色权限的数量。对于更复杂的需求,您可能希望定义一个逻辑映射,将您的应用程序所需的具体访问权限与分配给用户的角色之间进行转换,并在加载用户信息时进行转换。

Legacy Authorization Components

SpringSecurity 包含一些遗留组件。因为它们还没有被删除,所以出于历史目的包含了文档。他们推荐的替代品在上面。

The AccessDecisionManager

AccessDecisionManager 由 AbstractSecurityInterceptor 调用,负责做出最终的访问控制决策。AccessDecisionManager 接口包含三个方法:

void decide(Authentication authentication, Object secureObject,
	Collection<ConfigAttribute> attrs) throws AccessDeniedException;

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

AccessDecisionManager 的 decide 方法被传递所有它需要的信息,以便做出授权决策。特别是,传递安全 Object 允许检查实际安全对象调用中的所有参数。例如,假设安全对象是一个 MethodInvocation。您可以查询 MethodInvocation 是否有任何 Customer 参数,然后实现一些安全逻辑在 AccessDecisionManager 中,以确保主体被允许对该客户进行操作。实现被期望如果访问被拒绝则抛出一个 AccessDeniedException。

supports(ConfigAttribute) 方法在启动时被 AbstractSecurityInterceptor 调用,以确定 AccessDecisionManager 是否可以处理传递的 ConfigAttribute。supports(Class) 方法被安全拦截器实现调用,以确保配置的 AccessDecisionManager 支持安全拦截器呈现的安全 Object 的类型。

Voting-Based AccessDecisionManager Implementations

虽然用户可以实现自己的 AccessDecisionManager 来控制授权的所有方面,但 Spring Security 包括几个基于投票的 AccessDecisionManager 实现。投票决策管理器描述了相关的类。

下图显示了 AccessDecisionManager 接口:

access decision voting

*Figure 2. Voting Decision Manager*

通过使用这种方法,对授权决策进行一系列 AccessDecisionVoter 实现轮询。然后,AccessDecisionManager 根据对投票的评估决定是否抛出 AccessDeniedException。

AccessDecisionVoter 接口有三个方法:

int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attrs);

boolean supports(ConfigAttribute attribute);

boolean supports(Class clazz);

具体实现返回一个整数,可能的值反映在 AccessDecisionVoter 静态字段中,这些字段被命名为 ACCESS_ABSTAIN、ACCESS_DENIED 和 ACCESS_GRANTED。一个投票实现如果对授权决策没有意见,则返回 ACCESS_ABSTAIN。如果它确实有意见,它必须返回 ACCESS_DENIED 或 ACCESS_GRANTED。

Spring Security 提供了三个具体的 AccessDecisionManager 实现,以计算投票。ConsensusBased 实现基于非弃权投票的共识来授予或拒绝访问。提供了属性来控制在投票平局或所有投票都弃权的情况下的行为。AffirmativeBased 实现如果收到一个或多个 ACCESS_GRANTED 投票(换句话说,只要有一个授权投票,就忽略否认投票),则授予访问权限。与 ConsensusBased 实现类似,有一个参数来控制如果所有投票者都弃权的行为。UnanimousBased 实现期望获得一致的 ACCESS_GRANTED 投票,以便授予访问权限,忽略弃权。如果有任何 ACCESS_DENIED 投票,则拒绝访问。与其他实现一样,如果所有投票者都弃权,也有一个参数来控制行为。

您可以实现一个自定义 AccessDecisionManager,它以不同的方式计算选票。例如,来自特定 AccessDecisionVoter 的投票可能获得额外的权重,而来自特定选民的否决投票可能具有否决权效果。

RoleVoter

Spring Security 提供的最常用的 AccessDecisionVoter 是 RoleVoter,它将配置属性视为角色名,如果用户已经分配了该角色,它将投票授予访问权。

如果任何 ConfigAttribute 以 ROLE _ 前缀开头,它将进行表决。如果有一个 GrantedAuthority 返回的 String 表示(来自 getAuthority ()方法)与一个或多个以 ROLE _ 前缀开头的 ConfigAttritribute 完全相等,它将投票授予访问权。如果没有与以 ROLE _ 开头的任何 ConfigAttribute 完全匹配,RoleVoter 将投票拒绝访问。如果没有以 ROLE _ 开头的 ConfigAttribute,则选民弃权。

AuthenticatedVoter

我们隐式看到的另一个选民是 AuthenticatedVoter,它可以用来区分匿名、完全身份验证和 remember-me 身份验证用户。许多站点允许在 remember-me 身份验证下进行某些有限的访问,但是需要用户通过登录进行完全访问来确认他们的身份。

当我们使用 IS _ AUTHENTICATION _ ANONYMOUSLY 属性授予匿名访问权限时,AuthenticatedVoter 正在处理该属性。有关更多信息,请参见 AuthenticatedVoter。

Custom Voters

您还可以实现一个自定义 AccessDecisionVoter,并在其中放入几乎任何您想要的访问控制逻辑。它可能特定于您的应用程序(与业务逻辑相关) ,也可能实现某些安全管理逻辑。例如,在 Spring 网站上,您可以找到一篇博客文章,其中描述了如何使用投票者拒绝实时访问其帐户已被暂停的用户。

after invocation

*Figure 3. After Invocation Implementation*

像 Spring Security 的许多其他部分一样,AfterInvocationManager 有一个具体的实现 AfterInvocationProviderManager,它轮询 AfterInvocationProvider 列表。允许每个 AfterInvocationProvider 修改返回对象或抛出 AccessDeniedException。实际上,多个提供程序可以修改对象,因为前一个提供程序的结果将传递给列表中的下一个提供程序。

请注意,如果您使用 AfterInvocationManager,您仍然需要配置属性,以允许 MethodSecurityInterceptor 的 AccessDecisionManager 允许一个操作。如果您使用 Spring Security 通常包含的 AccessDecisionManager 实现,对于特定安全方法调用的未定义配置属性将导致每个 AccessDecisionVoter 弃权。相应地,如果 AccessDecisionManager 属性“allowIfAllAbstainDecisions”为 false,将抛出一个 AccessDeniedException。您可以通过以下两种方式之一避免这个潜在的问题:(i)将“allowIfAllAbstainDecisions”设置为 true(尽管这通常不推荐),或者(ii)简单地确保至少有一个配置属性,AccessDecisionVoter 将投票授予访问权限。后一种(推荐)方法通常通过配置一个 ROLE_USER 或 ROLE_AUTHENTICATED 属性来实现。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于单片机的空调控制器的设计

摘 要 &#xff1a; 以单片机为核心的空调控制器因其体积小 、 成本低 、 功能强 、 简便易行而得到广泛应用 。 本设计通过 &#xff21;&#xff34;&#xff18;&#xff19;&#xff33;&#xff15;&#xff12; 控制&#xff24;&#xff33;&#xff11;&#xff18;&a…

基于YOLOv9的脑肿瘤区域检测

数据集 脑肿瘤区域检测&#xff0c;我们直接采用kaggle公开数据集&#xff0c;Br35H 数据中已对医学图像中脑肿瘤位置进行标注 数据集我已经按照YOLO格式配置好&#xff0c;数据内容如下 数据集中共包含700张图像&#xff0c;其中训练集500张&#xff0c;验证集200张 模型训…

全网最适合入门的面向对象编程教程:11 类和对象的Python实现-子类调用父类方法-模拟串口传感器和主机

全网最适合入门的面向对象编程教程&#xff1a;11 类和对象的 Python 实现-子类调用父类方法-模拟串口传感器和主机 摘要&#xff1a; 本节课&#xff0c;我们主要讲解了在 Python 类的继承中子类如何进行初始化、调用父类的属性和方法&#xff0c;同时讲解了模拟串口传感器和…

【问题记录】Nodeclub运行make install报错npm ERR! code ELIFECYCLE

问题展示 按照官网给出的教程进行到make install这一步卡住了&#xff0c;显示了如下报错。 解决方法 将node.js版本变更为能够识别代码的版本&#xff0c;我将版本修改成了16.14.0以后成功运行。 nvm install 16.14.0

Java设计模式---(创建型模式)工厂、单例、建造者、原型

目录 前言一、工厂模式&#xff08;Factory&#xff09;1.1 工厂方法模式&#xff08;Factory Method&#xff09;1.1.1 普通工厂方法模式1.1.2 多个工厂方法模式1.1.3 静态工厂方法模式 1.2 抽象工厂模式&#xff08;Abstract Factory&#xff09; 二、单例模式&#xff08;Si…

从零开始做题:好怪哦

题目 给出一个压缩文件 解题 方法1 01Edit打开&#xff0c;发现是个反着的压缩包&#xff08;末尾倒着的PK头&#xff09; import os# 目标目录路径 # target_directory /home/ai001/alpaca-lora# 切换到目标目录 # os.chdir(target_directory)# 打印当前工作目录以确认…

前端开发过程中经常遇到的问题以及对应解决方法 (持续更新)

我的朋友已经工作了 3 年&#xff0c;他过去一直担任前端工程师。 不幸的是&#xff0c;他被老板批评了&#xff0c;因为他在工作中犯了一个错误&#xff0c;这是一个非常简单但容易忽视的问题&#xff0c;我想也是很多朋友容易忽视的一个问题。 今天我把它分享出来&#xff…

前端面试题27(在实际项目中,如何有效地利用Vue3的响应式系统提高性能?)

在实际项目中&#xff0c;有效利用Vue3的响应式系统提高性能主要涉及以下几个关键点&#xff1a; 1. 合理使用reactive和ref reactive&#xff1a;用于将复杂的数据结构&#xff08;如对象或数组&#xff09;转换成响应式版本。确保只将需要实时更新的数据结构声明为响应式&am…

elasticSearch的索引库文档的增删改查

我们都知道&#xff0c;elasticsearch在进行搜索引擎的工作时&#xff0c;是会先把数据库中的信息存储一份到elasticsearch中&#xff0c;再去分词查询等之后的工作的。 elasticsearch中的文档数据会被序列化为json格式后存储在elasticsearch中。elasticsearch会对存储的数据进…

4.Python4:requests

1.requests爬虫原理 &#xff08;1&#xff09;requests是一个python的第三方库&#xff0c;主要用于发送http请求 2.正则表达式 #正则表达式 import re,requests str1aceace #A(.*?)B,匹配A和B之间的值 print(re.findall(a(.*?)e,str1))import re,requests str2hello com…

Redis-Jedis连接池\RedisTemplate\StringRedisTemplate

Redis-Jedis连接池\RedisTemplate\StringRedisTemplate 1. Jedis连接池1.1 通过工具类1.1.1 连接池&#xff1a;JedisConnectionFactory&#xff1a;1.1.2 test&#xff1a;&#xff08;代码其实只有连接池那里改变了&#xff09; 2. SpringDataRedis&#xff08;lettuce&#…

滑动窗口(同向的双指针)

通过 双指针的 同向移动 算法应用的场景&#xff1a; 满足xxx条件&#xff08;计算结果&#xff0c;出现次数&#xff0c;同时包含&#xff09; 最长/最短 子串 /子数组/子序列 例如&#xff1a;长度最小的子数组 滑动窗口 使用思路 &#xff08;寻找最长&#xff09; –核心…

刷题(day01)

1、leetcode485.最大连续1的个数 给定一个二进制数组 nums &#xff0c; 计算其中最大连续 1 的个数。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,0,1,1,1] 输出&#xff1a;3 解释&#xff1a;开头的两位和最后的三位都是连续 1 &#xff0c;所以最大连续 1 的个数是 3.…

基于CentOS Stream 9平台搭建FRP内网穿透

内网穿透方法很多&#xff0c;本文以github上很火的frp为例 1.frp官方 文档&#xff1a;https://gofrp.org/zh-cn/docs/overview/ 1.1 下载 https://github.com/fatedier/frp/releases 选中合适的版本 2. 服务端&#xff08;服务器&#xff09;搭建frps 需要公网IP服务器 选…

假期笔记1:anaconda的安装与pycharm中的引用

1.下载安装 Download Anaconda Distribution | Anaconda 2.填个邮箱 11111.. 3.下载。有点需要时间 4.安装&#xff0c;双击&#xff0c;根据实际进行&#xff0c;记清安装路径 5。环境设置 conda -V 6.创建环境 conda create --name env_name conda create --na…

Qt文档阅读笔记-Queued Custom Type Example

此篇展示了使用Qt编写多线程程序。 概述 此案例创建一Block类&#xff0c;用于存储数据&#xff0c;并且在元对象系统中注册后&#xff0c;在多线程中进行信号与槽函数的连接中充当参数。 Block类 在元对象系统中&#xff0c;注册类&#xff0c;需要类在public部分提供默认构…

基于SSM的志愿者服务平台

基于SSM的志愿者服务平台系统主要其系统包括不同的端组成&#xff0c;前端主要包括系统用户管理、新闻数据管理、变幻图管理、志愿者管理、培训视频管理、志愿者项目管理、服务时长管理、交流分享管理、志愿者表彰管理。前台主要包括网站首页、培训视频、志愿者项目、交流分享、…

React+TS前台项目实战(二十六)-- 高性能可配置Echarts图表组件封装

文章目录 前言CommonChart组件1. 功能分析2. 代码详细注释3. 使用到的全局hook代码4. 使用方式5. 效果展示 总结 前言 Echarts图表在项目中经常用到&#xff0c;然而&#xff0c;重复编写初始化&#xff0c;更新&#xff0c;以及清除实例等动作对于开发人员来说是一种浪费时间…

C语言相关内容模块

C语言相关内容模块 1、函数指针定义方式 1、函数指针定义方式 函数指针的具体用法

最近点对问题(算法与数据结构设计)

课题内容和要求 最近点对问题&#xff0c;在二维平面上输入n个点列P。其中任一点pi&#xff08;xi&#xff0c;yi&#xff09;&#xff0c;编写程序求出最近的两个点。使用穷举法实现&#xff0c;算法复杂度O(n2)&#xff1b;优化算法&#xff0c;以O(nlog2n)实现这一问题 数…