认证服务-SpringSecurity及Oauth2介绍

news2024/11/24 7:38:59

认证服务-SpringSecurity及Oauth2介绍

统一身份认证服务

统一身份认证服务系统:以统一身份认证服务为核心,用户登录统一身份认证服务后,即可以使用所有支持统一身份认证服务的管理应用系统。

       统一认证服务的提供方在项目实施中通常由公司平台层面提供统一平台,作为业务系统的任务是需要通过OAUTH2以调用方的方式接入平台。

OAUTH2协议说明

开放授权(OAuth)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。由统一认证平台负责管理用户名和密码,统一认证作为唯一的登录入口,就是单点登录SSO了。目前主要是OAUTH2.0版本。

OAUTH2中的角色:

(1)Third-party application:第三方应用程序(client),资源的请求方。

(2)HTTP service:HTTP服务提供商,对外提供受保护资源服务。

(3)Resource Owner:资源所有者。

(4)User Agent:用户代理,通常指浏览器。

(5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。

(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。

OAUTH2.0流程

授权流程:

  1. 当请求某个资源时,客户端要求获取资源所有者的授权。
  2. 资源所有者同意授权。
  3. 客户端拿着资源所有者的授权向认证服务器申请访问令牌
  4. 授权服务器验证授权无误后返回访问令牌
  5. 客户端使用访问令牌向资源服务器申请访问受保护的资源
  6. 资源服务器通过授权服务器校验访问令牌通过后,返回受保护资源。

OAuth2.0的授权模式包括: 授权码模式,简化模式,密码模式和客户端模式。线面介绍一下授权码模式。

客户端的授权模式-授权码模式

授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。

流程如下:

  1. 用户通过客户端访问受保护的资源,客户端将用户导向认证服务器。例如页面跳转
  2. 认证服务器得到用户的授权。(密码,扫码)
  3. 认证服务器在得到用户授权后返回授权码(通过回调返回)
  4. 客户端使用授权码和一个跳转URI向认证服务器申请访问令牌
  5. 认证服务器验证授权码后返回访问令牌

其中请求A中,客户端的请求方式包括:

response_type:表示授权类型,必选项,此处的值固定为"code "

client_id:表示客户端的ID,必选项

redirect_uri:表示重定向URI,可选项

scope:表示申请的权限范围,可选项

state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

C中请求包括

code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。

state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

D请求中参数包括:

grant_type:表示使用的授权模式,必选项,此处的值固定为"authorization_code"。

code:表示上一步获得的授权码,必选项。

redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。

client_id:表示客户端ID,必选项。

E中认证服务器返回内容包括:

access_token:表示访问令牌,必选项。

token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。

expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。

refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。

scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。

Spring Security原理分析

基础架构

Spring Security架构建立在Servlet 容器的Filter机制基础上,入口的建立基于Spring-web框架的应用初始化来实现。 最终实现效果是使用DelatatingFilterProxy 插入到Servlet的过滤链中, DelatatingFilterProxy代理名字为“springSecurityFilterChain”对应的Filter—ProxyFilterChain。

FilterChainProxy:是Spring-security的唯一入口。官方文档对这个设计的解释是:1. 作为一个唯一入口,方便调试和查看, 2. 作为Spring Security的中心入口,可以执行一些Spring-security 的必须操作,如清除SecurityContext避免内存泄露;加入HttpFirewall来防止各类攻击。

关注点:从一些成熟框架的实现中我们可以看到下面两个非常重要的设计原则:

  • 单一入口/职责单一原则: 无论是框架或则是功能的入口通常是一个明确的,唯一的入口,这样可以减少框架整体架构和使用的复杂度,也易于基于单一入口进行扩展和补充核心功能,将扩展点限制在入口之后。
  • 开闭原则: 对修改封闭, 对开放拒绝。 框架在通过诸如模板方法等方式实现主要框架后提供足够必要的扩展点。 过滤器模式也是提供扩展功能最常用的设计模式。

AbstractSecurityWebApplicationInitializer 通过WebApplicationInitializer接口接入Spring-web容器进行初始化,其中创建DelegatingFilterProxy的源码如下:

private void insertSpringSecurityFilterChain(ServletContext servletContext) {

           //代理指定的过滤器,其实就是FilterChainProxy

String filterName = DEFAULT_FILTER_NAME;

           DelegatingFilterProxy springSecurityFilterChain =

                     new DelegatingFilterProxy(filterName);

     ……

         registerFilter(servletContext, true, filterName, springSecurityFilterChain);

         }

理解了基于过滤器架构后,Spring-Security和核心实现就是各种类型的SecurityFilter实现了,后续针对授权和认证相关的过滤器进行重点分析。

详见官方文档:

https://docs.spring.io/spring-security/reference/servlet/architecture.html ,

认证实现

总体流程

各种认证模式的实现都是AbstractAuthenticationProcessingFilter抽象类的子类,是认证的唯一入口。

  1. AbstractAuthenticationProcessingFilter 创建基于当前用户的资质的Authentication对象,通常不同的Authentication对象对应不同的AbstractAuthenticationProcessingFilter实现。
  2. 将Authentication交由AuthenticationManager进行认证。
  3. 如果认证失败
    1. SecurityContextHolder清空。
    2. 调用RememberMeServices.loginFail,如果remember me没有配置则无操作。
    3. 调用AuthenticationFailureHandler。
  4. 如果认证成功
    1. 通知SessionAuthenticationStrategy 有新的成功登陆。
    2. 将Authentication 设置到SecurityContextHolder中。SecurityContextPersistenceFilter可以针对SecurityContext中的数据进行持久化处理。
    3. 调用RememberMeServices.loginSuccesss接口。
    4. ApplicationEventPublisher发布InteractiveAuthenticationSuccessEvent事件。
    5. 调用AuthenticationSuccessHandler。

上面描述的主体框架就是AbstractAuthenticationProcessingFilter的doFilter方法的总体框架:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)

                          throws IOException, ServletException {

           ……

                  try {

                 //具体的认证过程,attemptAuthentication方法由子类实现

                          Authentication authenticationResult = attemptAuthentication(request, response);

                          if (authenticationResult == null) {

                                   // return immediately as subclass has indicated that it hasn't completed

                                   return;

                          }

                 //回话策略通知认证成功

                          this.sessionStrategy.onAuthentication(authenticationResult, request, response);

                          // Authentication success

                          if (this.continueChainBeforeSuccessfulAuthentication) {

                                   chain.doFilter(request, response);

                          }

                 //认证成功后的相关处理

                          successfulAuthentication(request, response, chain, authenticationResult);

                  }

                  catch (InternalAuthenticationServiceException failed) {

                          this.logger.error("An internal error occurred while trying to authenticate the user.", failed);

                 //认证失败后的相关处理

                          unsuccessfulAuthentication(request, response, failed);

                  }

                  catch (AuthenticationException ex) {

                          // Authentication failed

                 //认证失败后的相关处理

                          unsuccessfulAuthentication(request, response, ex);

                  }

         }

UserNamePasswordAuthenticationFilter的attemptAuthentication实现如下:

         public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)

                          throws AuthenticationException {

                  ……

                  String username = obtainUsername(request);

                  username = (username != null) ? username : "";

                  username = username.trim();

                  String password = obtainPassword(request);

                  password = (password != null) ? password : "";

          //获取用户名和密码创建UsernamePasswordAuthenticationToken类型的Authentication

          //交给AuthenticationManager进行认证

                  UsernamePasswordAuthenticationToken authRequest =

                 new UsernamePasswordAuthenticationToken(username, password);

                  setDetails(request, authRequest);

          //调用AuthenticationManager来进行认证

                  return this.getAuthenticationManager().authenticate(authRequest);

         }

