::: {.center}
xx公司

2020-01-01
:::

文档管理

合理地管理主文档,确保文档版本的及时更新,同时保持备份文档和源文档的一致性。

版本管理


本版本修订日期 2019-08-12 生效日期 2019-08-12


版本 生效日期 变更内容 编制人


V1.0 2020-01-01 初稿编写完成 xx

范围

标识

本条应包含本文档及本文档适用的系统和软件的完整标识,(若适用)包括标识号、标题、缩略词语、版本号和发行号。

系统概述

本条应简述本文档适用的系统和软件的用途,它应描述系统和软件的一般特性;概述系统开发、运行和维护的历史;标识项目的开发方、业主方、总集方、监理方等;标识当前和计划的运行现场等。

文档概述

本条应概述本文档的用途和内容,并描述与其使用有关的保密性和私密性的要求。

引用文档

应列出本文档引用的所有文档的编号、标题、修订版本、日期和来源。

术语和定义

提供此文档中用到的专门术语的定义和缩写词的原词组。

测试目标和测试内容

测试目标

描述本测试计划的测试目标。如完成软件的出厂测试,达到可交付验证测试的目的。

测试的功能和特性

概要说明本次需要测试的功能和特性

不测的功能和特性

说明本次不测的功能、特性及原因

测试质量目标

简要说明测试的质量目标,如:

测试计划中所有测试方法和模块已经执行通过

所有的测试案例已经执行过

所有的重要等级Bug已经解决并由测试验证

应交付的测试成果文档

