2023年11月25日发(作者:)

1

吴老师教学讲义

吴老师教学讲义

2

Spring Security3.0

什么是Spring Security?

Spring Security,这是一种基于Spring AOPServlet过滤器的安全框架。它提供全面

[7]

的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权。在Spring

Framework基础上,Spring Security充分利用了依赖注入(DIDependency Injection)和

面向切面技术。

获取Spring Security 3.0

Copyright©2010吴青版权所有

吴老师教学讲义

3

解压:

dist:目录中存放发布包

docs:存放文档

入门

新建web工程

为工程添加Spring支持

Copyright©2010吴青版权所有

吴老师教学讲义

4

我这里使用的是MyEclipse6.6 版本,该版本中的Spring的版本是Spring 2.5 ,而

Spring Security3.0 使用的是Spring 3.0 ,这里加入Spring支持是为了让工具为我们自动添

Spring的配置文件。

Spring Security3.0.2 下载解压后的dist目录中有两个war包,这两个war包是示例程序

.war.rar

WEB-INF/lib 总的所有的jar包拷贝到我们的工程中,在拷贝之前先清除掉MyEclipse帮助我

们加入的jar包。

Copyright©2010吴青版权所有

吴老师教学讲义

5

将这些工具帮助我们加入的jar包全

.war

添加后的结果:

Copyright©2010吴青版权所有

吴老师教学讲义

6

几个包的含义:

Copyright©2010吴青版权所有

吴老师教学讲义

7

:包含了核心认证和权限控制类和接口, 运程支持

和基本供应API使用Spring Security所必须的。支持单独运行的应用, 远程客户端,方法

(服务层)安全和JDBC用户供应

:包含过滤器和对应的web安全架构代码。任何需

要依赖servlet API的。如果需要Spring Security Web认证服务和基于URL的权限控制都需

要它

开始配置

首先配置文件

文件中添加一个过滤器,这个过滤器不在security包中. ,它可以代理一个

application context中定义的Spring bean所实现的filter

DelegatingFilterProxy做的事情是代理Filter的方法,从application context里获得

bean(这些bean就是Spring Security 中的核心部分,过滤器。这些过滤器被定义在了

Spring容器中) 这让bean可以获得spring web application context的生命周期支持,使

Copyright©2010吴青版权所有

吴老师教学讲义

8

配置较为轻便。 bean必须实现接口,它必须和filter-name里定义的名称

是一样的。

DelegatingFilterProxy这个类在包中

Copyright©2010吴青版权所有

吴老师教学讲义

9

1. 中通过监听器启动spring容器

2. Spring的配置文件中引入Spring Securityxml 命名空间,这需要修改原来的

文件. 这些代码在下载的文档或者示例应用程序中都能够找到

Spring Security 配置

以下的代码都在 文件中配置

Copyright©2010吴青版权所有

吴老师教学讲义

10

这表示,我们要保护应用程序中的所有URL只有拥有 角色的用户才能访

ROLE_USER

问。

元素是 所有web相关的命名空间功能的上级元素。元素

定义了

pattern

,用来匹配进入的请求URL,上面的表示拦截根,以及根子目录下的所有的

路径。

access

属性定义了请求匹配了指定模式时的需求。使用默认的配置, 这个一般是一个逗

号分隔的角色队列,一个用户中的一个必须被允许访问请求。 前缀ROLE_表示的是一个用

户应该拥有的权限比对。

上面的配置中只有角色是 ROLE_USER的用户才能访问,那么ROLE_USER在哪里定义呢?所

以下面的工作就是定义ROLE_USER

如果用户名为user,密码为user的用户成功登录了,它的角色是ROLE_USER ,那么他可以访

问规定的资源。

Copyright©2010吴青版权所有

吴老师教学讲义

11

编写首页

目前为止,项目中只有一个 在这个页面上只需要写一句话

将项目部署到web服务器中,然后访问首页,我们发现首页并没有出现,而是跳

转到了 一个登录页面上。因为项目刚启动,第一次访问的时候,没有任何用户登录,而在配置