AuthenticationManager架构

  1. Authentication交由AuthenticationManager进行认证,认证通过后保存再SecurityContextHolder中。
  2. AuthenticationManager通常使用实现类ProviderManager, ProviderManager负责管理各种认证实现的具体提供者, 每一个Provider针对某一特定的Authentication类型进行处理, ProviderManager 负责选择合适的AuthenticationProvider进行实际的认证。

ProviderManager的authenticate方法核心部分如下:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

    ......

for (AuthenticationProvider provider : getProviders()) {

  //判断provider是否支持当前Authentication类型

      if (!provider.supports(toTest)) {

        continue;

      }

      ......

      try {

        //调用Provider进行实际的认证实现

        result = provider.authenticate(authentication);

        if (result != null) {

          copyDetails(authentication, result);

          break;

        }

      }

      catch (AccountStatusException | InternalAuthenticationServiceException ex) {

      ……

    }

    ......

  }

AuthenticationProvider 针对不同的Authentication,提供不同的认证实现。如:OAuth2授权码模式对应的实现类:OAuth2AuthorizationCodeAuthenticationProvider。

Spring Security OAuth2.0 授权码模式

基于上面分析的Spring-Security框架结合Oauth2.0协议的介绍,下面分析Spring-Security 中对Oauth2.0支持的实现。