说明最终需要交付的测试成果文档,包括软件测试计划、软件测试说明(含测试用例、软件测试报告、测试问题报告等。文档名和数量因具体项目而异,应确定文档责任人。

测试策略

整体测试策略

说明基本的测试过程和策略。如测试人员在需求和设计阶段参与需求评审和设计评审、在开发完成前实施测试案例设计和测试开发,在系统开发完成之后正式执行测试等。

问题等级划分

划分软件缺陷的等级分类代码。推荐的等级划分如下:

A 严重缺陷

文档与软件不符、文档严重不足、关键性内容错误。软件需求明显未实现。软件不能正常运行,导致系统崩溃或资源严重不足。例如: 由程序引起的死机、非法退出;死循环;导致数据库发生死锁;错误操作导致的程序中断;与数据库连接错误;数据通讯错误等

B 较严重缺陷中现

软件能够运行,但当前缺陷严重影响软件基本功能的正确实现。例如:软件功能与需求不符;程序接口错误;数据流错误;数值计算错误等。

C 一般性缺陷

软件能够运行,但当前缺陷影响部分功能的正确实现。例如:界面错误;打印内容或格式错误;简单的输入限制未放在前台进行控制;删除操作未给出提示;数据输入没有边界值限定或不合理等。

D 较小缺陷

软件能够运行,软件功能基本实现,但当前缺陷使操作者不方便或遇到麻烦。例如:辅助说明描述不清楚;显示格式不规范;系统处理未优化;长时间操作未给用户进度提示;提示窗口文字未采用行业术语等。

开始/中断/完成标准

测试启动条件

说明启动测试的条件。如对于出厂测试,已经过评审、测试人力资源已经具备、软件需求规格说明/详细设计文档/测试说明文档已经过确认、内部模块测试和组装测试已经完成等。

其中业主方、总集方、监理方交付验证测试的准入条件:

软件源代码正确通过编译且为最终版本;

软硬件测试平台已搭建并已配置完成;

业主方具有测试所需的各种文档(纸质、电子版);

业主方获得的各种文档均与最终版本的软件相对应,且全部通过审核;

承建方、监理方已完成测试并提交测试报告。

测试中断条件

说明测试中断的条件。例如:

在测试中发现A类bug,并导致后续的测试无法继续时;

已执行完所有的测试用例,并已报告给承建方,等待承建方在限期内改正时。

测试终止条件

说明在什么条件下终止本计划所述产品交付验收证测试。如:

正常终止条件:

按照测试计划完成所有定义的测试用例;

客观、详细地记录了软件测试过程和软件测试中发现的问题;

有效完成了定位缺陷的回归测试循环;

测试中未发现A、B缺陷,以及少于n个C类缺陷;

提交测试报告。

异常终止条件

太多的A或B类缺陷以致测试无法进行或测试周期已结束。

或者针对软件规模,规定C类bug不超过n个

测试工具选择

说明需要用到的测试工具软件,应包括软件版本号。

测试流程

阐述或引用测试流程,应包括问题报告、审核、分配、跟踪、回归等各方面。测试流程与承建方内部质量管理制度和业主方的要求相关。

测试技术和方法

确定测试需要的技术或方法,如测试数据生成与验证技术、测试数据输入技术、测试结果获取技术等;明确测试用例的设计和选择方法,针对不同类型的测试(功能测试、性能测试、容量测试、用户界面测试,

根据需要,应给出针对性的测试用例设计要求。

评价准则和方法

测试通过准则

定义系统测试通过准则,以下是一个测试通过准则的示例:

可执行软件与需求规格说明书、设计说明书是一致的;

测试覆盖率应达到100%

测试用例通过率要达到95%;

软件缺陷终结率达到100%

系统页面风格符合规范化要求,程序代码编写以及各种命名符合规范化要求。

各模块正确衔接。

对异常数据应有相应的提示信息,并能安全终止异常操作。

对测试结果处理方法

测试结果分为通过和未通过。测试达到通过准则的要求称为”通过”,测试结果没有达到测试通过准则的称为”未通过”。说明对不同测试结果的处理方法。

测试项目组织与资源

参与部门和组织

说明参与测试的组织/部门

角色和职责

说明参与测试的组织/部门中各角色划分及职责。

人员和培训要求

说明参与测试的组织/部门的员及角色对应关系。以及是否需要预先进行相关培训。

关键资源

说明需要用到的关键资源

测试活动和进度计划应根据测试资源和测试项目内容,分解测试活动,分配测试资源,编制测试进度计划。以下是一个进度计划的示例:

测试阶段 开始时间 完成时间 测试人员 阶段完成标志


制定测试计划
需求 Review
设计 Review
设计测试用例
测试开发
测试环境准备
测试实施
功能测试
集成测试
性能测试
系统测试
验收测试
文档编写

风险分析及应对措施

风险分析是评测项目管理的重要内容。常见的风险包括供测试的软件版本混乱、软件缺陷修改时间过长、回归不足引发新的问题、测试方和开发方对缺陷的认识存在差异等。建议以列表的方式给出识别的风险并提供针对性的缓解措施。

内容审核要点:

测试内容是否完整,是否涵盖了测试目的、内容、方法策略、资源、进度安排等各方面;

测试进度安排是否合理;

测试资源要求是否充分;

测试技术和方法的选择是否可行;

是否包含了对测试结果评价分析标准;

是否包含了对测试过程的跟踪和控制规程。

参考

https://www.jianshu.com/p/a7984927cfb9

::: {.center}
xx公司

2020-01-01
:::

文档管理

合理地管理主文档,确保文档版本的及时更新,同时保持备份文档和源文档的一致性。

版本管理


本版本修订日期 2019-08-12 生效日期 2019-08-12


版本 生效日期 变更内容 编制人


V1.0 2020-01-01 初稿编写完成 xx

范围

标识

本条应包含本文档及本文档适用的系统和软件的完整标识,(若适用)包括标识号、标题、缩略词语、版本号和发行号。

系统概述

本条应简述本文档适用的系统和软件的用途,它应描述系统和软件的一般特性;概述系统开发、运行和维护的历史;标识项目的开发方、业主方、总集方、监理方等;标识当前和计划的运行现场等。

文档概述

本条应概述本文档的用途和内容,并描述与其使用有关的保密性和私密性的要求。

引用文件

应列出本文档引用的所有文档的编号、标题、修订版本、日期和来源。

术语和缩略语

提供此文档中用到的专门术语的定义和缩写词的原词组。

测试准备

以下按照软件测试类型(如:功能测试、性能测试、可靠性测试等)分章节编写。

每一项测试类型均应有唯一的标识号,应描述如何准备并获取测试资源,如测试环境所必须的软件、硬件、数据资源等;必要时,应描述如何准备测试程序,如开发测试接口所需的数据仿真、业务仿真程序以及测试支持软件等。

(测试名称、唯一标识号)

设施环境要求

描述对测试场所、设施和环境的要求。(若有)分析上述差异对测试可能造成的影响。

硬件准备

描述对测试硬件的要求。分析硬件差异对测试可能造成的影响。

软件准备

描述对测试软件的要求。分析软件差异对测试可能造成的影响。

数据准备

描述对测试数据的要求。分析数据差异对测试可能造成的影响。

其它测试准备

描述对测试程序等分面的其他测试准备工作。

测试项分解

将需测试的内容进行层次化的分解形成测试项,并进行标识命名。对最终分解后的每个测试项,说明测试用例设计方法的具体应用、测试数据的选择依据等。测试项与具体的功能和性能要求对应,测试项还应包含对用户文档(用户手册、安装部署手册)的测试。

测试说明

逐层对测试项和测试用例进行标识和说明。其中,测试用例至少应包含:所属测试项、用例名称标识、用例说明、对应需求、前提和约束、执行步骤、预期结果等。

注:测试用例可采用表格方式,可作为本文档的附件另行成文,以下是对测试用例相关项的解释。

对应需求:说明测试所依据的内容来源,如软件需求规格说明书中的需求功能编号或具体条款

测试说明:简要描述测试的对象、目的和所采用的测试方法。

前提和约束:说明实施该测试用例的前提条件和约束条件,如环境条件、准备工作等。

执行步骤:编写按照执行顺序排列的一系列相对独立的步骤,每一个执行步骤应包括测试操作动作、测试程序输入或设备操作、期望的测试结果。

预期结果:期望测试结果应有具体内容(如确定的元数值、业务流程状态等),不应是不确切的概念或笼统的描述。

测试用例执行顺序

应确定软件测试用例的执行顺序,从而合理安排测试执行过程,避免重复执行测试用例,提高测试工作效率。同时,通过合理的测试用例执行顺序实现对完整的业务流程的确认和验证。

内容审核要点:

测试说明的范围与合同及其附件要求等是否一致;是否覆盖了全部软件需求;是否覆盖了全部测试需求;

软件测试准备是否充分;

软件测试分解是否合理;测试设计思路是否清晰;测试技术实现方法是否科学;

对接口的分析和说明是否完整、准确;对接口测试的正常和容错说明是否全面;

测试用例是否充分;除正常操作、正常流程、正常数据外,是否覆盖了可测试的异常情况;

测试用例的执行顺序是否合理;是否可覆盖必要的业务流程。

参考

https://www.jianshu.com/p/a7984927cfb9

::: {.center}
xx公司

2020-01-01
:::

文档管理

合理地管理主文档,
确保文档版本的及时更新,同时保持备份文档和源文档的一致性。

版本管理


本版本修订日期 2019-08-12 生效日期 2019-08-12


版本 生效日期 变更内容 编制人


V1.0 2020-01-01 初稿编写完成 xx

引言

标识

本条应包含本文档的完整标识,以及本文档适用的系统和软件的完整标识,包括标识号、标题、缩略词语、版本号和发行号。

系统概述

本条应简述本文档适用的系统和软件的用途,应描述系统和软件的一般特性;概述系统开发、运行和维护的历史;标识项目的业主方、用户、承建方、监理方等;标识当前和计划的运行现场等。

文档概述

概述本文档的用途和内容,并描述与其使用有关的保密性和私密性的要求。

与其他计划之间的关系

描述本计划和其他项目管理计划的关系。

输入基线

给出编写本项目开发计划的输入基线,如业务需求说明书等。

引用文件

应列出本文档引用的所有文档的编号、标题、修订版本、日期和来源。

项目范围及约束条件

交付产品

列出本项目的交付产品成果,包括软件程序、交付文档等,以及各交付成果的交付期限。

业务需求和约束条件

分条阐述项目的业务需求和约束条件;

其它方面的需求和约束条件

分条阐述其它方面的约束,如项目进度要求、保密性等。

项目目标

综述项目进度目标、成本目标、质量目标。

项目总体计划

软件开发过程和里程碑

描述要采用的软件开发过程。计划应覆盖论及它的所有合同条款,确定已计划的开发阶段(适用)、目标和各阶段要执行的软件开发活动。

里程碑设置分条阐述里程碑/阶段名称、期限、里程碑标志说明(进入条件和输出)、评审方式等。

提供一张工作产品矩阵表,描述各工作产品的编号、名称、产生阶段、评审方式等。工作产品包括各阶段产生的过程文档和技术文档等,是工作任务分解和配置管理计划制定的重要依据。

软件开发方法

描述或引用要使用的软件开发方法,包括为支持这些方法所使用的手工、自动工具和过程的描述。

软件产品标准

描述或引用在表达需求、设计、编码、测试用例、测试过程和测试结果方面要遵循的标准和要求。对要使用的各种编程语言应提供编码标准。

评审途径

阐述软件内部评审的方式,以及需方或授权代表(总集、监理)实施软件产品和活动评审的途径和方式。

软件配置管理

描述针对本项目所采用和遵循的软件配置管理方法.包括配置项的标识、控制、状态统计、审核、交付等。

具体的配置项识别和管理可在配置管理计划中另文给出。

软件质量保证

描述针对在本项目所采用和遵循的软件质量保证方法。包括软件质量保证评估、软件质量保证记录和处理、第三方独立性保证等。质量保证计划也可另文给出。承建方应在计划中落实下述内部审核和检查工作:

软件计划审核:在软件计划编制阶段结束后必须进行软件计划审核,以确保在软件开发计划、软件配置管理计划、软件质量保证计划中所规定的计划的合适性。

软件需求审核:在软件需求分析阶段结束后必须进行软件需求审核,以确保在软件需求规格说明中所规定的各项需求的合适性。

软件概要设计审核:在软件设计结束后必须进行软件设计的审核,以评价软件概要设计说明、数据结构设计说明中所描述的软件设计在总体结构、外部接口、主要部件功能分配、全局数据结构以及各主要部件之间的接口等方面的合适性;以及在数据结构、功能、算法和过程描述等方面的合适性。

软件详细设计、编码阶段的审核:在软件详细设计和编码实现完成之后要对软件详细设计说明、软件测试计划、软件测试说明(含软件测试用例)、目标程序生成说明进行审核,以确认业主方的业务需求是否得到满足,验证软件需求规格说明中的需求是否已由软件设计说明描述的设计实现,软件设计说明表达的设计是否已由编码实现。

出厂测试审核:在完成出厂测试之后要对源代码交付验证说明、软件测试报告(含源代码交付验证报告)以及按照合同要求应提交给业主方的所有文档(如:软件用户手册等)进行审核,以评价待交付的程序及文档的质量是否满足出厂交付要求、覆盖了业主方的业务需求、做好了交付准备。

部署审核:在配合监理方和业主方完成测试工作后承建方进入系统部署阶段,在完成软件部署并配合完成系统联调联试后,要对部署实施报告进行审核,以验证部署实施的真实性以及外部接口的正确性。

问题跟踪和处理(更正活动)

描述软件更正活动中要遵循的方法,包括不同阶段的问题发现、纪录、报告、处理、审核和更正流程,问题/bug跟踪系统的选用等。须论及出厂测试、需方验证测试、试运行三个阶段。

档案收集

阐述作为承建方在项目进行过程中进行自身档案收集管理的方法,包括纸质档案。

项目估算及进度计划

工作任务分解

分解项目工作任务,得出工作任务分解结构(WBS)。

规模估算

估算项目规模,如需新编的代码行数、文档页数等。

工作量估算

根据规模估算及项目经验,估算项目工作量。

进度计划

在工作任务分解结构(WBS)、工作量估算的基础上,进行活动排序、资源分配,进而编制进度计划甘特图,标识各活动的依赖关系、资源分配情况、起止时间等。

风险估计及应对方法

逐条给出识别的风险及其风险估计量化指标(可能性、严重性等级)、相应的对策和缓解方案。建议以列表的方式给出。

项目跟踪与变更管理

项目日常跟踪

阐述项目日常跟踪方法,包括由业主方和授权代表(监理方)参与的项目跟踪。

里程碑评审

阐述或引用项目各里程碑评审的方法。

变更管理

包括计划变更、需求变更等的处理流程、机制和方法。

项目组织和资源

项目组织

本条应描述本项目要采用的组织结构,包括涉及的组织机构、机构之间的关系、执行所需活动的每个机构的权限和职责。

项目资源

本条应描述适用于本项目的资源。 主要包括:

人力资源,分条说明投入此项目的人员、职责、投入阶段、地理位置和涉密程度等;

开发人员要使用的设施,包括执行工作的地理位置、要使用的设施、保密区域和运用合同项目的设施的其他特性;

为满足合同需要,需方应提供的设备、软件、服务、文档、资料及设施及需要提供的时间。

内部培训

根据项目的技术要求和项目成员的情况,确定是否需要进行项目培训,并制订培训计划。如不需要培训,应说明理由。

注解

本章应包含有助于理解本文档的一般信息(例如原理)。本章应包含为理解本文档需要的术语和定义,所有缩略语和它们在文档中的含义的字母序列表。

附录

附录可用来提供那些为便于文档维护而单独出版的信息(例如图表、分类数据)。为便于处理,附录可单独装订成册。附录应按字母顺序(A,
B等)编排。

内容审核要点:

项目范围阐述与合同及其附件要求等是否一致;

工作任务分解与项目范围/需求是否一致;

工作任务分解、工作量估计和活动进度安排是否合理;

计划内容是否包括软件项目管理的各个必要的方面;

作产品定义是否包含需方所要求的各相关必要文档;

织机构和人力资源安排和分配是否合理并符合合同相关要求;

目日常跟踪管理是否具有可操作性;

目开发过程中的问题/bug处理机制是否具有可操作性;

案收集管理是否具有可操作性。

是否制定了合适的配置管理策略和质量保证策略。

参考

https://www.jianshu.com/p/a7984927cfb9

::: {.center}
xx公司

2020-01-01
:::

文档管理

合理地管理主文档,确保文档版本的及时更新,同时保持备份文档和源文档的一致性。

版本管理


本版本修订日期 2019-08-12 生效日期 2019-08-12


版本 生效日期 变更内容 编制人


V1.0 2020-01-01 初稿编写完成 xx

概述

编写目的

说明编写目的,指出本文档的预期读者。

背景

说明系统的项目背景,使用本系统所包含的用户。

范围

说明本文档的适用范围。

参考资料

列出所参考的文档,如其它的用户文档。

软件清单

列出所提交的软件产品的程序及其它必要的附件文件,包括可能的支持软件、第三方包、脚本文件、说明文档等,对清单中的名项给出必要的说明。

运行环境要求

说明系统进行安装部署时,对运行环境的要求,分为硬件环境和软件环境,包括服务器、客户端。

服务器端的运行环境,硬件方面需要指出最低参数要求,软件方面需要指出如操作系统、数据库软件、web应用服务器的名称、版本信息等。

客户端的运行环境,硬件方面需要指出最低参数要求,软件方面需要列出操作系统版本、运行依赖的浏览器版本、需要安装的驱动程序等。

支撑软件的安装、部署和配置

给出所有支撑软件的安装、部署和配置步聚,可以引用第三方文档。

<支撑软件X>的安装、部署和配置

支撑软件X安装、部署和配置步聚。

应用程序的安装、部署和配置

<应用程序x>的安装、部署和配置

安装、部署前的准备工作

说明系统进行安装部署前,需要进行的前期准备工作。如对操作系统、数据库、web应用服务器进行相应的参数设置,数据库的初始化等等。

部署环境概要说明

可以对部署的服务器环境,如文件目录结构,进行概要说明。

依赖

系统在进行安装、部署时,如果需要对外部系统运行或接口有依赖关系,在此列出。

安装、部署和配置步聚

列出详细的安装、部署和配置过程,包括对相关配置文件内容的修改。

程序的启动和停止

给出软件的启动和停止说明

内容审核要点:

所述运行环境和支撑软件是否详细完备;

部署前的准备工作是否详细完备;

安装过程是否详细易懂。

参考

https://www.jianshu.com/p/a7984927cfb9

::: {.center}
xx公司

2020-01-01
:::

文档管理

合理地管理主文档,
确保文档版本的及时更新,同时保持备份文档和源文档的一致性。

版本管理


本版本修订日期 2019-08-12 生效日期 2019-08-12


版本 生效日期 变更内容 编制人


V1.0 2020-01-01 初稿编写完成 xx

概述

背景

说明系统的项目背景,本系统的使用和运维单位等。

范围

说明本文档的适用范围。

源代码交付清单

以表格的形式,列出所提交的源代码光盘中所有的文件目录结构,对各目录和子目录加以说明。对主要源代码程序加以说明。对其它必要的附件(包括第三方包、类库、配置文件、程序的帮助说明文档等)也要加以说明。

编译和打包的说明

编译环境和工具要求

如果需要,对编译环境和所需要工具进行简要说明。

编译和打包步聚

说明对源代码进行编译和打包的详细步聚,要能根据这些步骤生成可用的目标程序。尽量使用脚本方式生成目标程序。

生成物说明

对编译生成的执行程序进行简要说明。

内容审核要点:

交付清单是否详细完备;

说明文字是否明确易懂;

交付物版本是否和实际系统的程序版本一致(能根据本文档生成与实际生产系统一致的程序);

参考

https://www.jianshu.com/p/a7984927cfb9

封面

::: {.center}
xx公司

2020-01-01
:::

文档管理

合理地管理主文档,确保文档版本的及时更新,同时保持备份文档和源文档的一致性。

版本管理


本版本修订日期 2019-08-12 生效日期 2019-08-12


版本 生效日期 变更内容 编制人


V1.0 2020-01-01 初稿编写完成 xx

概述

编写目的

说明编写目的,指出预期的读者范围。

背景

简要描述此次上线部署的背景。

参考资料

列出所参考的文档,如安装部署手册等。

工作内容

说明本次上线部署的工作内容。即要做哪几件事。

实际完成情况

对照工作内容,详细说明本次实际部署过程和工作任务完成情况。

部署测试情况

详细说明本次部署测试情况,测试内容是否都获通过。

遗留工作和待解决问题

如果有遗留工作或问题,需要在此部分列出。

签字栏

本报告作为验收的依据,需要相关方的签字。应留有签字栏。

内容审核要点:

任务描述是否明确;

部署实际完成情况是否描述清楚;

部署测试情况是否描述清楚;

部署部署过程是否与实际情况一致。

参考

https://www.jianshu.com/p/a7984927cfb9

::: {.center}
xx公司

2020-01-01
:::

文档管理

合理地管理主文档,
确保文档版本的及时更新,同时保持备份文档和源文档的一致性。

版本管理


本版本修订日期 2019-08-12 生效日期 2019-08-12


版本 生效日期 变更内容 编制人


V1.0 2020-01-01 初稿编写完成 xx

概述

编写目的

说明编写目的。本文档主要供开发方项目组内部使用,其目的包括:

  • 便于项目组测试人员通过版本控制机制,每次生成新的目标程序在干净的环境下进行自测(包括出厂测试);
  • 便于今后源代码的交付。

背景

说明软件系统的名称和作用,以及本可执行程序属于软件系统的哪一部分。

参考资料

列出所参考的文档,如需求规格说明、设计说明等。

工具和支撑软件要求

指出目标程序生成的工具要求,如对于使用ant
脚本生成目标程序的方式,需要ant工具的支持。
指出目标程序执行的支撑软件要求,如虚拟机、应用服务器或其它底层软件支持。
注:建议尽量提供生成脚本而不依赖于可视化开发工具来生成目标代码,以便于测试方能根据版本控制库中的源码迅速生成目标程序进行测试和回归测试。

源代码获取

指出从何处及如何获取源代码,包括脚本程序代码。一般情况下,在开发团队内部,源代码的获取方法和步骤与项目源代码配置管理方式相关。

目标程序生成步骤

详细说明目标程序生成步骤,步骤的多少与项目复杂度、所编制脚本功能强弱等因素相关。
注:不管步骤多少,整个步骤应是明确和完备的,并提供方便的清除所生成目标程序的方法。

目标程序的执行

说明如何运行生成的目标程序,一般包括以下内容:

  • 从何处获取生成的目标程序;
  • 执行前的准备工作,如配置文件的修改、数据库表定义/删除的脚本的执行、数据库表的初始化数据生成/清除脚本的执行等;
  • 软硬件运行环境的建立;
  • 程序的部署和执行。

内容审核要点:

  • 所述各目标程序生成说明文档是否涵盖项目整个软件系统;
  • 所述步骤是否明确和详细;
  • 是否内部有版本控制机制。

参考

https://www.jianshu.com/p/a7984927cfb9

::: {.center}
xx公司

2020-01-01
:::

文档管理

合理地管理主文档,
确保文档版本的及时更新,同时保持备份文档和源文档的一致性。

版本管理


本版本修订日期 2019-08-12 生效日期 2019-08-12


版本 生效日期 变更内容 编制人


V1.0 2020-01-01 初稿编写完成 xx

概述

编写目的

描述该文档的编写目的。

参考资料

列出此文档所参考的文档,如软件需求规格说明、软件设计说明等。

分类

(若有)对存储数据分类进行简要说明(数据可以按照子系统进行划分)。

  • 第全局类数据及其说明
  • 第二类数据及其说明
  • 第三类数据及其说明

使用它的程序

描述对应不同分类的数据,所使用它的外部程序或者业务系统。

约定

描述数据库设计方面的标识约定、设计约定、特殊约定等。

数据定义

全局数据

主要应用范围: 作用: 数据库定义文件: ER图文件:

表结构设计

  1. .1 表一与触发器

    表一结构说明,包括:表名、表说明(内容、作用)、索引、各列属性。
    各列属性,包括:列英文名、列中文名、 数据类型、长度、 列取值含义等。
    触发器列表,包括:名称、说明、定义等。

  2. .2 表二与触发器

    ……

视图设计

  1. .1 视图一

    定义: 用途:

  2. .1 视图二

    ……

存储过程设计

  1. .1 过程一

    定义: 用途: 输入: 输出:

.2 过程二

……

第二类数据

主要应用范围: 作用: 数据库定义文件: ER图文件:

表结构设计

……

视图设计

……

存储过程设计

……

数据库角色定义

第一类角色

角色职能: 角色权限:

第二类角色

……

数据库安全设计

针对数据库安全方面的要求进行的设计,包括保密性、安全性等方面。

数据库备份设计

针对数据的备份要求,描述数据库的备份策略、备份和恢复的手段、备份计划、操作步骤等方面的内容。

内容审核要点:

  • 本文档内容与软件设计文档、软件需求文档是否一致性;
  • 所述内容是否完备;
  • 本文档内容本身是否前后一致;
  • 表结构描述是否清楚明确; (若有)触发器描述是否清楚明确;
    (若有)视图描述是否清楚明确; (若有)存储过程描述是否清楚明确;
  • 数据库角色设置是否清楚合理。
  • 是否满足了安全、备份的要求。

参考

https://www.jianshu.com/p/a7984927cfb9

概述

接口的概念

在应用软件系统中,接口是程序和系统与外界交互的窗口。本文中所阐述的接口,包括:

应用软件系统提供软件功能供外部软件程序调用;
应用软件系统调用外部系统提供的软件功能;
应用软件系统依据应用级别的交互协议与外系统进行功能和数据的交换;
应用软件系统通过公用文件目录或数据库与外部系统交换信息。

此处中所阐述的接口不包括:

应用软件系统内部功能模块之间的接口调用;
应用软件系统提供的人机交互界面。

接口处理策略

xxxxxxxxxxx是一个庞大的由众多应用子系统构成的复杂分布式系统。各应用子系统之间存在众多的软件接口关系。
在xxxxxxxxxxx中,我们采用SOA的体系架构来解决各应用子系统之间、应用子系统与外部系统之间的接口问题。通过基于ESB服务总线技术的应用支撑平台来发布和管理应用软件系统对外提供公用服务。在这里,服务系指精确定义、封装完善、独立于其他服务所处环境和状态的软件功能。接口服务系指以服务的方式提供的接口实现。
通过支撑平台,各业务子系统接口之间的点对点关系,变成了各业务子系统接口与支撑平台的关系,从而简化了系统间的逻辑关系,应用支撑平台是各业务子系统相互连接的中介和纽带。各业务子系统之间在进行服务请求和服务调用时所需的一些附加工作,如通信协议的转换、路由、消息格式转换、安全性保证等,也可以由应用支撑平台来提供支持。此外,应用支撑平台提供适配器方式以支持文件同步、数据库同步、JMS、FTP等,应用支持平台还提供定制适配器功能接入特殊的外部系统。
通过应用支持平台发布公用服务,将有利于接口服务的复用,以及接口服务的维护和管理。

接口文档规范的必要性

xxxxxxxxxxx建设过程中,各应用软件系统的接口定义、实现、发布和管理是处理好接口问题的四个关键环节,他们贯穿在软件生命周期的全过程。为了保证在这些环节对应用软件系统之间的接口问题进行有效的处理,保证接口的良好定义、合理实现、受控发布和方便管理,有必要对相关技术文档提出规范化要求。
涉及接口定义的技术文档主要有业务需求说明书、软件需求规格说明书;涉及接口实现的技术文档主要有软件概要设计说明书、软件详细设计说明书、用户手册;涉及接口服务发布和管理的技术文档包括接口服务发布和管理规范等。

接口定义的文档规范要求

业务需求说明书中的接口描述

在xxxxxxxxxxx中,业务需求说明书由业主方任命的子项目组负责编制。业务需求说明书中有关接口描述的要求:

  • 宜有专门章节阐述子项目拟分包系统与其它子项目系统及外部系统之间的接口关系;
  • 在描述本系统与外部某系统接口关系时,宜:

具体阐明本系统的哪项或哪些业务功能与所述外部系统存在接口关系;
阐明接口的类型,如向外系统提供数据、从外系统获取数据、对外系统提供功能服务、向外系统请求某项功能服务;
对所述接口作必要的文字解释。

软件需求规格说明书中的接口描述

软件需求规格说明书系由承建方在业务需求说明书的基础上,在系统功能、性能、外部限制条件等方面的进一步明确和细化,作为系统设计、实现、测试的依据。是涉及接口定义的重要文档。
软件需求规格说明书中有关接口定义和描述的要求如下:

  • 软件需求规格说明书中有关与外部系统接口定义的范围应涵盖业务需求说明书中有关接口的描述,并补充业务需求书中遗漏的接口内容;
  • 本系统作为接口对外提供的软件功能,应在软件需求规格说明书中明确阐明该功能。包括接口功能编号、名称、性质(功能服务、数据交换、文件交换等)、接口功能的各输入参数及数据类型,返回的结果数据类型等;
  • 如果接口是作为某种信息交互协议(如Z39.50)的服务端提供的功能,要求阐明支持的协议版本以及是否完全支持该协议,如果不完全支持,要明确列举支持或不支持的功能;
  • 如果接口是某种信息交互协议(如Z39.50)的客户端,且外部系统提供的相应协议服务端功能是本系统相关功能实现的必要条件,须将对外部系统的相关接口实现要求列入软件需求规格说明书中的限制条件部分;
  • 如果系统功能涉及到要使用或访问外部系统提供的功能,须将其列入需求规格说明书中限制条件部分。

接口定义的变更

如果在软件需求评审通过后的实施过程中,原定义或描述的接口发生了变化,须通过变更流程修改软件需求规格说明书;或编制需求文档的补充材料,与原需求规格说明书一起,作为测试和验收的依据。

接口实现的文档规范要求

软件设计说明书中的接口服务实现描述

在软件概要设计说明书中,有关接口实现的要求如下:

  • 须覆盖软件需求说明书中所述对外提供接口功能;
  • 确定接口的实现策略。如采用Web
    Service、EJB、通过网络端口连接对外提供服务、文件目录或数据库同步等。
  • 对外提供的接口功能,需要进行形式化描述,如果不能确定在软件概要设计说明书完全进行接口功能的形式化表述,应在该说明书中明示并在详细设计说明书中给出以供编码实现之用。

用户文档中的接口使用描述

在设计说明书中描述的所有对外提供的接口功能,最终都要落实在用户文档中。具体的用户文档名称,取决于项目合同和软件开发计划中对最终交付文件的规定。如用户使用手册、用户参考手册、用户技术手册等。
各应用软件系统通过该系统的用户文档对外正式公布本系统对外的接口服务的功能和调用方式。

基本设置 springboot整合

  1. 引入依赖包
1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>

配置文件

  1. 创建shiro.ini
1
2
3
4
5
6
7
8
#格式: 用户名=密码,角色1,角色2
[users]
ck=123456,admin

#格式:角色=权限1,权限2
[roles]
admin=add
teacher=update
  1. 使用api测试验证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public Map<String,Object> userLoginAction (String userName,String passWord){

Map<String,Object> resultMap = new HashMap<>();
//初始化SecurityManager对象
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//通过SecurityManager工厂对象,获取SecurityManager实例对象.
SecurityManager securityManager = factory.getInstance();
// 把 securityManager 实例 绑定到 SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
//组建Subject主体.
Subject subject = SecurityUtils.getSubject();
//创建 token 令牌
UsernamePasswordToken token = new UsernamePasswordToken(userName,passWord);
//用户登录操作.
try{
subject.login(token);
resultMap.put("code","200");
resultMap.put("msg","用户登录成功");
}catch (AuthenticationException e){
//登录失败原因 1 用户不存在 2 用户密码不正确
resultMap.put("code","-1");
resultMap.put("msg","用户登录失败");
}
return resultMap;

}

自定义

  1. 自定义配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
import org.apache.shiro.mgt.SecurityManager;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

@Slf4j
@Configuration
public class CustomShiroConfiguration {

/**
* Filter Name Class
* anon org.apache.shiro.web.filter.authc.AnonymousFilter
* authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter
* authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
* perms org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
* port org.apache.shiro.web.filter.authz.PortFilter
* rest org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
* roles org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
* ssl org.apache.shiro.web.filter.authz.SslFilter
* user org.apache.shiro.web.filter.authc.UserFilter
* anon:所有 url 都都可以匿名访问
* authc: 需要认证才能进行访问
* user:配置记住我或认证通过可以访问
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
log.info("注入Shiro的Web过滤器-->shiroFilter", ShiroFilterFactoryBean.class);
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

//Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
//要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
//登录成功后要跳转的连接,逻辑也可以自定义,例如返回上次请求的页面
shiroFilterFactoryBean.setSuccessUrl("/index");
//用户访问未对其授权的资源时,所显示的连接
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
/*定义shiro过滤器,例如实现自定义的FormAuthenticationFilter,需要继承FormAuthenticationFilter **本例中暂不自定义实现,在下一节实现验证码的例子中体现 */

/*定义shiro过滤链 Map结构 * Map中key(xml中是指value值)的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 *
anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 * authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org
.apache.shiro.web.filter.authc.FormAuthenticationFilter */
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");

// <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/login", "anon");//anon 可以理解为不拦截
filterChainDefinitionMap.put("/reg", "anon");
filterChainDefinitionMap.put("/plugins/**", "anon");
filterChainDefinitionMap.put("/pages/**", "anon");
filterChainDefinitionMap.put("/api/**", "anon");
filterChainDefinitionMap.put("/dists/img/*", "anon");
filterChainDefinitionMap.put("/**", "authc");

shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

return shiroFilterFactoryBean;
}

// @Bean
// public EhCacheManager ehCacheManager() {
// EhCacheManager cacheManager = new EhCacheManager();
// return cacheManager;
// }

@Bean
public CustomShiroRealm customShiroRealm() {
return new CustomShiroRealm();
}

@Bean
public SecurityManager securityManager(CustomShiroRealm userRealm) {
log.info("注入Shiro的Web过滤器-->securityManager", ShiroFilterFactoryBean.class);
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
//注入缓存管理器;
// securityManager.setCacheManager(ehCacheManager());//这个如果执行多次,也是同样的一个对象;
return securityManager;
}

/**
* Shiro生命周期处理器 * @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}

/**
* 开启Shiro的注解
* (如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*
* @return
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}

@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}

/**
* 异常处理
*
* @return
*/
@Bean
public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
Properties mappings = new Properties();
mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理
mappings.setProperty("UnauthorizedException", "403");
r.setExceptionMappings(mappings); // None by default
r.setDefaultErrorView("error"); // No default
r.setExceptionAttribute("ex"); // Default is "exception"
//r.setWarnLogCategory("example.MvcLogger"); // No default
return r;
}

/**
* 自定义方式
*/
class CustomShiroRealm extends AuthorizingRealm {

/**
* 授权, 通过角色授权, 这里设置角色信息即可
* 参考: https://gitee.com/ityouknow/spring-boot-examples/blob/master/spring-boot-shiro/src/main/java/com/neo
* /config/MyShiroRealm.java
* https://blog.csdn.net/fu_fei_wen/article/details/77571989
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//在实际开发中,开发者根据自身情况自行获取用户信息。此处简化如下:
String role = "admin";
String permission = "add";

log.info("{}, role:{}, permission:{}", Thread.currentThread().getStackTrace()[1].getMethodName(), role,
permission);

//设置权限信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole(role);
simpleAuthorizationInfo.addStringPermission(permission);

return simpleAuthorizationInfo;
}


/**
* 主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
//在实际开发中,开发者根据自身情况自行获取用户信息。此处简化如下:
String name = "zhangsan";
String pwd = "11";

UsernamePasswordToken upt = (UsernamePasswordToken) token;
String username = upt.getUsername();
String password = new String(upt.getPassword());

if (name.equals(username) && pwd.equals(password)) {
return new SimpleAuthenticationInfo(username, password, "zhangsansan");
}
return null;
}
}
}

  1. 登录控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package com.lx.demo.springbootshiro.web.controller;

import com.lx.demo.springbootshiro.domain.User;
import com.lx.demo.springbootshiro.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import javax.validation.Valid;

@Slf4j
@Controller
public class SecurityController {

@Autowired
private UserService userService;

@GetMapping("/login")
public String loginForm() {
return "login";
}

@GetMapping("/index")
public String index(){
return "index";
}

@PostMapping("/login")
public String login(@Valid User user, BindingResult bindingResult, RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
return "login";
}
String loginName = user.getLoginName();
log.info("准备登陆用户 => {}", loginName);
UsernamePasswordToken token = new UsernamePasswordToken(loginName,user.getPassword());
//获取当前的Subject
Subject currentUser = SecurityUtils.getSubject();
try {
//在调用了login方法后,SecurityManager会收到AuthenticationToken,并将其发送给已配置的Realm执行必须的认证检查
//每个Realm都能在必要时对提交的AuthenticationTokens作出反应
//所以这一步在调用login(token)方法时,它会走到MyRealm.doGetAuthenticationInfo()方法中,具体验证方式详见此方法
log.info("对用户[" + loginName + "]进行登录验证..验证开始");
currentUser.login(token);
log.info("对用户[" + loginName + "]进行登录验证..验证通过");
} catch (UnknownAccountException uae) {
log.info("对用户[" + loginName + "]进行登录验证..验证未通过,未知账户");
redirectAttributes.addFlashAttribute("message", "未知账户");
} catch (IncorrectCredentialsException ice) {
log.info("对用户[" + loginName + "]进行登录验证..验证未通过,错误的凭证");
redirectAttributes.addFlashAttribute("message", "密码不正确");
} catch (LockedAccountException lae) {
log.info("对用户[" + loginName + "]进行登录验证..验证未通过,账户已锁定");
redirectAttributes.addFlashAttribute("message", "账户已锁定");
} catch (ExcessiveAttemptsException eae) {
log.info("对用户[" + loginName + "]进行登录验证..验证未通过,错误次数过多");
redirectAttributes.addFlashAttribute("message", "用户名或密码错误次数过多");
} catch (AuthenticationException ae) {
//通过处理Shiro的运行时AuthenticationException就可以控制用户登录失败或密码错误时的情景
log.info("对用户[" + loginName + "]进行登录验证..验证未通过,堆栈轨迹如下");
ae.printStackTrace();
redirectAttributes.addFlashAttribute("message", "用户名或密码不正确");
}
//验证是否登录成功
if (currentUser.isAuthenticated()) {
log.info("用户[" + loginName + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)");
return "redirect:/index";
} else {
token.clear();
return "redirect:/login";
}
}

@GetMapping("/logout")
public String logout(RedirectAttributes redirectAttributes) {
//使用权限管理工具进行用户的退出,跳出登录,给出提示信息
SecurityUtils.getSubject().logout();
redirectAttributes.addFlashAttribute("message", "您已安全退出");
return "redirect:/login";
}


// @GetMapping("/reg")
// @ResponseBody
// public Result<String> reg(@Valid User user, BindingResult bindingResult) {
// if (bindingResult.hasErrors()) {
// return Result.error("用户信息填写不完整");
// }
// userService.save(user);
// return Result.ok();
// }
}
  1. 登录后一段时间内所有请求都可以通过

测试

  1. 通过浏览器访问,正常登录即可
  2. 如果通过curl访问需要保留session信息,下次请求带入,否则需要一直校验

集成redis-cache

集成缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
  /**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}

/**
* 配置shiro redisManager
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost("localhost");
redisManager.setPort(6379);
redisManager.setExpire(1800);// 配置缓存过期时间
redisManager.setTimeout(0);
// redisManager.setPassword(password);
return redisManager;
}

/**
* Session Manager
* 使用的是shiro-redis开源插件
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}

/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}

支持多客户端登录踢出

  1. 设置过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package com.lx.demo.springbootshiro.filter;

import com.lx.demo.springbootshiro.domain.User;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

/**
* 踢出最好在chrome无痕模式下测试同一个用户, 这样可以保证每次都有新sessionid
* 默认只允许一个用户登录在一个地方登录, 这样第二个登录后,第一个刷新页面发现被踢出
*/
public class KickoutSessionControlFilter extends AccessControlFilter {

private String kickoutUrl; //踢出后到的地址
private boolean kickoutAfter = false; //踢出之前登录的/之后登录的用户 默认踢出之前登录的用户
private int maxSession = 1; //同一个帐号最大会话数 默认1

private SessionManager sessionManager;
private Cache<String, Deque<Serializable>> cache;

public void setKickoutUrl(String kickoutUrl) {
this.kickoutUrl = kickoutUrl;
}

public void setKickoutAfter(boolean kickoutAfter) {
this.kickoutAfter = kickoutAfter;
}

public void setMaxSession(int maxSession) {
this.maxSession = maxSession;
}

public void setSessionManager(SessionManager sessionManager) {
this.sessionManager = sessionManager;
}

//设置Cache的key的前缀
public void setCacheManager(CacheManager cacheManager) {
this.cache = cacheManager.getCache("shiro_redis_cache");
}

@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return false;
}

@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
Subject subject = getSubject(request, response);
if (!subject.isAuthenticated() && !subject.isRemembered()) {
//如果没有登录,直接进行之后的流程
return true;
}


Session session = subject.getSession();
String username = subject.getPrincipal().toString();
Serializable sessionId = session.getId();

//读取缓存 没有就存入
Deque<Serializable> deque = cache.get(username);

//如果此用户没有session队列,也就是还没有登录过,缓存中没有
//就new一个空队列,不然deque对象为空,会报空指针
if (deque == null) {
deque = new LinkedList<Serializable>();
}

//如果队列里没有此sessionId,且用户没有被踢出;放入队列
if (!deque.contains(sessionId) && session.getAttribute("kickout") == null) {
//将sessionId存入队列
deque.push(sessionId);
//将用户的sessionId队列缓存
cache.put(username, deque);
}

//如果队列里的sessionId数超出最大会话数,开始踢人
while (deque.size() > maxSession) {
Serializable kickoutSessionId = null;
if (kickoutAfter) { //如果踢出后者
kickoutSessionId = deque.removeFirst();
//踢出后再更新下缓存队列
cache.put(username, deque);
} else { //否则踢出前者
kickoutSessionId = deque.removeLast();
//踢出后再更新下缓存队列
cache.put(username, deque);
}


try {
//获取被踢出的sessionId的session对象
Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));
if (kickoutSession != null) {
//设置会话的kickout属性表示踢出了
kickoutSession.setAttribute("kickout", true);
}
} catch (Exception e) {//ignore exception
}
}