文件中我们拦截的是所有的请求,所以第一次请求被拦截了。

Copyright©2010吴青版权所有

吴老师教学讲义

12

输入刚才在配置文件中配置的 用户名:user,密码:user,提交后,发现能够正确的转到

首页

问题: 那个登录页面哪里来的?

当有请求道来的时候,Spring Security框架开始检查要访问的资源是否有权访问,如果当

前登录用户无权或者当前根本就没有用户登录,则Spring Securtiy 框架就自动产生一个 登录

页面。

当在登录页面进行了正确的登录后,Spring Security会自动进行登录验证,如果成功登录,

将用户信息放到session中,然后转到先前请求的页面上。我们可以在index中将session

key—value打印出来,看看Spring Security 将用户信息如何放到session中的:

Copyright©2010吴青版权所有

吴老师教学讲义

13

可以看到 :用户使用 key:

SPRING_SECURITY_LAST_USERNAME

进阶I

在入门部分有明显缺陷:

A) 一般登录页面都是我们自己编写的,并且登录页面是不需要验证直接可以访问的。

B) 用户名和密码直接写在配置文件中,而实际项目中我们是放在数据库中的。

Copyright©2010吴青版权所有

吴老师教学讲义

14

指定登录页面

在前面的例子中,我们在Spring Security框架生成的登录页面中直接输入用户名和密码

后,Spring Security框架替我们进行验证的,那么HTTP请求的时候,向web服务器提交了什

么样的数据呢?我们打开生成的登录页面的源代码:

由于验证过程是Spring Security框架自动完成的,所以在我们的登录页面中表单元素的name

都是固定的。

下面是我们自己编写的登录页面:

Copyright©2010吴青版权所有

吴老师教学讲义

15

写完登录页面之后,得让Spring Security 框架知道哪个页面是登录页面,当需要登录的时候,

Spring Security 框架会自动跳转到这个登录页面上。所以得在文件

中进行配置

启动浏览器访问,我们发现自动跳转到页面上了。

Copyright©2010吴青版权所有

吴老师教学讲义

16

如果输入不正确的用户名或者密码,页面中没有任何错误消息提示,怎么样才能将错误消息

显示出来呢?

国际化输出

Spring Security 框架将所有的错误信息都定义成了异常,并且提供了国际化的资源文件。这

个资源文件在 文件中

Copyright©2010吴青版权所有

吴老师教学讲义

17

在配置文件中指定使用的资源文件。这里定义的messageSource对象被spring Security框架

内部使用。

jsp文件中取出错误消息,Spring Security 框架将抛出的异常对象放到了session范围中,

key: SPRING_SECURITY_LAST_EXCEPTION,取出的是异常对象,所以还要调用

getMessage()方法取出真正的错误信息

Copyright©2010吴青版权所有

吴老师教学讲义

18

自定义错误消息

src中新建一个message_zh_ties文件

Copyright©2010吴青版权所有

吴老师教学讲义

19

中指定资源文件。

Copyright©2010吴青版权所有

吴老师教学讲义

20

对密码进行加密

上面的配置中,密码以明文的形式出现在了配置文件中,这是非常危险的。所以我们可以

将真实密码经过加密之后的结果放到配置文件中。这需要在配置文件中指明使用什么样的加密算

法。

MD5是一种不可逆的加密算法,验证的时候,将用户输入的密码经过MD5加密之后的结

果再与配置文件中配置的密文进行比较,如果相同则通过验证。

admin 经过MD5加密之后的结果是:21232f297a57a5a743894a0e4a801fc3

于是作如下配置:

将权限放入数据库保存

前面将用户名和密码,还有权限都定义在了配置文件中,而实际项目中需要将这些放到数

据数据库中,那么这些信息在数据库中的表结构是什么,能不能自己定义呢?spring Security

将表结构已经定义好了,我们可以参考 发行文档中的 附录 A

Copyright©2010吴青版权所有

吴老师教学讲义

21

/*用户表*/

create table users