授权码模式实现

授权码流程如下:

  1. 用户通过客户端访问受保护的资源,客户端将用户导向认证服务器。例如页面跳转
  2. 认证服务器得到用户的授权。(密码,扫码)
  3. 认证服务器在得到用户授权后返回授权码(通过回调返回)
  4. 客户端使用授权码和一个跳转URI向认证服务器申请访问令牌
  5. 认证服务器验证授权码后返回访问令牌

对应的Spring Security具体实现如下:

A:OAuth2AuthorizationRequestRedirectFilter  负责将用户代理跳转到授权服务器来启动授权码模式,执行请求跳转到配置的registration.{id}.reidrect-uri。

涉及的工具接口:

OAuth2AuthorizationRequestResolver:将Web请求解析为OAuth2AuthorizationRequest,默认实现为DefaultOAuth2AuthorizationRequestResolver,该解析器匹配路径为/oauth2/authorization/{registrationId}的请求,从中提取registrationId以获取对应的注册信息。(见上实例配置信息)

B: 由认证服务器完成

C:认证服务器返回授权码,OAuth2LoginAuthenticationFilter过滤器响应回调: 最终构建OAuth2AuthorizationCodeAuthenticationToken交由authenticationManager进行认证

D/E:OAuth2AuthorizationCodeAuthenticationProvider 使用授权码获取AccessToken

OAuth2AuthorizationCodeAuthenticationProvider的authentication实现源码:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {

    //校验获取Authorization Code返回的statue和请求state是否一致

    if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {

      OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);

      throw new OAuth2AuthorizationException(oauth2Error);

    }

    //获取AccessToken

    OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(

        new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),

            authorizationCodeAuthentication.getAuthorizationExchange()));

    ……

  }

OAuth2LoginAuthenticationFilter/OAuth2AuthorizationCodeGrantFilter

OAuth2LoginAuthenticationFilter 是AbstractAuthenticationProcessingFilter的实现类,处理授权码模式下的授权码返回的响应,生成OAuth2LoginAuthenticationToken委托给AuthenticationManager进行登录验证。

详细流程如下:

  1. 当终端用户已经给客户端授权后, 授权服务器将code和state参数添加到redirect_uri上,重定向到用户的终端代理上,交由OAuth2LoginAuthenticationFilter处理。
  2. OAuth2LoginAuthenticationFilter使用接收到的code生成OAuth2AuthenticationToken 并委托给AuthenticationManager进行认证。
  3. 当认证成功,将创建一个OAuth2AuthenticationToken用来代表终端用户的身份, 并通过OAuth2AuthorizedClientRepository 建立起Token和用户的关系。
  4. 最后OAuth2AuthenticationToken返回,并存储在SecurityContextRepository中完成认证过程。