//如果被踢出了,直接退出,重定向到踢出后的地址
if (session.getAttribute("kickout") != null) {
//会话被踢出了
try {
//退出登录
subject.logout();
} catch (Exception e) { //ignore
}
saveRequest(request);

Map<String, String> resultMap = new HashMap<String, String>();
//判断是不是Ajax请求
if ("XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"))) {
resultMap.put("user_status", "300");
resultMap.put("message", "您已经在其他地方登录,请重新登录!");
//输出json串
out(response, resultMap);
} else {
//重定向
WebUtils.issueRedirect(request, response, kickoutUrl);
}
return false;
}
return true;
}

private void out(ServletResponse hresponse, Map<String, String> resultMap)
throws IOException {
try {
hresponse.setCharacterEncoding("UTF-8");
PrintWriter out = hresponse.getWriter();
// out.println(JSON.toJSONString(resultMap));
out.flush();
out.close();
} catch (Exception e) {
System.err.println("KickoutSessionFilter.class 输出JSON异常,可以忽略。");
}
}
}
  1. 配置过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  /**
* 限制同一账号登录同时登录人数控制
*
* @return
*/
@Bean
public KickoutSessionControlFilter kickoutSessionControlFilter() {
KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();
kickoutSessionControlFilter.setCacheManager(cacheManager());
kickoutSessionControlFilter.setSessionManager(sessionManager());
kickoutSessionControlFilter.setKickoutAfter(false);
kickoutSessionControlFilter.setMaxSession(1);
kickoutSessionControlFilter.setKickoutUrl("/logout");
return kickoutSessionControlFilter;
}

  1. springboot集成问题处理及注册
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  /**
* org.apache.shiro.UnavailableSecurityManagerException: No SecurityManager accessible to the calling code,
* @return
*/
@Bean
public FilterRegistrationBean delegatingFilterProxy(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
DelegatingFilterProxy proxy = new DelegatingFilterProxy();
proxy.setTargetFilterLifecycle(true);
proxy.setTargetBeanName("shiroFilter");
filterRegistrationBean.setFilter(proxy);
return filterRegistrationBean;
}