(

username varchar_ignorecase(50) not null primary key, /*用户名*/

password varchar_ignorecase(50) not null, /*密码*/

enabled boolean not null /*是否禁用*/

);

/*权限表*/

create table authorities

(

username varchar_ignorecase(50) not null,

authority varchar_ignorecase(50) not null,

constraint fk_authorities_users foreign key(username) references

users(username)

);

create unique index ix_auth_username on authorities

(username,authority);

Copyright©2010吴青版权所有

吴老师教学讲义

22

插入数据:

INSERT INTO users(username,PASSWORD,enabled)

VALUES('admin','21232f297a57a5a743894a0e4a801fc3',1);

/*密码是字符串admin加密后的结果*/

INSERT INTO users(username,PASSWORD,enabled)

VALUES('user','ee11cbb19052e40b07aac0ca060c23ee',1);

/*密码是字符串user 加密后的结果 */

INSERT INTO authorities VALUES('admin','ROLE_ADMIN');

INSERT INTO authorities VALUES('user','ROLE_USER');

Copyright©2010吴青版权所有

吴老师教学讲义

23

·修改配置文件

A) 在配置文件中加入数据源,因为要访问数据库,所以还要加入MySQL的驱动

<bean id="datasource"

class="ManagerDataSource">

<property name="driverClassName"

value="">

property>

<property name="url"

value="jdbc:mysql://localhost:3306/springDB?useUnicode=true&characterEncodi

property>

<property name="username" value="root">property>

<property name="password" value="root">property>

bean>

ng=utf8">

B) 将原来的user-service注释起来,再提供一个userService,使用刚刚配置的dataSource

<security:authentication-manager>

<security:authentication-provider>

<security:password-encoder hash="md5" />

<security:jdbc-user-service data-source-ref="datasource"/>

security:authentication-provider>

password="21232f297a57a5a743894a0e4a801fc3"

security:authentication-manager>

Copyright©2010吴青版权所有

吴老师教学讲义

24

在做实验之前,首先看看权限相关的配置:

如果我们需要完成这样的功能:

A) 系统中除了可以直接访问以外,其它的页面都需要权限才能进入

B) 页面 ROLE_USER ROLE_ADMIN都可以访问;

C) 页面只有ROLE_ADMIN权限可以访问

页面内容如下:

页面内容:

Copyright©2010吴青版权所有

吴老师教学讲义

25

上面的配置能满足要求吗?下面做实验验证:

启动项目,直接访问,输入用户名 user,密码user

登录之后:

Copyright©2010吴青版权所有

吴老师教学讲义

26

然后访问页面

可以访问,这里无法满足我满上面的要求,为什么?

我们仔细观察上面的配置,并没有配置访问权限,所以我们在配置文件中添加配

:

Copyright©2010吴青版权所有

吴老师教学讲义

27

重新启动项目后,点击链接,发现访问被拒绝.这样满足了我们的要求

再以admin用户进行登录,登录后我们也发现:

Copyright©2010吴青版权所有

吴老师教学讲义

28

我们要求admin用户可以访问,为什么这里被拒绝了呢?看看配置文件:

我们发现 实际上匹配的是/** ,要求ROLE_USER才能访问,ROLE_ADMIN就自然

不能访问了

修改配置文件:

自定义访问被拒绝页面

Copyright©2010吴青版权所有

吴老师教学讲义

29

修改后满足了我们的要求。但是403 错误信息不太友好,如何自定义访问拒绝页面呢?

首先新建一个页面:

修改配置文件,指定被拒绝的页面:

Copyright©2010吴青版权所有

吴老师教学讲义

30

获取登录用户信息

第一种方式:使用java代码:

了解Spring Security框架的核心共享组件:

被称为"shared"的组件,是指它在框架中占有很重要的位置,框架离开它们就没法运行。

内置的一系列的过滤器中都用到了这些共享组件。这些java类表达了维持系统的构建代码块,

所以理解他们的位置是非常重要的,即使你不需要直接跟他们打交道。

SecurityContextHolder

最基础的对象就是

SecurityContextHolder

我们把当前应用程序的当前安全环境

SecurityContextHolder

使

ThreadLocal

存储这些信息,这意味着,安全环境在同一个线程执行的方法一直是有效

的。我们把安全主体和系统交互的信息都保存在

SecurityContextHolder

中了

Copyright©2010吴青版权所有

吴老师教学讲义

31

查看该类的源代码:我们发现在这个类中有一个静态属性:

SecurityContextHolderStrategy strategy,

SecurityContextHolder初始化的时候,

为它指定了实例对象。

还有一个静态方法:

SecurityContext:

Security上下文,是一个接口

Authentication

:

也是一个接口。我们把安全主体和系统交互的信息都保存在

SecurityContextHolder中了。 Spring Security使用一个Authentication对应来表现这

Authentication

Copyright©2010吴青版权所有

吴老师教学讲义

32

SecurityContextHolder获取上下文对象,然后通过上下文对象(SecurityContext)获取

即可

UserDetails

UserDetails

是一个Spring Security的核心接口。 它代表一个主体(包含于用户相

关的信息)。

Authentication接口中有一个方法

Object getPrincipal();

这个方法返回的是一个

UserDetails

UerDetails对象之后,就可以通过这个对象的getUserName()方法获取当前用户名。

下面的代码演示了在任何地方,如何获取当前的用户名:

第二种方式:使用Spring Security 框架的标签库.

标签库存放在

Copyright©2010吴青版权所有

吴老师教学讲义

33

jsp页面中使用标签库的时候,首先引入标签库:下面在中使用标签库输出登录用

有选择的显示具备权限的链接

上面进入admin..jsp” 这个链接不管是user登录还是admin登录,都显示出来了。实际

上只有admin有权限执行这个链接,所以最好是amdin登录的时候,链接显示,而uer登录

的时候,不显示链接 .这个可以使用 标签库可以做到

<body>

这是首页,欢迎<sec:authentication property="name" /> ! <br />

Copyright©2010吴青版权所有

吴老师教学讲义

34

<sec:authorize ifAllGranted="ROLE_ADMIN" >

<a href="">进入页面a>

sec:authorize>

body>

sec:authorize 标签的属性:

A) ifAllGranted:只有当前用户拥有所有指定的权限时,才能显示标签体的内容 (相当于“与”

的关系)

B) ifAnyGranted:当前用户拥有指定的权限中的一个的时候,就能显示标签内部内容(相当于

“或”的关系)

C) ifNotGranted:没有指定的权限的时候,显示标签体的内容 (相当于“非”的关系)

也可以写成下面的形式:

当前用户如果能访问/,则显示标签体的内容

<sec:authorize url="/" >

<a href="">进入页面a>

sec:authorize>

admin登录:

Copyright©2010吴青版权所有

吴老师教学讲义

35

user登录:

Copyright©2010吴青版权所有

吴老师教学讲义

36

进阶II

理解安全过滤器链

Spring Securityweb架构是完全基于标准的servlet过滤器的。 它没有在内部使用

servlet或任何其他基于servlet的框架(比如spring mvc), 所以它没有与任何特定的web

技术强行关联。 它只管处理HttpServletRequest HttpServletResponse,不关心请求时

来自浏览器,web服务客户端,还是一个AJAX应用。

Spring Security 一启动就会包含一批负责各种安全管理的过滤器,这些过滤器组成过滤

器链。

那么这些过滤器如何启动?在文件中我们看到了这样的配置:

这里配置了一个过滤器,名字是DelegatingFilterProx,这个类在

包中

Copyright©2010吴青版权所有

吴老师教学讲义

37

DelegatingFilterProxy做的事情是代理Filter的方法,从spring容器application context

里获得bean 这让bean可以获得spring web application context的生命周期支持,使配

置较为轻便。 bean必须实现接口,它必须和filter-name里定义的名称是

一样的。查看DelegatingFilterProxyjavadoc获得更多信息。

刚才说到Spring Security需要一些过滤器,这些过滤器如果在中配置的话,配

置起来就很繁琐了,这些过滤器还必须按照顺序来配置. 所以为了方便,就只在文件

中配置了一个代理过滤器,它就相当于是一个入口,web容器启动的时候,由它到spring容器

中启动spring Security需要的过滤器链。

既然如此,我们就需要在spirng容器中配置过滤器了。我们知道,代理类需要有个代理目

标,文件中的DelegatingFilterProxy 所代理的目标类是什么呢?这个类就是:

Copyright©2010吴青版权所有

吴老师教学讲义

38

ChainProxy ,

spring容器中声明这个

类,然后将要启动的过滤器链配置进去就完成了配置任务,下面是在

文件中的配置:

事实上,在前面的应用中,我们并没有看到像这样的配置,这是怎么回事?

其实我们是用下面的配置替代了上面的配置:

Copyright©2010吴青版权所有

吴老师教学讲义

39

如果我们我们不想用 自动配置的方式,我们可以自己编写过滤器,然后按照上面的方式来

进行配置。Spring Security框架提供的过滤器已经很完善了,一般没有必要自己来定义,除非

是特殊需求。

下面来看看这些过滤器:

HttpSessionContextIntegrationFilter

Copyright©2010吴青版权所有

吴老师教学讲义

40

ssionContextIntegrationFilter:

第一个起作用的过滤器,首先判断用户的session中是否已经存在一个SecurityContext

了,如果存在,就把SecurityContext拿出来,放到SecurityContextHolder中,拱

Spring Security其他部分使用。如果不存在,就创建一个SecurityContext出来,还是放

SecurityContextHolder中,拱其它部分使用。

在所有过滤器执行完毕后,清空SecurityContextHolde

LogoutFilterFilter】:

只处理注销请求,默认为/j_spring_security_logout . 在用户发送注销请求的时候,销

毁用户session,清空SecurityContextHolder,然后重定向到注销成功页面。

AuthenticationProcessingFilter

ticationProcessingFilter

处理 form 登陆的过滤器,与form 登陆有关的所有操作都是在此进行的。默认情况下只

处理/j_spring_security_check 请求,这个请求应该是用户使用form登陆后的提交地址

此过滤器执行的基本操作时,通过用户名和密码判断用户是否有效,如果登录成功就跳转到

成功页面(可能是登陆之前访问的受保护页面,也可能是默认的成功页面)如果登录失败,

就跳转到失败页面

Copyright©2010吴青版权所有

吴老师教学讲义

41

DefaultLoginPageGeneratingFilter

tLoginPageGeneratingFil

ter

此过滤器用来生成一个默认的登录页面,默认的访问地址为/spring_security_login,这个

默认的登录页面虽然支持用户输入用户名,密码,也支持rememberMe 功能,但是因为

太难看了,只能是在演示时做个样子,不可能直接用在实际项目中

BasicProcessingFilter

rocessingFilter

此过滤器用于进行 basic 验证,功能与AuthenticationProcessingFilter 类似,只是验

证的方式不同

SecurityContextHolderAwareRequestFilter

tyContextHolderAwareReques

tFilter

此过滤器用来包装客户的请求。目的是在原始请求的基础上,为后续程序提供一些额外的

数据。比如getRemoteUser()时直接返回当前登陆的用户名之类的。

RememberMeProcessingFilter

Copyright©2010吴青版权所有

吴老师教学讲义

42

erMeProcessingFilte

r

此过滤器实现 RememberMe 功能,当用户cookie 中存在rememberMe 的标记,此

过滤器会根据标记自动实现用户登陆,并创建SecurityContext,授予对应的权限。

AnonymousProcessingFilter

ousProcessing

Filter

为了保证操作统一性,当用户没有登陆时,默认为用户分配匿名用户的权限。有关匿名登

录功能的详细信息

ExceptionTranslationFilte

ionTranslationFilter

此过滤器的作用是处理中 FilterSecurityInterceptor 抛出的异常,然后将请求重定向到

对应页面,或返回对应的响应错误代码。

SessionFixationProtectionFilter

nFixationProtectionFilter

Copyright©2010吴青版权所有

吴老师教学讲义

43

防御会话伪造攻击。

FilterSecurityInterceptor

SecurityInterceptor

AuthenticationCredentialsNotFoundException“尚未认证异常”。

功能二:如果用户已登录,但是没有访问当前资源的权限,则抛出

AccessDeniedException“拒绝访问异常”。

功能三:如果用户已登录,也具有访问当前资源的权限,则放行。

至此,我们完全展示了默认情况下Spring Security 中使用到的过滤器,以及每个

过滤器的应用场景和显示功能。

管理会话

应用中常常有这样的情形:一个用户正确的登录了系统,并没有使用系统提供的退出

功能,而是直接关掉浏览器,或者长时间不适用体统,Tomcat默认情况下会保存session 30

分钟,30分钟后,会话就会过期。

我们可以配置Spring Security 检测失效的session ID,并把用户转发到对应的URL上,

我们可以通过配置 session-management元素

Copyright©2010吴青版权所有

吴老师教学讲义

44

文件

DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>

<head>

<title>会话超时title>

head>

<body>

您的会话超时了!

body>

html>

Copyright©2010吴青版权所有

吴老师教学讲义

45

在页面中提供“退出”链接

Spring Security在启动的时候,启动了一个过滤器LogoutFilter它能够接收请求

/j_spring_security_logout ,此时可以将当前登录的用户“踢”出系统

在首页中添加一个连接

指定退出系统后,跳转的地方:

同步会话控制

Copyright©2010吴青版权所有

吴老师教学讲义

46

多个用户不能使用同一个账号同时登陆系统。为了实现这个功能,我们首先要在

文件中添加一个监听器,这个监听器会session 和销毁时候通Spring

Security

<listener>

<listener-class>

ssionEventPublisher

listener-class>

listener>

然后,在applicationContext中添加下面的配置, 这将防止一个用户重复登录好几次-

二次登录会让第一次登录失效。

通常我们更想防止第二次登录,这时候我们可以使用

Copyright©2010吴青版权所有

吴老师教学讲义

47

进阶III

保护业务代码的执行

Spring Security 使用AOP对方法调用进行了权限控制,这部分内容来源于Spring提供

AOP功能,Spring Security进行了自己的封装,我们可以使用声明和编程两种方式进行权

限管理

为应用添加一个业务接口和业务的实现类:

Copyright©2010吴青版权所有

吴老师教学讲义

48

使用annotation 保护业务方法的执行

第一步:首先在文件中启用annotation

第二步:在业务接口中使用注解对方法调用加以限制

Copyright©2010吴青版权所有

吴老师教学讲义

49

管理员两个方法都可以调用,而RSER只能调用sayHello方法

第三步:调用这些业务方法。在这个例子中,为了简单起见,我不使用任何第三方MVC

架,编写一个Servlet直接在Servlet中从Spring容器中取出业务对象,然后对它进行调

用。

Spring容器中部署业务类

添加ServletServlet的映射是/ .以下是ServletdoGet方法中的部分方法

Copyright©2010吴青版权所有

吴老师教学讲义

50

中添加链接

为了让adminuser都能够访问,applicationContext中需要放行:

user身份登录后执行 问好

user身份登录后执行 再见

Copyright©2010吴青版权所有

吴老师教学讲义

51

admin身份登录后发现两者都能执行

使用配置的方式,在配置文件中使用

protect-pointcut

添加安全切点。

使用annotation的方式很明显非常麻烦,需要在每一个业务方法中指定权限。如果

使用protect-pointcut添加切入点,定义一个切入点表达式,就会非常方便。关于切

入点表达式可以参考 spring参考手册》 6.2章节

切入点表达式语法格式:

executionmodifiers-pattern? ret-type-pattern declaring-type-pattern?

name-patternparam-patternthrows-pattern?

其中用?标注的是可选部分

Copyright©2010吴青版权所有

吴老师教学讲义

52

去掉代码中的annotation,然后在applicationContext中修改代码如下:

Copyright©2010吴青版权所有