OAuth2AuthorizationCodeGrantFilter 是 OncePerRequestFilter的子类,是以独立功能形式的过滤器存在的,用于获取OAuth2.0授权码,处理OAuth2.0的授权响应。

授权响应的处理如下:

  1. 当终端用户已经给客户端授权后, 授权服务器将code和state参数添加到redirect_uri上,重定向到用户的终端代理上,交由OAuth2AuthorizationCodeGrantFilter处理。
  2. OAuth2AuthorizationCodeGrantFilter使用接收到的code生成

 OAuth2AuthorizationCodeAuthenticationToken并委托给AuthenticationManager进行认证。

  1. 当认证成功,为用户创建一个授权用户,将accessToken和当前凭证保存到OAuth2AuthorizedClientRepository。

OAuth2LoginAuthenticationFilter 和 OAuth2AuthorizationCodeGrantFilter区别在前者是针对登录,是 Spirng Security 认证的一个步骤,需要保存登录用户到回话中,而OAuth2AuthorizationCodeGrantFilter是一个独立功能的过滤器, 用于帮助完成AccessToken的获取, 两者都依赖于Spring Security对Oauth2功能支持的类。如:ClientRegistration,OAuth2AuthorizationCodeAuthenticationToken, OAuth2AuthorizationCodeAuthenticationProvider, OAuth2AuthorizedClientRepository等。

AuthorizationRequestRepository

在整个认证过程中存储OAuth2AuthorizationRequest。用于关联和验证Authorization Response。

默认实现是HttpSessionOAuth2AuthorizationRequestRepository,将OAuth2AuthorizationRequest保存于HttpSession中。

OAuth2AccessTokenResponseClient

授权码模式下AccessToken获取接口,默认实现是

DefaultAuthorizationCodeTokenResponseClient 使用RestOperations来交换授权码和AccessToken。提供了针对请求前预处理和响应定制处理扩展点来满足扩展。

  • setRequestEntityConverter方法设置请求的前置处理器。默认实现是:OAuth2AuthorizationCodeGrantRequestEntityConverter:用于构建一个标准的OAuth2.0 Accesss Token 请求。

  • SetRestOperation 方法可以定制RestOperation,对请求响应进行配置, 默认配置如下:

RestTemplate restTemplate = new RestTemplate(

                                   Arrays.asList(new FormHttpMessageConverter(),

 new OAuth2AccessTokenResponseHttpMessageConverter()));

                  restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

                  this.restOperations = restTemplate;

DefaultRefreshTokenTokenResponseClient

实现Access Token 刷新流程,类似OAuth2AccessTokenResponseClient

JwtBearerOAuth2AuthorizedClientProvider

支持通过JWT获取AccessToken的客户端

项目实施-统一登录客户端

上面介绍了Oauth2.0服务和Spring Security的基本原理,从项目的角度需要实现的以Oauth的客户端的角色接入到公司的统一认证平台。

方式一 基于Spring Security的Oauth标准流程进行接入

基于Spring Security的Oauth标准流程进行接入。针对上文解析中涉及到的各个扩展点,如

  1. ClientRegistrationReporisoty配置存取接口: 通常实际项目中的统一登录相关配置是保存在数据库或配置中心上。
  2. OAuth2AccessTokenResponseClient: 获取accessToken的接口,项目的accesssToken获取接口可能是所谓非标准的流程。
  3. 实现特定的AuthenticationSuccessHandler或AuthenticationFailureHandler来对认证结果进行后续处理。

优点:Spring Security框架成熟, 对于OAUTH2.0的标准协议可以迅速接入,稳定性高,扩展性强,目前Spring Security是主流的安全框架, 对OAUTH2后续可能的新增特性的可以保证持续更新。