测试

通过无痕模式访问,每次变更sessionid,就被干出去了

权限控制

注解方式

注: 处理顺序与标题顺序一致

RequiresRoles:

当前Subject必须拥有所有指定的角色时,才能访问被该注解标注的方法。如果当天Subject不同时拥有所有指定角色,则方法不会执行还会抛出AuthorizationException异常。

1
2
3
4
5
6
7
8
//属于user角色
@RequiresRoles("user")

//必须同时属于user和admin角色
@RequiresRoles({"user","admin"})

//属于user或者admin之一;修改logical为OR 即可
@RequiresRoles(value={"user","admin"},logical=Logical.OR)

RequiresPermissions:

当前Subject需要拥有某些特定的权限时,才能执行被该注解标注的方法。如果当前Subject不具有这样的权限,则方法不会被执行。

1
2
3
4
5
6
7
8
//符合index:hello权限要求
@RequiresPermissions("index:hello")

//必须同时复核index:hello和index:world权限要求
@RequiresPermissions({"index:hello","index:world"})

//符合index:hello或index:world权限要求即可
@RequiresPermissions(value={"index:hello","index:world"},logical=Logical.OR)

RequiresAuthentication:

使用该注解标注的类,实例,方法在访问或调用时,当前Subject必须在当前session中已经过认证。

