概述磁盘阵列（Redundant Arrays of Independent Drives，RAID），简单地说，就是讲若干块独立磁盘构成具有冗余能力的阵列。 他将很多块磁盘组合到一起构成一个磁盘组，来提升整个磁盘系统的读写性能及安全性。 利用同位检查（Parity Check）的观念，通过数据冗余实现磁盘系统中任何一个磁盘故障时整个磁盘系统仍然可以继续工作。 对于服务器开发和运维人员，RAID 是必须了解和使用的磁盘系统管理方式，随着时代的进步，越来越多的人在家庭、日常工作中使用简单的磁盘阵列来增加磁盘读写性能或提高数据安全性，甚至一些主板都已经提供了支持 RAID 的功能。 然而，RAID 概念很多，有时候会引起混淆，本文我们来详细介绍一下 RAID 技术的概念特征、基本原理、关键技术、各种等级和发展现状等方面的内容。RAIDRAID 是 1988 年美国加州大学伯克利分校的 D. A. Patterson 教授等首次在论文中提出的，起初，RAID 指的是廉价磁盘阵列，主要目的是为了节省购置大容量磁盘的成本，此后，RAID 咨询委员会将 Inexpensive 替换为 Independent 就是我们今日的 RAID，但其实质内容并没有改变。 RAID 主要利用数据条带、镜像和数据校验技术来获取高性能、可靠性、容错能力和扩展性，根据运用或组合运用这三种技术的策略和架构，拥有 RAID0 ~ RAID6 等的等级，近年来，存储厂商不断推出诸如 RAID7 、 RAID10/01 、 RAID50 、 RAID53 、 RAID100 等 RAID 等级，但这些并无统一的标准。 目前业界公认的标准是 RAID0 ~ RAID5 ，除 RAID2 外的四个等级被定为工业标准，而在实际应用领域中使用最多的 RAID 等级是 RAID0 、 RAID1 、 RAID3 、 RAID5 、 RAID6 和 RAID10。 从实现角度看， RAID 主要分为软 RAID、硬 RAID 以及软硬混合 RAID 三种。软 RAID 所有功能均有操作系统和 CPU 来完成，没有独立的 RAID 控制 / 处理芯片和 I/O 处理芯片，效率自然最低。硬 RAID 配备了专门的 RAID 控制 / 处理芯片和 I/O 处理芯片以及阵列缓冲，不占用 CPU 资源，但成本很高。软硬混合 RAID 具备 RAID 控制 / 处理芯片，但缺乏 I/O 处理芯片，需要 CPU 和驱动程序来完成，性能和成本 在软 RAID 和硬 RAID 之间。 目前来看，广泛采用的是以硬 RAID 为主的方式来实现 RAID，但各操作系统也都在不同程度上支持某些级别的软 RAID。关键技术RAID 主要有三个关键技术： 1. 镜像（Mirroring） 2. 数据条带（Data Stripping） 3. 校验（Data Parity） 不同的 RAID 级别会采用上述一到多个技术来获得不同的数据可靠性、可用性或IO性能。镜像镜像技术指的是将数据复制到多个磁盘，这样，在实际的使用中，只要有一个磁盘没有发生故障，都不影响整个磁盘系统的工作。 同时，并发地在多个磁盘上读取也可以提高读取性能，但是，相对的，镜像技术的写性能相对较低，每次写入都必须确保多个磁盘写成功。数据条带将数据分片保存在多个不同的磁盘，多个数据分片共同组成一个完整数据副本。 这与镜像的多个副本是不同的，它通常用于性能考虑。 数据条带具有更高的并发粒度，当访问数据时，可以同时对位于不同磁盘上数据进行读写操作， 从而获得非常可观的 I/O 性能提升。数据校验利用冗余数据进行数据错误检测和修复的技术。 冗余数据通常采用海明码、异或操作等算法来计算获得。 利用校验功能，可以很大程度上提高磁盘阵列的可靠性、鲁棒性和容错能力。 比起镜像 50% 的冗余开销 ，数据校验要小很多，它利用校验冗余信息对数据进行校验和纠错。 不过，数据校验需要从多处读取数据并进行计算和对比，会影响系统性能。优势大容量、高性能、可靠性、可管理性JBODJBOD 即 Just a Bunch Of Disks，他并不是标准的 RAID 等级。 它通常用来表示一个没有控制软件提供协调控制的磁盘集合，将多个物理磁盘串联起来，提供一个巨大的逻辑磁盘。 JBOD 的存放机制是由第一块磁盘开始按顺序往后存储，当前磁盘存储空间用完后，再依次往后面的磁盘存储数据。 它的存储性能完全等同于单块磁盘，而且也不提供数据安全保护，只是简单提供一种扩展存储空间的机制， JBOD 可用存储容量等于所有成员磁盘的存储空间之和。RAID0RAID 是一种简单、无数据冗余的数据带化技术，实际上它并不算是一种真正的 RAID，因为他不提供任何形式的冗余策略。 RAID0 将所在磁盘条带化后组成大容量存储空间，将数据分散存储在所有磁盘中，以独立访问方式实现多块磁盘的并发访问。 由于可以并发执行 I/O 操作，总线带宽得到充分利用。再加上不需要进行数据校验，RAID0 的性能在所有 RAID 等级中是最高的。 理论上讲，一个由 n 块磁盘组成的 RAID0 ，它的读写性能是单个磁盘性能的 n 倍，但由于总线带宽等多种因素的限制，实际的性能提升低于理论值。 RAID0 具有低成本、高读写性能、 100% 的高存储空间利用率等优点，但是它不提供数据冗余保护，一旦数据损坏，将无法恢复。 因此， RAID0 一般适用于对性能要求严格但对数据安全性和可靠性不高的应用，如视频、音频存储、临时数据缓存空间等。RAID1RAID1 称为镜像，它将数据完全一致地分别写到工作磁盘和镜像 磁盘，它的磁盘空间利用率为 50% RAID1 在数据写入时，响应时间会有所影响，但是读数据的时候没有影响。 RAID1 提供了最佳的数据保护，一旦工作磁盘发生故障，系统自动从镜像磁盘读取数据，不会影响用户工作。 RAID1 与 RAID0 刚好相反，是为了增强数据安全性使两块 磁盘数据呈现完全镜像，从而达到安全性好、技术简单、管理方便。 RAID1 拥有完全容错的能力，但实现成本高。 RAID1 应用于对顺序读写性能要求高以及对数据保护极为重视的应用，如对邮件系统的数据保护。RAID5至少使用3块硬盘（也可以更多）组建RAID5，一个磁盘发生损坏后，不会影响数据的完整性，从而保证了数据安全。常用在服务器数据存储中。当损坏的磁盘被替换后，RAID还会自动利用剩下奇偶校验信息去重建此磁盘上的数据，来保持RAID5的高可靠性。
基于过滤拦截技术实现的身份认证方案
基于过滤拦截技术实现的身份认证方案系统基本流程用户发送请求过滤器过滤掉，访问需要保持登录状态的资源但是又没有携带token的请求拦截器拦截掉，访问需要具备相应权限的资源但是又没有对应权限的请求进入业务逻辑代码层级configMyFilterConfig：自定义过滤器的基础配置，对所有资源都进行过滤MyInterceptorConfig：自定义拦截器的基础配置，设置所需拦截的放行的URLcontrollerUserController：接口层entityUser：用户的实体对象。目前为了简化操作，不必新建权限表，所以在用户的实体对象里存放了list<String>集合，即用户的属性里就带着用户的所有权限信息filter：MyFilter：自定义过滤器，这块主要是判断用户请求有没有携带token，如果没有携带token，而且还访问的是需要携带token(即保持登录)的接口，那么就过滤掉用户的这次请求。但是，有的资源是属于开放资源，即不需要用户保持登录状态，因此，在这个模块，做了白名单列表，过滤器不对白名单列表进行过滤。interceptorMyInterceptor：自定义拦截器，这块主要是拦截需要权限的接口，获取用户的token，并且解析出用户权限信息，拿用户权限信息和接口所需权限进行比对，失败则拒绝，成功则让请求进行接口层。serviceUserService：接口所对应的具体业务逻辑，这块目前主要是有判断用户名密码，进而生成token，返回前端。utilJWTUtil：JWT工具类，可以用于生成JWT，解密JWT
【计算机网络原理】电路交换、报文交换、分组交换
试简述分组交换的要点分组交换又称为报文分组交换，它是基于报文交换，将报文划分为更小的数据单位，分组交换实质上是在存储转发基础上发展起来的，它兼有电路交换和报文交换的优点。分组交换在线路上采用动态复用技术。其主要流程是如果一个源站有一个长的报文要进行发送，该报文就会被分割成一系列的分组，每一分组包含用户数据的一部分加上一些控制信息，控制信息至少要包括网络为了把分组送到目的地做路由选择所需要的信息，在路径上的每个节点，分组被接受，短时间存储然后传递给下一结点。试从多个方面比较电路交换、报文交换和分组交换的主要优缺点电路交换定义：电路交换在通信之前要在通信双方之间建立一条被双方独占的物理通路（由通信双方之间的交换设备和链路逐段连接而成）优点： ①由于通信线路为通信双方用户专用，数据直达，所以传输数据的时延非常小。②通信双方之间的物理通路一旦建立，双方可以随时通信，实时性强。③双方通信时按发送顺序传送数据，不存在失序问题。④电路交换既适用于传输模拟信号，也适用于传输数字信号。⑤电路交换的交换设备（交换机等）及控制均较简单。缺点： ①电路交换的平均连接建立时间对计算机通信来说嫌长。②电路交换连接建立后，物理通路被通信双方独占，即使通信线路空闲，也不能供其他用户使用，因而信道利用低。③电路交换时，数据直达，不同类型、不同规格、不同速率的终端很难相互进行通信，也难以在通信过程中进行差错控制。这种通信方式非常适合语音（如打电话）这种对实时性要求高的业务。但不适合数据通信业务，对于两台计算机进行数据通信来说，大多数时间线路是空闲的，由于物理通路被通信是独占的，不能供其他用户使用，因而信道利用低。报文交换定义：报文交换是以报文为数据交换的单位，报文携带有目标地址、源地址等信息，在交换结点采用存储转发的传输方式优点：①报文交换不需要为通信双方预先建立一条专用的通信线路，不存在连接建立时延，用户可随时发送报文。②通信双方不是固定占有一条通信线路，而是在不同的时间一段一段地部分占有这条物理通路，因而大大提高了通信线路的利用率。③由于采用存储转发的传输方式，使之具有下列优点：在报文交换中便于设置代码检验和数据重发设施，加之交换结点还具有路径选择，就可以做到某条传输路径发生故障时，重新选择另一条路径传输数据，提高了传输的可靠性；在存储转发中容易实现代码转换和速率匹配，甚至收发双方可以不同时处于可用状态。这样就便于类型、规格和速度不同的计算机之间进行通信；提供多目标服务，即一个报文可以同时发送到多个目的地址，这在电路交换中是很难实现的；允许建立数据传输的优先级，使优先级高的报文优先转换。缺点：①于数据进入交换结点后要经历存储、转发这一过程，从而引起转发时延（包括接收报文、检验正确性、排队、发送时间等），而且网络的通信量愈大，造成的时延就愈大，因此报文交换的实时性差，不适合传送实时或交互式业务的数据。②报文交换只适用于数字信号。③由于报文长度没有限制，而每个中间结点都要完整地接收传来的整个报文，当输出线路不空闲时，还可能要存储几个完整报文等待转发，要求网络中每个结点有较大的缓冲区。为了降低成本，减少结点的缓冲存储器的容量，有时要把等待转发的报文存在磁盘上，进一步增加了传送时延。分组交换定义：又称为报文分组交换，基于报文交换，将报文划分为更小的数据单位：报文分组（也称为段、包、分组）优点： ①加速了数据在网络中的传输。因为分组是逐个传输，可以使后一个分组的存储操作与前一个分组的转发操作并行，这种流水线式传输方式减少了报文的传输时间。此外，传输一个分组所需的缓冲区比传输一份报文所需的缓冲区小得多，这样因缓冲区不足而等待发送的机率及等待的时间也必然少得多。②简化了存储管理。因为分组的长度固定，相应的缓冲区的大小也固定，在交换结点中存储器的管理通常被简化为对缓冲区的管理，相对比较容易。③减少了出错机率和重发数据量。因为分组较短，其出错机率必然减少，每次重发的数据量也就大大减少，这样不仅提高了可靠性，也减少了传输时延。④由于分组短小，更适用于采用优先级策略，便于及时传送一些紧急数据，因此对于计算机之间的突发式的数据通信，分组交换显然更为合适些。缺点： ①尽管分组交换比报文交换的传输时延少，但仍存在存储转发时延，而且其结点交换机必须具有更强的处理能力。②分组交换与报文交换一样，每个分组都要加上源、目的地址和分组编号等信息，使传送的信息量大约增大5%～10%，一定程度上降低了通信效率，增加了处理的时间，使控制复杂，时延增加。③当分组交换采用数据报服务时，可能出现失序、丢失或重复分组，分组到达目的结点时，要对分组按编号进行排序等工作，增加了麻烦。若采用虚电路服务，虽无失序问题，但有呼叫建立、数据传输和虚电路释放三个过程。小写和大写开头的英文名字internet和Internae在意思是上有何重要区别internet:指互连网，是一个通用名词，它泛指由多个计算机网络互连而成的计算机网络，不一定要采取TCP/ip协议，通信协议可以是任意的。Internet:指因特网或互联网，是一个专有名词，它指当前全球最大的、开发的、由众多网络相互连接而成的特定的计算机网络，它采用TCP/IP协议作为通信的规则，并且前身为ARPAnet
专利申请文件结尾那一段套话的英文翻译是什么？
在很多专利申请文件中，结尾处常有这样一段话：虽然本申请以较佳实施例公开如上，但并不用于限定权利要求，任何本领域技术人员在不脱离本申请实质和精神的前提下，可以做出若干可能的变型、改变、替换……。这些变型、改变或替换均属于本发明的保护范围。中文申请文件中的这些套话，其实是从国外的申请文件（尤其是美国专利申请）中翻译和借鉴（抄）过来的。国外专利申请文件末尾的套话示例如下：示例（1）While the invention has been particularly shown anddescribed with respect to the illustrated and preferred embodiments thereof, itwill be understood by those skilled in the art that the foregoing and otherchanges in form and details may be made therein without departing from thespirit and scope of the invention.示例（2）Modifications, additions, or omissions may be made to the systems,apparatuses, and methods described herein without departing from the scope ofthe disclosure. For example, the components of the systems and apparatuses maybe integrated or separated. Moreover, the operations of the systems andapparatuses disclosed herein may be performed by more, fewer, or othercomponents and the methods described may include more, fewer, or other steps.Additionally, steps may be performed in any suitable order. As used in thisdocument, “each” refers to each member of a set or each member of a subset of a set.这段套话，根据其字面描述，貌似囊括了一切可能的变型，感觉可以堵死他人对本发明作出改进的路。这段套话，有的专利申请中有，有的没有。先不管这段套话在专利申请文件是否有必要，是否有用，我们这里来说一下“套话”这个词对应的英文是什么？“套话”的英文是什么？Legalese？Template sentences？在专利申请中，这种套话对应的英文是：boilerplate、boilerplatesentences或 boilerplatelanguage。Boilerplate的英文含义：Boilerplate, also known as standard, miscellaneous, orgeneral clauses, is located at the end of most corporate legal documents. Theyprovide instructions on how to act in a variety of situations and clarify therelationship between the parties. 套话，亦称为标准、附加或通用的条款，位于大多数企业法律文件的末尾。这些条款提供了在各种情况下进行操作的说明并明晰各方之间的关系。Boilerplate与Broadening statements二者的区别？Boierplate是格式化的文本，在实务中，这种套可有可无，只是有时候让申请文件看起来高大上。实际上对专利的保护范围并没有起到实质的作用。专利的保护范围以权利要求界定的范围为准，不会因为套话的这种“漫无边际的声明”而影响权利要求保护范围。个人不建议在专利申请文件中写入过多的这种套话。在实务中，对于申请文件末尾的那一段套话，其一个作用是表明申请文件已经写完了，并没有丢失必要的文本。也就是说，这段套话，可以起到标示申请文件已经完结的作用，对撰写人员起到心理安定的作用。当然，如果能够像美国专利申请文件那样，在申请文件末尾加上“*”这样的文本结束标识，则是更好的。最好的是，在申请文件末尾加一个“（完）”字就好了。不过，没有人会想这么多，反正也是将别的申请文件中的套话复制过来，粘贴到申请文件末尾就行了，哪用管那么多？这些套话来自国外，那外国专利律师对这些套话有什么不同的看法？This word “boilerplate”. I see it all the time, in relation to patent specifications written in the USA. But I neversee it used in relation to those drafted elsewhere.译文：“套话”这个词，在美国的专利申请文件中经常看到，但在其他国家或地区的申请文件从未见过。So, is such text an artefact of the USlegal system? What purpose does it serve (other than to impress impressionablereaders).那么，这段套话是美国法律体系的“杰作”？除了让读者觉得高大上以外，它的作用是什么？Is it so, that diligent US attorneys find creative ways to include ever more boilerplate, to signal to their clientsthat they are better drafters than all the others?美国勤奋的专利律师找到更多的富有创造性的方式来加入更多的套话文本来向他们的客户展示，显得他们比其他律师撰写得更好，是这样的吗？If so, would you say that the strategysucceeds. Dennis seems to think so.如果是这样的话，是不是说，这种策略是成功的？其实，专利申请文件撰写得是否专业与是否加入这些冗余的套话无关。
授权，也叫访问控制，即在应用中控制谁能访问哪些资源（如访问页面/编辑数据/页面操作等）。在授权中需了解的几个关键对象：主体（Subject）、资源（Resource）、权限（Permission）、角色（Role） 主体主体，即访问应用的用户，在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。资源在应用中用户可以访问的任何东西，比如访问JSP页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。权限安全策略中的原子授权单位，通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源，如：访问用户列表页面查看/新增/修改/删除用户数据（即很多时候都是CRUD（增查改删）式权限控制）打印文档等等。。。如上可以看出，权限代表了用户有没有操作某个资源的权利，即反映在某个资源上的操作允不允许，不反映谁去执行这个操作。所以后续还需要把权限赋予给用户，即定义哪个用户允许在某个资源上做什么操作（权限），Shiro不会去做这件事情，而是由实现人员提供。Shiro支持粗粒度权限（如用户模块的所有权限）和细粒度权限（操作某个用户的权限，即实例级别的），后续部分介绍。角色角色代表了操作集合，可以理解为权限的集合，一般情况下我们会赋予用户角色而不是权限，即这样用户可以拥有一组权限，赋予权限时比较方便。典型的如：项目经理、技术总监、CTO、开发工程师等都是角色，不同的角色拥有一组不同的权限。隐式角色：即直接通过角色来验证用户有没有操作权限，如在应用中CTO、技术总监、开发工程师可以使用打印机，假设某天不允许开发工程师使用打印机，此时需要从应用中删除相应代码；再如在应用中CTO、技术总监可以查看用户、查看权限；突然有一天不允许技术总监查看用户、查看权限了，需要在相关代码中把技术总监角色从判断逻辑中删除掉；即粒度是以角色为单位进行访问控制的，粒度较粗；如果进行修改可能造成多处代码修改。显示角色：在程序中通过权限控制谁能访问某个资源，角色聚合一组权限集合；这样假设哪个角色不能访问某个资源，只需要从角色代表的权限集合中移除即可；无须修改多处代码；即粒度是以资源/实例为单位的；粒度较细。请google搜索“RBAC”和“RBAC新解”分别了解“基于角色的访问控制”“基于资源的访问控制(Resource-Based Access Control)”。授权方式Shiro支持三种方式的授权：编程式：通过写if/else授权代码块完成：Subject subject = SecurityUtils.getSubject(); if(subject.hasRole(“admin”)) { //有权限 } else { //无权限 }注解式：通过在执行的Java方法上放置相应的注解完成：@RequiresRoles("admin") public void hello() { //有权限 } 没有权限将抛出相应的异常；JSP/GSP标签：在JSP/GSP页面通过相应的标签完成：<shiro:hasRole name="admin"> <!— 有权限 —></shiro:hasRole> 后续部分将详细介绍如何使用。 授权基于角色的访问控制（隐式角色）1、在ini配置文件配置用户拥有的角色（shiro-role.ini）[users] zhang=123,role1,role2 wang=123,role1规则即：“用户名=密码,角色1，角色2”，如果需要在应用中判断用户是否有相应角色，就需要在相应的Realm中返回角色信息，也就是说Shiro不负责维护用户-角色信息，需要应用提供，Shiro只是提供相应的接口方便验证，后续会介绍如何动态的获取用户角色。2、测试用例（com.github.zhangkaitao.shiro.chapter3.RoleTest）@Test public void testHasRole() { login("classpath:shiro-role.ini", "zhang", "123"); //判断拥有角色：role1 Assert.assertTrue(subject().hasRole("role1")); //判断拥有角色：role1 and role2 Assert.assertTrue(subject().hasAllRoles(Arrays.asList("role1", "role2"))); //判断拥有角色：role1 and role2 and !role3 boolean[] result = subject().hasRoles(Arrays.asList("role1", "role2", "role3")); Assert.assertEquals(true, result[0]); Assert.assertEquals(true, result[1]); Assert.assertEquals(false, result[2]); } Shiro提供了hasRole/hasRole用于判断用户是否拥有某个角色/某些权限；但是没有提供如hashAnyRole用于判断是否有某些权限中的某一个。@Test(expected = UnauthorizedException.class) public void testCheckRole() { login("classpath:shiro-role.ini", "zhang", "123"); //断言拥有角色：role1 subject().checkRole("role1"); //断言拥有角色：role1 and role3 失败抛出异常 subject().checkRoles("role1", "role3"); }Shiro提供的checkRole/checkRoles和hasRole/hasAllRoles不同的地方是它在判断为假的情况下会抛出UnauthorizedException异常。到此基于角色的访问控制（即隐式角色）就完成了，这种方式的缺点就是如果很多地方进行了角色判断，但是有一天不需要了那么就需要修改相应代码把所有相关的地方进行删除；这就是粗粒度造成的问题。基于资源的访问控制（显示角色）1、在ini配置文件配置用户拥有的角色及角色-权限关系（shiro-permission.ini）[users] zhang=123,role1,role2 wang=123,role1 [roles] role1=user:create,user:update role2=user:create,user:delete 规则：“用户名=密码，角色1，角色2”“角色=权限1，权限2”，即首先根据用户名找到角色，然后根据角色再找到权限；即角色是权限集合；Shiro同样不进行权限的维护，需要我们通过Realm返回相应的权限信息。只需要维护“用户——角色”之间的关系即可。2、测试用例（com.github.zhangkaitao.shiro.chapter3.PermissionTest）@Test public void testIsPermitted() { login("classpath:shiro-permission.ini", "zhang", "123"); //判断拥有权限：user:create Assert.assertTrue(subject().isPermitted("user:create")); //判断拥有权限：user:update and user:delete Assert.assertTrue(subject().isPermittedAll("user:update", "user:delete")); //判断没有权限：user:view Assert.assertFalse(subject().isPermitted("user:view")); } Shiro提供了isPermitted和isPermittedAll用于判断用户是否拥有某个权限或所有权限，也没有提供如isPermittedAny用于判断拥有某一个权限的接口。@Test(expected = UnauthorizedExceptionclass) public void testCheckPermission () { login("classpath:shiro-permissionini", "zhang", "123"); //断言拥有权限：user:create subject().checkPermission("user:create"); //断言拥有权限：user:delete and user:update subject().checkPermissions("user:delete", "user:update"); //断言拥有权限：user:view 失败抛出异常 subject().checkPermissions("user:view"); } 但是失败的情况下会抛出UnauthorizedException异常。到此基于资源的访问控制（显示角色）就完成了，也可以叫基于权限的访问控制，这种方式的一般规则是“资源标识符：操作”，即是资源级别的粒度；这种方式的好处就是如果要修改基本都是一个资源级别的修改，不会对其他模块代码产生影响，粒度小。但是实现起来可能稍微复杂点，需要维护“用户——角色，角色——权限（资源：操作）”之间的关系。 Permission字符串通配符权限规则：“资源标识符：操作：对象实例ID” 即对哪个资源的哪个实例可以进行什么操作。其默认支持通配符权限字符串，“:”表示资源/操作/实例的分割；“,”表示操作的分割；“*”表示任意资源/操作/实例。1、单个资源单个权限subject().checkPermissions("system:user:update");用户拥有资源“system:user”的“update”权限。2、单个资源多个权限ini配置文件role41=system:user:update,system:user:delete然后通过如下代码判断subject().checkPermissions("system:user:update", "system:user:delete");用户拥有资源“system:user”的“update”和“delete”权限。如上可以简写成：ini配置（表示角色4拥有system:user资源的update和delete权限）role42="system:user:update,delete"接着可以通过如下代码判断subject().checkPermissions("system:user:update,delete");通过“system:user:update,delete”验证"system:user:update, system:user:delete"是没问题的，但是反过来是规则不成立。3、单个资源全部权限ini配置role51="system:user:create,update,delete,view"然后通过如下代码判断subject().checkPermissions("system:user:create,delete,update:view");用户拥有资源“system:user”的“create”、“update”、“delete”和“view”所有权限。如上可以简写成：ini配置文件（表示角色5拥有system:user的所有权限）role52=system:user:*也可以简写为（推荐上边的写法）：role53=system:user然后通过如下代码判断subject().checkPermissions("system:user:*"); subject().checkPermissions("system:user"); 通过“system:user:*”验证“system:user:create,delete,update:view”可以，但是反过来是不成立的。4、所有资源全部权限ini配置role61=*:view然后通过如下代码判断subject().checkPermissions("user:view"); 用户拥有所有资源的“view”所有权限。假设判断的权限是“"system:user:view”，那么需要“role5=::view”这样写才行。5、实例级别的权限5.1、单个实例单个权限ini配置role71=user:view:1对资源user的1实例拥有view权限。然后通过如下代码判断 subject().checkPermissions("user:view:1");5.2、单个实例多个权限ini配置 role72="user:update,delete:1"对资源user的1实例拥有update、delete权限。然后通过如下代码判断subject().checkPermissions("user:delete,update:1"); subject().checkPermissions("user:update:1", "user:delete:1");5.3、单个实例所有权限ini配置 role73=user:*:1对资源user的1实例拥有所有权限。然后通过如下代码判断 subject().checkPermissions("user:update:1", "user:delete:1", "user:view:1");5.4、所有实例单个权限ini配置 role74=user:auth:*对资源user的1实例拥有所有权限。然后通过如下代码判断 subject().checkPermissions("user:auth:1", "user:auth:2");5.5、所有实例所有权限ini配置 role75=user::对资源user的1实例拥有所有权限。然后通过如下代码判断 subject().checkPermissions("user:view:1", "user:auth:2");6、Shiro对权限字符串缺失部分的处理如“user:view”等价于“user:view:”；而“organization”等价于“organization:”或者“organization::”。可以这么理解，这种方式实现了前缀匹配。另外如“user:”可以匹配如“user:delete”、“user:delete”可以匹配如“user:delete:1”、“user::1”可以匹配如“user:view:1”、“user”可以匹配“user:view”或“user:view:1”等。即可以匹配所有，不加可以进行前缀匹配；但是如“:view”不能匹配“system:user:view”，需要使用“::view”，即后缀匹配必须指定前缀（多个冒号就需要多个来匹配）。7、WildcardPermission如下两种方式是等价的： subject().checkPermission("menu:view:1"); subject().checkPermission(new WildcardPermission("menu:view:1"));因此没什么必要的话使用字符串更方便。8、性能问题通配符匹配方式比字符串相等匹配来说是更复杂的，因此需要花费更长时间，但是一般系统的权限不会太多，且可以配合缓存来提供其性能，如果这样性能还达不到要求我们可以实现位操作算法实现性能更好的权限匹配。另外实例级别的权限验证如果数据量太大也不建议使用，可能造成查询权限及匹配变慢。可以考虑比如在sql查询时加上权限字符串之类的方式在查询时就完成了权限匹配。 授权流程流程如下：1、首先调用Subject.isPermitted/hasRole接口，其会委托给SecurityManager，而SecurityManager接着会委托给Authorizer；2、Authorizer是真正的授权者，如果我们调用如isPermitted(“user:view”)，其首先会通过PermissionResolver把字符串转换成相应的Permission实例；3、在进行授权之前，其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限；4、Authorizer会判断Realm的角色/权限是否和传入的匹配，如果有多个Realm，会委托给ModularRealmAuthorizer进行循环判断，如果匹配如isPermitted/hasRole会返回true，否则返回false表示授权失败。ModularRealmAuthorizer进行多Realm匹配流程：1、首先检查相应的Realm是否实现了实现了Authorizer；2、如果实现了Authorizer，那么接着调用其相应的isPermitted/hasRole接口进行匹配；3、如果有一个Realm匹配那么将返回true，否则返回false。如果Realm进行授权的话，应该继承AuthorizingRealm，其流程是：1.1、如果调用hasRole*，则直接获取AuthorizationInfo.getRoles()与传入的角色比较即可；1.2、首先如果调用如isPermitted(“user:view”)，首先通过PermissionResolver将权限字符串转换成相应的Permission实例，默认使用WildcardPermissionResolver，即转换为通配符的WildcardPermission；2、通过AuthorizationInfo.getObjectPermissions()得到Permission实例集合；通过AuthorizationInfo. getStringPermissions()得到字符串集合并通过PermissionResolver解析为Permission实例；然后获取用户的角色，并通过RolePermissionResolver解析角色对应的权限集合（默认没有实现，可以自己提供）；3、接着调用Permission. implies(Permission p)逐个与传入的权限比较，如果有匹配的则返回true，否则false。 Authorizer、PermissionResolver及RolePermissionResolverAuthorizer的职责是进行授权（访问控制），是Shiro API中授权核心的入口点，其提供了相应的角色/权限判断接口，具体请参考其Javadoc。SecurityManager继承了Authorizer接口，且提供了ModularRealmAuthorizer用于多Realm时的授权匹配。PermissionResolver用于解析权限字符串到Permission实例，而RolePermissionResolver用于根据角色解析相应的权限集合。我们可以通过如下ini配置更改Authorizer实现：authorizer=org.apache.shiro.authz.ModularRealmAuthorizer securityManager.authorizer=$authorizer对于ModularRealmAuthorizer，相应的AuthorizingSecurityManager会在初始化完成后自动将相应的realm设置进去，我们也可以通过调用其setRealms()方法进行设置。对于实现自己的authorizer可以参考ModularRealmAuthorizer实现即可，在此就不提供示例了。设置ModularRealmAuthorizer的permissionResolver，其会自动设置到相应的Realm上（其实现了PermissionResolverAware接口），如：permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver authorizer.permissionResolver=$permissionResolver设置ModularRealmAuthorizer的rolePermissionResolver，其会自动设置到相应的Realm上（其实现了RolePermissionResolverAware接口），如：rolePermissionResolver=com.github.zhangkaitao.shiro.chapter3.permission.MyRolePermissionResolver authorizer.rolePermissionResolver=$rolePermissionResolver示例1、ini配置（shiro-authorizer.ini） [main] 自定义authorizer authorizer=org.apache.shiro.authz.ModularRealmAuthorizer 自定义permissionResolverpermissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver permissionResolver=com.github.zhangkaitao.shiro.chapter3.permission.BitAndWildPermissionResolver authorizer.permissionResolver=$permissionResolver 自定义rolePermissionResolverrolePermissionResolver=com.github.zhangkaitao.shiro.chapter3.permission.MyRolePermissionResolver 1authorizer.rolePermissionResolver=$rolePermissionResolver securityManager.authorizer=$authorizer自定义realm 一定要放在securityManager.authorizer赋值之后（因为调用setRealms会将realms设置给authorizer，并给各个Realm设置permissionResolver和rolePermissionResolver） realm=com.github.zhangkaitao.shiro.chapter3.realm.MyRealm securityManager.realms=$realm 设置securityManager 的realms一定要放到最后，因为在调用SecurityManager.setRealms时会将realms设置给authorizer，并为各个Realm设置permissionResolver和rolePermissionResolver。另外，不能使用IniSecurityManagerFactory创建的IniRealm，因为其初始化顺序的问题可能造成后续的初始化Permission造成影响。2、定义BitAndWildPermissionResolver及BitPermissionBitPermission用于实现位移方式的权限，如规则是：权限字符串格式：+资源字符串+权限位+实例ID；以+开头中间通过+分割；权限：0 表示所有权限；1 新增（二进制：0001）、2 修改（二进制：0010）、4 删除（二进制：0100）、8 查看（二进制：1000）；如 +user+10 表示对资源user拥有修改/查看权限。public class BitPermission implements Permission { private String resourceIdentify; private int permissionBit; private String instanceId; public BitPermission(String permissionString) { String[] array = permissionString.split("\\+"); if(array.length > 1) { resourceIdentify = array[1]; } if(StringUtils.isEmpty(resourceIdentify)) { resourceIdentify = "*"; } if(array.length > 2) { permissionBit = Integer.valueOf(array[2]); } if(array.length > 3) { instanceId = array[3]; } if(StringUtils.isEmpty(instanceId)) { instanceId = "*"; } } @Override public boolean implies(Permission p) { if(!(p instanceof BitPermission)) { return false; } BitPermission other = (BitPermission) p; if(!("*".equals(this.resourceIdentify) || this.resourceIdentify.equals(other.resourceIdentify))) { return false; } if(!(this.permissionBit ==0 || (this.permissionBit & other.permissionBit) != 0)) { return false; } if(!("*".equals(this.instanceId) || this.instanceId.equals(other.instanceId))) { return false; } return true; } } Permission接口提供了boolean implies(Permission p)方法用于判断权限匹配的； public class BitAndWildPermissionResolver implements PermissionResolver { @Override public Permission resolvePermission(String permissionString) { if(permissionString.startsWith("+")) { return new BitPermission(permissionString); } return new WildcardPermission(permissionString); } } BitAndWildPermissionResolver实现了PermissionResolver接口，并根据权限字符串是否以“+”开头来解析权限字符串为BitPermission或WildcardPermission。 3、定义MyRolePermissionResolver RolePermissionResolver用于根据角色字符串来解析得到权限集合。 public class MyRolePermissionResolver implements RolePermissionResolver { @Override public Collection<Permission> resolvePermissionsInRole(String roleString) { if("role1".equals(roleString)) { return Arrays.asList((Permission)new WildcardPermission("menu:*")); } return null; } } 此处的实现很简单，如果用户拥有role1，那么就返回一个“menu:*”的权限。4、自定义Realmpublic class MyRealm extends AuthorizingRealm { @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addRole("role1"); authorizationInfo.addRole("role2"); authorizationInfo.addObjectPermission(new BitPermission("+user1+10")); authorizationInfo.addObjectPermission(new WildcardPermission("user1:*")); authorizationInfo.addStringPermission("+user2+10"); authorizationInfo.addStringPermission("user2:*"); return authorizationInfo; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //和com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1. getAuthenticationInfo代码一样，省略 } } 此时我们继承AuthorizingRealm而不是实现Realm接口；推荐使用AuthorizingRealm，因为：AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)：表示获取身份验证信息；AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)：表示根据用户身份获取授权信息。这种方式的好处是当只需要身份验证时只需要获取身份验证信息而不需要获取授权信息。对于AuthenticationInfo和AuthorizationInfo请参考其Javadoc获取相关接口信息。另外我们可以使用JdbcRealm，需要做的操作如下：1、执行sql/ shiro-init-data.sql 插入相关的权限数据；2、使用shiro-jdbc-authorizer.ini配置文件，需要设置jdbcRealm.permissionsLookupEnabled为true来开启权限查询。此次还要注意就是不能把我们自定义的如“+user1+10”配置到INI配置文件，即使有IniRealm完成，因为IniRealm在new完成后就会解析这些权限字符串，默认使用了WildcardPermissionResolver完成，即此处是一个设计权限，如果采用生命周期（如使用初始化方法）的方式进行加载就可以解决我们自定义permissionResolver的问题。5、测试用例public class AuthorizerTest extends BaseTest { @Test public void testIsPermitted() { login("classpath:shiro-authorizer.ini", "zhang", "123"); //判断拥有权限：user:create Assert.assertTrue(subject().isPermitted("user1:update")); Assert.assertTrue(subject().isPermitted("user2:update")); //通过二进制位的方式表示权限 Assert.assertTrue(subject().isPermitted("+user1+2"));//新增权限 Assert.assertTrue(subject().isPermitted("+user1+8"));//查看权限 Assert.assertTrue(subject().isPermitted("+user2+10"));//新增及查看 Assert.assertFalse(subject().isPermitted("+user1+4"));//没有删除权限 Assert.assertTrue(subject().isPermitted("menu:view"));//通过MyRolePermissionResolver解析得到的权限 } }
一、单系统登录机制1、http无状态协议 web应用采用browser/server架构，http作为通信协议。http是无状态协议，浏览器的每一次请求，服务器会独立处理，不与之前或之后的请求产生关联，这个过程用下图说明，三次请求/响应对之间没有任何联系3c91a3bf-25d8-4b1f-8e4a-68535c51aaa8 但这也同时意味着，任何用户都能通过浏览器访问服务器资源，如果想保护服务器的某些资源，必须限制浏览器请求；要限制浏览器请求，必须鉴别浏览器请求，响应合法请求，忽略非法请求；要鉴别浏览器请求，必须清楚浏览器请求状态。既然http协议无状态，那就让服务器和浏览器共同维护一个状态吧！这就是会话机制2、会话机制 浏览器第一次请求服务器，服务器创建一个会话，并将会话的id作为响应的一部分发送给浏览器，浏览器存储会话id，并在后续第二次和第三次请求中带上会话id，服务器取得请求中的会话id就知道是不是同一个用户了，这个过程用下图说明，后续请求与第一次请求产生了关联8a9fb230-d506-4b19-b821-4001c68c4588 服务器在内存中保存会话对象，浏览器怎么保存会话id呢？你可能会想到两种方式请求参数cookie 将会话id作为每一个请求的参数，服务器接收请求自然能解析参数获得会话id，并借此判断是否来自同一会话，很明显，这种方式不靠谱。那就浏览器自己来维护这个会话id吧，每次发送http请求时浏览器自动发送会话id，cookie机制正好用来做这件事。cookie是浏览器用来存储少量数据的一种机制，数据以”key/value“形式存储，浏览器发送http请求时自动附带cookie信息 tomcat会话机制当然也实现了cookie，访问tomcat服务器时，浏览器中可以看到一个名为“JSESSIONID”的cookie，这就是tomcat会话机制维护的会话id，使用了cookie的请求响应过程如下图518293d9-64b2-459c-9d45-9f353c757d1f3、登录状态 有了会话机制，登录状态就好明白了，我们假设浏览器第一次请求服务器需要输入用户名与密码验证身份，服务器拿到用户名密码去数据库比对，正确的话说明当前持有这个会话的用户是合法用户，应该将这个会话标记为“已授权”或者“已登录”等等之类的状态，既然是会话的状态，自然要保存在会话对象中，tomcat在会话对象中设置登录状态如下用户再次访问时，tomcat在会话对象中查看登录状态HttpSession session = request.getSession();session.setAttribute("isLogin", true);HttpSession session = request.getSession();session.getAttribute("isLogin"); 实现了登录状态的浏览器请求服务器模型如下图描述 每次请求受保护资源时都会检查会话对象中的登录状态，只有 isLogin=true 的会话才能访问，登录机制因此而实现。二、多系统的复杂性 web系统早已从久远的单系统发展成为如今由多系统组成的应用群，面对如此众多的系统，用户难道要一个一个登录、然后一个一个注销吗？就像下图描述的这样6dfbb0b1-46c0-4945-a3bf-5f060fa80710 web系统由单系统发展成多系统组成的应用群，复杂性应该由系统内部承担，而不是用户。无论web系统内部多么复杂，对用户而言，都是一个统一的整体，也就是说，用户访问web系统的整个应用群与访问单个系统一样，登录/注销只要一次就够了9fe14ab3-4254-447b-b850-0436e628c254 虽然单系统的登录解决方案很完美，但对于多系统应用群已经不再适用了，为什么呢？ 单系统登录解决方案的核心是cookie，cookie携带会话id在浏览器与服务器之间维护会话状态。但cookie是有限制的，这个限制就是cookie的域（通常对应网站的域名），浏览器发送http请求时会自动携带与该域匹配的cookie，而不是所有cookie4d58ccfa-0114-486d-bec2-c28f2f9eb513 既然这样，为什么不将web应用群中所有子系统的域名统一在一个顶级域名下，例如“*.baidu.com”，然后将它们的cookie域设置为“baidu.com”，这种做法理论上是可以的，甚至早期很多多系统登录就采用这种同域名共享cookie的方式。 然而，可行并不代表好，共享cookie的方式存在众多局限。首先，应用群域名得统一；其次，应用群各系统使用的技术（至少是web服务器）要相同，不然cookie的key值（tomcat为JSESSIONID）不同，无法维持会话，共享cookie的方式是无法实现跨语言技术平台登录的，比如java、php、.net系统之间；第三，cookie本身不安全。 因此，我们需要一种全新的登录方式来实现多系统应用群的登录，这就是单点登录三、单点登录 什么是单点登录？单点登录全称Single Sign On（以下简称SSO），是指在多系统应用群中登录一个系统，便可在其他所有系统中得到授权而无需再次登录，包括单点登录与单点注销两部分1、登录 相比于单系统登录，sso需要一个独立的认证中心，只有认证中心能接受用户的用户名密码等安全信息，其他系统不提供登录入口，只接受认证中心的间接授权。间接授权通过令牌实现，sso认证中心验证用户的用户名密码没问题，创建授权令牌，在接下来的跳转过程中，授权令牌作为参数发送给各个子系统，子系统拿到令牌，即得到了授权，可以借此创建局部会话，局部会话登录方式与单系统的登录方式相同。这个过程，也就是单点登录的原理，用下图说明 下面对上图简要描述用户访问系统1的受保护资源，系统1发现用户未登录，跳转至sso认证中心，并将自己的地址作为参数sso认证中心发现用户未登录，将用户引导至登录页面用户输入用户名密码提交登录申请sso认证中心校验用户信息，创建用户与sso认证中心之间的会话，称为全局会话，同时创建授权令牌sso认证中心带着令牌跳转会最初的请求地址（系统1）系统1拿到令牌，去sso认证中心校验令牌是否有效sso认证中心校验令牌，返回有效，注册系统1系统1使用该令牌创建与用户的会话，称为局部会话，返回受保护资源用户访问系统2的受保护资源系统2发现用户未登录，跳转至sso认证中心，并将自己的地址作为参数sso认证中心发现用户已登录，跳转回系统2的地址，并附上令牌系统2拿到令牌，去sso认证中心校验令牌是否有效sso认证中心校验令牌，返回有效，注册系统2系统2使用该令牌创建与用户的局部会话，返回受保护资源 用户登录成功之后，会与sso认证中心及各个子系统建立会话，用户与sso认证中心建立的会话称为全局会话，用户与各个子系统建立的会话称为局部会话，局部会话建立之后，用户访问子系统受保护资源将不再通过sso认证中心，全局会话与局部会话有如下约束关系局部会话存在，全局会话一定存在全局会话存在，局部会话不一定存在全局会话销毁，局部会话必须销毁 你可以通过博客园、百度、csdn、淘宝等网站的登录过程加深对单点登录的理解，注意观察登录过程中的跳转url与参数2、注销 单点登录自然也要单点注销，在一个子系统中注销，所有子系统的会话都将被销毁，用下面的图来说明3b139d2e-0b83-4a69-b4f2-316adb8997ce sso认证中心一直监听全局会话的状态，一旦全局会话销毁，监听器将通知所有注册系统执行注销操作 下面对上图简要说明用户向系统1发起注销请求系统1根据用户与系统1建立的会话id拿到令牌，向sso认证中心发起注销请求sso认证中心校验令牌有效，销毁全局会话，同时取出所有用此令牌注册的系统地址sso认证中心向所有注册系统发起注销请求各注册系统接收sso认证中心的注销请求，销毁局部会话sso认证中心引导用户至登录页面四、部署图 单点登录涉及sso认证中心与众子系统，子系统与sso认证中心需要通信以交换令牌、校验令牌及发起注销请求，因而子系统必须集成sso的客户端，sso认证中心则是sso服务端，整个单点登录过程实质是sso客户端与服务端通信的过程，用下图描述fb29685c-487c-42b9-9ceb-6c7ee29e98c9 sso认证中心与sso客户端通信方式有多种，这里以简单好用的httpClient为例，web service、rpc、restful api都可以五、实现 只是简要介绍下基于java的实现过程，不提供完整源码，明白了原理，我相信你们可以自己实现。sso采用客户端/服务端架构，我们先看sso-client与sso-server要实现的功能（下面：sso认证中心=sso-server） sso-client拦截子系统未登录用户请求，跳转至sso认证中心接收并存储sso认证中心发送的令牌与sso-server通信，校验令牌的有效性建立局部会话拦截用户注销请求，向sso认证中心发送注销请求接收sso认证中心发出的注销请求，销毁局部会话 sso-server验证用户的登录信息创建全局会话创建授权令牌与sso-client通信发送令牌校验sso-client令牌有效性系统注册接收sso-client注销请求，注销所有会话 接下来，我们按照原理来一步步实现sso吧！1、sso-client拦截未登录请求 java拦截请求的方式有servlet、filter、listener三种方式，我们采用filter。在sso-client中新建LoginFilter.java类并实现Filter接口，在doFilter()方法中加入对未登录用户的拦截public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; HttpSession session = req.getSession(); if (session.getAttribute("isLogin")) { chain.doFilter(request, response); return; } //跳转至sso认证中心 res.sendRedirect("sso-server-url-with-system-url"); }2、sso-server拦截未登录请求 拦截从sso-client跳转至sso认证中心的未登录请求，跳转至登录页面，这个过程与sso-client完全一样3、sso-server验证用户登录信息 用户在登录页面输入用户名密码，请求登录，sso认证中心校验用户信息，校验成功，将会话状态标记为“已登录”@RequestMapping("/login") public String login(String username, String password, HttpServletRequest req) { this.checkLoginInfo(username, password); req.getSession().setAttribute("isLogin", true); return "success"; }4、sso-server创建授权令牌 授权令牌是一串随机字符，以什么样的方式生成都没有关系，只要不重复、不易伪造即可，下面是一个例子String token = UUID.randomUUID().toString();5、sso-client取得令牌并校验 sso认证中心登录后，跳转回子系统并附上令牌，子系统（sso-client）取得令牌，然后去sso认证中心校验，在LoginFilter.java的doFilter()中添加几行 // 请求附带token参数 String token = req.getParameter("token"); if (token != null) { // 去sso认证中心校验token boolean verifyResult = this.verify("sso-server-verify-url", token); if (!verifyResult) { res.sendRedirect("sso-server-url"); return; } chain.doFilter(request, response); } verify()方法使用httpClient实现，这里仅简略介绍，httpClient详细使用方法请参考官方文档HttpPost httpPost = new HttpPost("sso-server-verify-url-with-token"); HttpResponse httpResponse = httpClient.execute(httpPost);6、sso-server接收并处理校验令牌请求 用户在sso认证中心登录成功后，sso-server创建授权令牌并存储该令牌，所以，sso-server对令牌的校验就是去查找这个令牌是否存在以及是否过期，令牌校验成功后sso-server将发送校验请求的系统注册到sso认证中心（就是存储起来的意思） 令牌与注册系统地址通常存储在key-value数据库（如redis）中，redis可以为key设置有效时间也就是令牌的有效期。redis运行在内存中，速度非常快，正好sso-server不需要持久化任何数据。 令牌与注册系统地址可以用下图描述的结构存储在redis中，可能你会问，为什么要存储这些系统的地址？如果不存储，注销的时候就麻烦了，用户向sso认证中心提交注销请求，sso认证中心注销全局会话，但不知道哪些系统用此全局会话建立了自己的局部会话，也不知道要向哪些子系统发送注销请求注销局部会话3b221593-f9c4-45af-a567-4937786993e87、sso-client校验令牌成功创建局部会话 令牌校验成功后，sso-client将当前局部会话标记为“已登录”，修改LoginFilter.java，添加几行if (verifyResult) { session.setAttribute("isLogin", true); } sso-client还需将当前会话id与令牌绑定，表示这个会话的登录状态与令牌相关，此关系可以用java的hashmap保存，保存的数据用来处理sso认证中心发来的注销请求8、注销过程 用户向子系统发送带有“logout”参数的请求（注销请求），sso-client拦截器拦截该请求，向sso认证中心发起注销请求String logout = req.getParameter("logout"); if (logout != null) { this.ssoServer.logout(token); } sso认证中心也用同样的方式识别出sso-client的请求是注销请求（带有“logout”参数），sso认证中心注销全局会话@RequestMapping("/logout") public String logout(HttpServletRequest req) { HttpSession session = req.getSession(); if (session != null) { session.invalidate();//触发LogoutListener } return "redirect:/"; } sso认证中心有一个全局会话的监听器，一旦全局会话注销，将通知所有注册系统注销public class LogoutListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent event) {} @Override public void sessionDestroyed(HttpSessionEvent event) { //通过httpClient向所有注册系统发送注销请求 } }
《深入理解计算机系统》之计算机系统漫游
计算机系统漫游信息就是位+上下文源程序设计上就是一个由值0和1组成的位（又称比特）序列，8个位被组织成一组，称为字节。每个字节表示程序中的某些文本字符。大部分的现代计算机系统都实用ASCII来表示每个文本字符，这种方式实际上就是用一个唯一的单字节大小的整数值来表示每个字符。基本思想：系统中所有的信息——包括磁盘文件、内存中的程序、内存中存放的用户数据以及网络上传送的数据，都是由一串比特进行表示的。区分不同的数据对象的唯一方法是我们读到这些数据对象时的上下文。比如，在不同的上下文中，一个同样的字节序列可能表示一个整数、浮点数、字符串或者机器指令。源文件到目标文件的转化从源文件到目标文件的转化是由编译器驱动程序完成的从源文件到目标文件的翻译一共需要分为四个阶段，执行这四个阶段的程序（预处理器、编译器、汇编器和链接器）一起构成了编译系统预处理阶段预处理器（cpp）根据以字符#开头的命令，修改原始的C语言程序。（相当于完成所有宏命令的引入）编译阶段将C语言编译成为汇编语言汇编阶段汇编器将汇编语言翻译成为机器语言指令链接阶段链接阶段时将系统函数库进行引入，链接器就负责处理这种合并。结果将生成一个可执行部目标文件，可以被加载到内存中，由系统进行执行。知道编译系统工作有哪些好处优化程序性能理解链接时出现的错误避免安全漏洞系统的硬件组成总线贯穿整个系统的是一组电子管道，称作总线，它携带信息字节并负责在各个部件间进行传递。通常总线被设计成传送定长的字节块，也就是字。字中的字节数（即字长）是一个基本的系统参数，各个系统中都不尽相同。现在大多数机器字长要么是4个字节（32位），要么是8个字节（64位）I/O设备I/O（输入/输出）设备是系统和外部世界的联系通道。每个I/O设备都通过一个控制器或/适配器与I/O总线相连。控制器和适配器之间的区别主要是在于它们的封装方式。控制器是I/O设备本身或者系统的主印制电路板（通常称作主板）上的芯片组。而适配器则是一块插在主板插槽上的卡。无论如何，它们的功能都是在I/O总线和I/O设备之间进行传递信息。主存主存是一个临时存储设备，在处理器执行程序时，用来存放程序和程序处理的数据。从物理上来说，主存是由一组动态随机存取存储器（DRAM）芯片组成的。从逻辑上来说，存储器是一个相信的字节数组，每个字节都有其唯一的地址（数组索引），这些地址都是从零开始的。处理器中央处理器单元（CPU），简称处理器，是解释（或执行）存储在主存中指令的引擎。处理器的核心是一个大小为一个字的存储设备（或寄存器），成为程序计数器（PC）。在任何时候，PC都指向主存中的某条机器语言指令（即含有该指令的地址）。高速缓存针对处理器和主存在速度方面的差异，系统设计者采用了更小更快的存储设备，称为高速缓存存储器，作为暂时的集结区域，存放处理器近期可能会需要的信息。L1和L2高速缓存是用一种叫做静态随机访问存储器的硬件技术实现的。比较新的、处理能力更强大的系统甚至用三级高速缓存：L1、L2、L3。系统可以获得一个很大存储器，同时访问速度也很快，原因是利用了高速缓存的局部性原理，即程序具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据，大部分内存操作都能在快速的高速缓存中完成。存储器的层次结构寄存器L1高速缓存（SRAM）L2高速缓存（SRAM）L3高速缓存（SRAM）主存（DRAM）本地二级存储（本地磁盘）远程二级存储（分布式文件系统，Web服务器）操作系统管理硬件文件是对I/O设备的抽象表示虚拟内存是对主存和磁盘I/O设别的抽象表示进程是对处理器、主存和I/O设备的抽象表示进程上下文：操作系统保持跟踪进程运行所需的所有状态信息。这种状态，也就是上下文，包括许多信息，比如PC和寄存器文件的当前值，以及主存的内容。上下文切换：在任何一个时刻，单处理器的系统都只能执行一个进程的代码。当操作系统决定要把控制权从当前进程转移到某个新进程时，就会进行上下文切换，即保存当前进程的上下文、恢复新进程的上下文，然后将控制权传递到新进程。新进程就会从它上次停止的地方继续开始。从一个进程到另一个进程的转换是由操作系统内核管理的，内核是操作系统代码常驻内存的部分。注意：内核并不是一个独立的进程，相反，它是系统管理全部进程所用代码和数据结构的集合。线程一个进程实际上可以由多个成为线程的执行单元组成，每个线程都运行在进程的上下文中，并共享同样的代码和全局数据。虚拟内存虚拟内存是一个抽象概念，它为每一个进程提供了一个假象，即每个进程都在独占地使用主存。每个进程看到的内存都是一致的，称为虚拟地址空间。（此处暂不赘述）文件文件就是字节序列，仅此而已。每一I/O设备，包括磁盘、键盘、显示器，甚至网络，都可以看成是文件。Amdahl定律主要思想：当我们对系统的某个部分加速时，其对系统整体性能的影响取决于该部分的重要性和加速程度。主要观点：要想显著加速整个系统，就必须提升全系统中相当大的部分的速度。并发和并行并发：两个任务交替执行并行：两个任务同时执行多核处理器是值将多个CPU集成到一个集成电路芯片上。小结计算机系统由硬件和软件组成，它们共同协作以运行应用程序。计算机内部的信息被表示为一组组的位，它们依据上下文有不同的解释方式。程序被其他程序翻译成不同的形式，开始时时ASCII文本，然后被编译器和链接器翻译成二进制可执行文件。处理器读取并解释存放在主存里的二进制指令。因为计算机花费了大量的时间在内存、I/O设别和CPU寄存器之间复制数据，所以将系统中的存储设备划分成层次结构——CPU寄存器在顶部，接着是多层的硬件高速缓存存储器、DRAM主存和硬盘存储器。在层次模型中，位于更高层的存储设备比底层的存储设备要更快，单位比特的造价也更高。层次结构中较高层次的存储设备可以作为较低层次设备的高速缓存。通过理解和运用这种存储层次结构的知识，程序员可以优化C程序的性能。操作系统内核是应用程序和硬件之间的媒介。它提供了三个基本的抽象：文件是对I/O设备的抽象，虚拟内存是对主存和磁盘的抽象，进程是处理器、主存和I/O设备的抽象。最后，网络提供了计算机系统之间通信的手段，从特殊系统的角度来看，网络就是一种I/O设备。
什么是面向切面编程官方：在软件业，AOP为Aspect Oriented Programming的缩写，意为：面向切面编程，通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续，是软件开发中的一个热点，也是Spring框架中的一个重要内容，是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离，从而使得业务逻辑各部分之间的耦合度降低，提高程序的可重用性，同时提高了开发的效率个人理解：面向切面编程旨在较少冗余代码，使用的是一种代码横向抽取（举例：不同的方法拥有三句同样的代码，那这个代码就可以抽取出来，但使用父类、接口、继承、实现会极大的提高代码的耦合度，因此我们采用面向切面编程，把这三句代码全部抽取到一个方法当中）xml配置版实例代码接口：public interface Hello { void sayHello(); } 实现类public class HelloImpl implements Hello { @Override public void sayHello() { System.out.println("hello"); } }增强（相当于抽取出来的部分代码）public class BuildHello { void beforeBuilder(){ System.out.println("start..."); } void afterBuilder(){ System.out.println("ending..."); } } xml中bean实例的配置 <bean id="hello" class="com.soft1851.spring.aop.hello.HelloImpl"/> <bean id="beforeAdvice" class="com.soft1851.spring.aop.hello.BuildHello"/> <aop:config > <aop:aspect id="before" ref="beforeAdvice"> <aop:pointcut id="beforeCut" expression="execution(* com.soft1851.spring.aop.hello..*.*(..))"/> <aop:before method="beforeBuilder" pointcut-ref="beforeCut"/> </aop:aspect> <aop:aspect id="after" ref="beforeAdvice"> <aop:pointcut id="afterCut" expression="execution(* com.soft1851.spring.aop.hello..*.*(..))"/> <aop:after-returning method="afterBuilder" pointcut-ref="afterCut"/> </aop:aspect> </aop:config>注意要点：aop-config是配置整体的切面编程，而aop-aspect是设置切点和增强的容器，aop-pointcun用于设置切点，aop:before用于设置前置增强，aop:after-returning用于设置后置增强注解版实例代码dao底层代码@Repository public class ForumDaoImpl implements ForumDao { private JdbcTemplate jdbcTemplate; public ForumDaoImpl(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public List<Forum> selectAll() { String sql = "SELECT * FROM t_forum "; System.out.println(jdbcTemplate); return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Forum.class)); } @Override public int insert(Forum forum) { System.out.println("开始插入数据"); String sql = "INSERT INTO t_forum VALUES (Null,?)"; Object[] args = {forum.getForumName()}; return jdbcTemplate.update(sql, args); } @Override public int[] batchInsert(List<Forum> forums) { String sql = "INSERT INTO t_forum VALUES (NULL,?)"; return jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement preparedStatement, int i) throws SQLException { preparedStatement.setString(1,forums.get(i).getForumName()); } @Override public int getBatchSize() { return forums.size(); } }); } @Override public int delete(int forumId) { System.out.println("开始删除数据"); String sql = "DELETE FROM t_forum WHERE forum_id = ?"; Object args = forumId; return jdbcTemplate.update(sql,args); } /** * 修改forum中的字段值 * @param forum * @return */ @Override public int update(Forum forum) { String sql = "UPDATE t_forum SET forum_name = ? WHERE forum_id = ?"; Object[] args = {forum.getForumName(), forum.getForumId()}; return jdbcTemplate.update(sql, args); } @Override public Forum get(int forumId) { String sql = " SELECT * FROM t_forum WHERE forum_id = ? "; Object[] args = {forumId}; return jdbcTemplate.queryForObject(sql, args,new BeanPropertyRowMapper<>(Forum.class)); } } 增强类@Aspect public class MyAdvice { /** * 配置切点 */ @Pointcut("execution(* com.soft1851.spring.web.dao..*.insert*(..))") public void pointCut() { } /** * 配置前置增强 */ @Before("MyAdvice.pointCut()") public void beforeMethod() { System.out.println("等待数据插入"); } /** * 配置后置增强 */ @AfterReturning("execution(* com.soft1851.spring.web.dao..*.*(..))") public void afterMethod(){ System.out.println("关闭数据库"); } } xml实例配置 <bean name="forumDao" class="com.soft1851.spring.web.dao.impl.ForumDaoImpl"/> <bean name="myAdvice" class="com.soft1851.spring.web.interceptor.MyAdvice"/> <aop:aspectj-autoproxy/>注意要点：增强类头部要写注解@Aspectxml文件中照样还需要配置，只是配置代码减少@afterReturning是后置增强（也就是中央代码执行结束后执行）aop支持的切点execution——匹配运行方法的连接点within——使匹配连接点限定在特定类型this——限定匹配的连接点是给定类型的实例target——限定匹配连接点的目标是给定类型的实例args——限定连接点的参数是给定类型的实例@target——限定匹配的连接点的运行的对象有该类型的注解@args——运行时传递的参数拥有给定类型的注解@within——限定匹配给定指定注解类型的连接点@annotation——限定连接点拥有指定的注解bean——使得连接点匹配特质的bean或bean集合exexction表达式匹配所有的公有方法execution(public (..))匹配所有以set开头的方法execution( set(..))匹配所有AccountService接口定义的方法execution( com.xyz.service.AccountService.(..))匹配所有service包内的方法execution(* com.xyz.service..(..))匹配所有service包以及子包内的方法execution(* com.xyz.service...(..))所有在service包内的连接点within(com.xyz.service.*)所有service包以及子包内的连接点within(com.xyz.service..*)所有实现AccountService接口的连接点this(com.xyz.service.AccountService)所有的目标对象实现AccountService接口的连接点target(com.xyz.service.AccountService)有一个参数并且运行时传入参数为Serializable的连接点args(java.io.Serializable)目标对象有@Transactional注解@target(org.springframework.transaction.annotation.Transactional)定义的目标对象类型含有@Transactional注解@within(org.springframework.transaction.annotation.Transactional)含有@Transactional注解的可运行方法@annotation(org.springframework.transaction.annotation.Transactional)含有一个参数并且在运行时传入的参数含有@Classified注解@args(com.xyz.security.Classified)bean名字定义为tradeService的连接点bean(tradeService)bean名字满足Service命名的所有连接点bean(Service)总结本文我们主要学习了如何使用XML配置方式来完成Spring AOP的使用。文章中我们学习了很多配置标签，完成了切面定义、切点定义以及多种增强方法的定义。至此我们已经掌握了Spring AOP的基本知识点，可在具体的项目开发中使用。不过学了那么多的知识点，我们还是需要一定的实践来进行消化吸收。