2023年11月25日发(作者:)
1
吴老师教学讲义
抚之忽
尺,然
而抚
已尺
一
人
、
一
桌
吴老师教学讲义
2
Spring Security3.0
什么是Spring Security?
Spring Security,这是一种基于Spring AOP和Servlet过滤器的安全框架。它提供全面
[7]
的安全性解决方案,同时在Web请求级和方法调用级处理身份确认和授权。在Spring
Framework基础上,Spring Security充分利用了依赖注入(DI,Dependency 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 Security的xml 命名空间,这需要修改原来的
文件. 这些代码在下载的文档或者示例应用程序中都能够找到
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 Security的web架构是完全基于标准的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里定义的名称是
一样的。查看DelegatingFilterProxy的javadoc获得更多信息。
刚才说到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。
LogoutFilter【Filter】:
只处理注销请求,默认为/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容器中部署业务类 添加Servlet,Servlet的映射是/ .以下是Servlet中doGet方法中的部分方法 Copyright©2010吴青版权所有 吴老师教学讲义 50 在中添加链接 为了让admin和user都能够访问,在applicationContext中需要放行: 以user身份登录后执行 ”问好” 以user身份登录后执行 ”再见” Copyright©2010吴青版权所有 吴老师教学讲义 51 以admin身份登录后发现两者都能执行 使用配置的方式,在配置文件中使用 protect-pointcut 添加安全切点。 使用annotation的方式很明显非常麻烦,需要在每一个业务方法中指定权限。如果 使用protect-pointcut添加切入点,定义一个切入点表达式,就会非常方便。关于切 入点表达式可以参考 《spring参考手册》 6.2章节 切入点表达式语法格式: execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?) 其中用?标注的是可选部分 Copyright©2010吴青版权所有 吴老师教学讲义 52 去掉代码中的annotation,然后在applicationContext中修改代码如下: Copyright©2010吴青版权所有
发布评论