RequiresUser

当前Subject必须是应用的用户,才能访问或调用被该注解标注的类,实例,方法。

RequiresGuest:

使用该注解标注的类,实例,方法在访问或调用时,当前Subject可以是”gust”身份,不需要经过认证或者在原先的session中存在记录。

动态权限控制

这里要特别注意/**的控制,必须放最后,否则后续的权限可能不起作用

  1. 增加权限控制类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping(value = "/filter")
public class FilterManageController {

//加载自定义的拦截工厂
@Autowired
private ShiroFilterFactoryBean myShiroFilterFactoryBean;

@GetMapping("/all")
public Map<String, String> findAll() {
return myShiroFilterFactoryBean.getFilterChainDefinitionMap();
}

@GetMapping(value = "/add")
public Map<String, String> addFilter() {
Map<String, String> filterMap = new HashMap<>();
filterMap.put("/user/login**", "anon");
filterMap.put("/admin/**", "anon");//把 admin 设置成不需要拦截
filterMap.put("/super/**", "authc,roles[super],perms[super:info]");
//管理员才可以操做
filterMap.put("/users/**", "authc,roles[user]");
filterMap.put("/**", "authc,kickout");
addAndUpdatePermission(filterMap);

return myShiroFilterFactoryBean.getFilterChainDefinitionMap();
}

/**
* 动态更新新的权限, 更新原权限,增加新权限
*
* @param filterMap
*/
private synchronized void addAndUpdatePermission(Map<String, String> filterMap) {

AbstractShiroFilter shiroFilter = null;
try {
shiroFilter = (AbstractShiroFilter) myShiroFilterFactoryBean.getObject();

// 获取过滤管理器
PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter
.getFilterChainResolver();
DefaultFilterChainManager filterManager =
(DefaultFilterChainManager) filterChainResolver.getFilterChainManager();

//清空拦截管理器中的存储
filterManager.getFilterChains().clear();
/*
清空拦截工厂中的存储,如果不清空这里,还会把之前的带进去
ps:如果仅仅是更新的话,可以根据这里的 map 遍历数据修改,重新整理好权限再一起添加
*/
// myShiroFilterFactoryBean.getFilterChainDefinitionMap().clear();

Map<String, String> chains = myShiroFilterFactoryBean.getFilterChainDefinitionMap();
chains.remove("/**");
//把修改后的 map 放进去
filterMap.entrySet().forEach(fm -> {
chains.putIfAbsent(fm.getKey(), fm.getValue());
});
// chains.putAll(filterMap);

//这个相当于是全量添加
for (Map.Entry<String, String> entry : chains.entrySet()) {
//要拦截的地址
String url = entry.getKey().trim().replace(" ", "");
//地址持有的权限
String chainDefinition = entry.getValue().trim().replace(" ", "");
//生成拦截
filterManager.createChain(url, chainDefinition);
}
} catch (Exception e) {
log.error("updatePermission error,filterMap=" + filterMap, e);
}
}
}

  1. 测试 修改前可直接删除用户, 修改权限后,删除用户的角色必须是user

参考

  1. https://blog.csdn.net/fu_fei_wen/article/details/77571989
  2. https://waylau.gitbooks.io/apache-shiro-1-2-x-reference/content/

代码路径

0%