缺点: Spring Security的Oauth2.0流程设计上存在一定复杂度,设计较多接口和类,有一定门槛, 实际项目中通常仅仅是为了完成OAUTH2的流程,如果深度集成到其流程中,需要较高的成本。

方式二 基于Spring Security 的认证流程接入。

这是方式一的一种取舍, 当项目中使用了Spring Security来进行认证后,需要补充OAuth2来完成统一登录的情况下, 可以直接基于AbstractAuthenticationProcessingFilter 自己实现OAuth2.0流程。

优点: 架构简单,实现代码集中,避免了Spring Security OAuth2.0的学习成本。

缺点:需要重复开发

方式三 不基于Spring Security实现OAuth2.0流程

这个方式是针对没有使用Spring Security的项目,比如使用了Shiro,直接按照OAuth2.0的流程实现响应的Http相关接口。

优点:避免引入过多依赖, 保持架构简洁。

缺点: 需要独立开发。

总结: 推荐使用方式二或方式三的实现统一登录, 毕竟统一登录对于企业数字化项目来说并不是一个经常改变的项目,通常所以一次性的。 另外OAUTH2.0本身的交互流程并不复杂,实现的难度不高,相对于引入新的框架的学习成本更低。

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

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

相关文章

Unity地面交互效果——5、角色足迹的制作

大家好,我是阿赵。   之前几篇文章,已经介绍了地面交互的轨迹做法。包括了法线、曲面细分还有顶点偏移。Shader方面的内容已经说完了,不过之前都是用一个球来模拟轨迹,这次来介绍一下,怎样和角色动作结合&#xff0c…

基于ssm的大学生社团管理系统

基于ssm的大学生社团管理系统 摘要 基于SSM的大学生社团管理系统是一个全面、高效的社团管理平台,旨在帮助大学生和社团管理员更方便、更快捷地进行社团活动的组织和管理。该系统基于Spring、SpringMVC和MyBatis(简称SSM)开发,这三…

IS-LM模型:从失衡到均衡的模拟

IS-LM模型:从失衡到均衡的模拟 文章目录 IS-LM模型:从失衡到均衡的模拟[toc] 1 I S − L M 1 IS-LM 1IS−LM模型2 数值模拟2.1 长期均衡解2.2 政府部门引入2.3 价格水平影响2.4 随机扰动因素 1 I S − L M 1 IS-LM 1IS−LM模型 I S − L M IS-LM IS−LM是…

JVM GC 垃圾收集器

文章目录 System.gc()内存溢出(OOM)OOM 的原因 内存泄漏垃圾回收的并行与并发安全点与安全区域 Java 中的引用分类强引用(Strong Reference)软引用(Soft Reference)弱引用(Weak Reference&#…

设计模式是测试模式咩?

设计模式和测试模式概述 软件的生命周期为什么要进行测试(测试的目的)?软件的设计模式1. **瀑布模型**3. 增量和迭代模型4. 敏捷模型5. 喷泉模型 测试模型V模型W模型 一个应用程序从出生到“死亡”会经过非常漫长的流程…… 软件的生命周期 …

你觉得哪个软件写verilog体验最好?

最近在媒体上看到一个热点问题,浏览量高达680,003。“你觉得哪个软件写verilog体验最好?”这个问题可以说是IC设计师们最想知道的问题,也是大家工作交流中比较常见的。今天移知教育小编就来为大家分享一下,我对于这个问题的解答。…

第4版信息系统模考真题

请点击↑关注、收藏,本博客免费为你获取精彩知识分享!有惊喜哟!! 1.下列关于信息的说法,错误的是( )。 A信息是物质、能量及其属性的标示的集合是确定性的增加 B信息是以物质介质为载体,传递和反映世界…

浙大计算机学院2024届推免直博生名单

名单: 分析: 浙大计算机学院共录取推免直博生158人,其中计算机科学与技术专业73人,人工智能专业7人,软件工程专业21人,网络空间安全专业19人,电子信息专业31人,设计专业7人 欢迎关…

nginx如何编译安装和应用

nginx是什么 Nginx,简称为"engine x",是一个高性能的HTTP和反向代理web服务器,同时也Nginx,简称为"engine x",是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务…

Flink SQL -- 概述

1、Flink SQL中的动态表和连续查询 1、动态表: 因为Flink是可以做实时的,数据是在不断的变化的,所以动态表指的是Flink中一张实时变换的表,表中会不断的有新的数据。但是这张表并不是真正的物理表。 2、连续查询: 连续…

视频编软件会声会影2024中文版功能介绍

会声会影2024中文版是一款加拿大公司Corel发布的视频编软件。会声会影2024官方版支持视频合并、剪辑、屏幕录制、光盘制作、添加特效、字幕和配音等功能,用户可以快速上手。会声会影2024软件还包含了视频教学以及模板素材,让用户剪辑视频更加的轻松。 会…

虚拟机复制后,无法ping通问题解决

虚拟机复制后,无法ping通问题解决 可能出现的现象 ssh工具连接不上虚拟机;虚拟机ping不通外网或者ping不通内网其它虚拟机; 原因 原虚拟机和新复制出来的虚拟机的ip地址重复;原虚拟机和新复制出来的虚拟机的MAC地址重复&#…

官方Redis视图化工具Redisinsight

一、下载最新版本的 docker pull redislabs/redisinsight mkdir /data/redisinsight docker run -d -u root -p 8001:8001 -v /etc/localtime:/etc/localtime -v /data/redisinsight:/db --restartunless-stopped redislabs/redisinsight:latest 二、浏览器打开 http://192…

轻量封装WebGPU渲染系统示例<22>- 渲染到纹理(RTT)(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxgpu/sample/RTTTest.ts 当前示例运行效果: 此示例基于此渲染系统实现,当前示例TypeScript源码如下: export class RTTTest {private mRscene new RendererScene()…

前端AJAX入门到实战,学习前端框架前必会的(ajax+node.js+webpack+git)(三)

知者乐水,仁者乐山。 XMLHttpRequest AJAX原理 - XMLHttpRequest 前面与服务器交互使用的不是axios吗? ajax并不等于axios 我们使用的axios的内部,实际上对XHR对象/原理 的封装 为什么还要学习ajax? ①在一些静态网站项目中…

Microsoft Dynamics 365 CE 扩展定制 - 9. Dynamics 365扩展

在本章中,我们将介绍以下内容: Dynamics 365应用程序Dynamics 365通用数据服务构建Dynamics 365 PowerApp使用Flow在CDS和Dynamics 365之间移动数据从AppSource安装解决方案使用数据导出服务解决方案进行数据复制从CRM数据构建Power BI仪表板简介 多年来,Dynamics CRM已从一…

跨时钟域(Clock Domain Crossing,CDC)

本文参考:http://t.csdnimg.cn/VHga2 【数字IC基础】跨时钟域(CDC,Clock Domain Crossing)_ReRrain的博客-CSDN博客 同步设计:所有设计使用同一时钟源,频率相位可预知。 异步设计:设计中有两…

MATLAB|风玫瑰图

目录 扫一扫关注公众号 效果图 粉丝给的图: 复刻的图: 其他样式效果: 数据 绘图教程 绘制左边Y轴 绘制主、次网格和主、次刻度的极坐标区域。 添加刮风数据,添加数据和颜色、图列大小映射关系。 颜色条绘制​​​​​​…

在mac上使用jmap -heap命令报错:Attaching to process ID 96530, please wait...

在mac上执行命令jmap -heap 96530 报错: Attaching to process ID 96530, please wait... ERROR: attach: task_for_pid(96530) failed: (os/kern) failure (5) Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Cant attach to the proc…