使用 Spring Security 保护 Web 应用的安全Word下载.docx
- 文档编号:17098594
- 上传时间:2022-11-28
- 格式:DOCX
- 页数:15
- 大小:31.04KB
使用 Spring Security 保护 Web 应用的安全Word下载.docx
《使用 Spring Security 保护 Web 应用的安全Word下载.docx》由会员分享,可在线阅读,更多相关《使用 Spring Security 保护 Web 应用的安全Word下载.docx(15页珍藏版)》请在冰豆网上搜索。
对这些资源所能执行的操作也不相同。
SpringSecurity能帮助开发人员以简单的方式满足这些安全性相关的需求。
第二个示例展示了如何与LDAP服务器进行集成。
第三个示例展示了如何与OAuth进行集成。
完整的示例代码见参考资料。
下面首先介绍基本的用户认证和授权的实现。
回页首
基本用户认证和授权
本节从最基本的用户认证和授权开始对SpringSecurity进行介绍。
一般来说,Web应用都需要保存自己系统中的用户信息。
这些信息一般保存在数据库中。
用户可以注册自己的账号,或是由系统管理员统一进行分配。
这些用户一般都有自己的角色,如普通用户和管理员之类的。
某些页面只有特定角色的用户可以访问,比如只有管理员才可以访问/admin这样的网址。
下面介绍如何使用SpringSecurity来满足这样基本的认证和授权的需求。
首先需要把SpringSecurity引入到Web应用中来,这是通过在web.xml添加一个新的过滤器来实现的,如代码清单1所示。
清单1.在web.xml中添加SpringSecurity的过滤器
<
filter>
filter-name>
springSecurityFilterChain<
/filter-name>
filter-class>
org.springframework.web.filter.DelegatingFilterProxy<
/filter-class>
/filter>
filter-mapping>
url-pattern>
/*<
/url-pattern>
/filter-mapping>
SpringSecurity使用的是Servlet规范中标准的过滤器机制。
对于特定的请求,SpringSecurity的过滤器会检查该请求是否通过认证,以及当前用户是否有足够的权限来访问此资源。
对于非法的请求,过滤器会跳转到指定页面让用户进行认证,或是返回出错信息。
需要注意的是,代码清单1中虽然只定义了一个过滤器,SpringSecurity实际上是使用多个过滤器形成的链条来工作的。
下一步是配置SpringSecurity来声明系统中的合法用户及其对应的权限。
用户相关的信息是通过接口来加载的。
该接口的唯一方法是loadUserByUsername(Stringusername),用来根据用户名加载相关的信息。
这个方法的返回值是接口,其中包含了用户的信息,包括用户名、密码、权限、是否启用、是否被锁定、是否过期等。
其中最重要的是用户权限,由接口来表示。
虽然SpringSecurity内部的设计和实现比较复杂,但是一般情况下,开发人员只需要使用它默认提供的实现就可以满足绝大多数情况下的需求,而且只需要简单的配置声明即可。
在第一个示例应用中,使用的是数据库的方式来存储用户的信息。
SpringSecurity提供了类来支持从数据库中加载用户信息。
开发人员只需要使用与该类兼容的数据库表结构,就可以不需要任何改动,而直接使用该类。
代码清单2中给出了相关的配置。
清单2.声明使用数据库来保存用户信息
beanid="
dataSource"
class="
org.springframework.jdbc.datasource.DriverManagerDataSource"
>
propertyname="
driverClassName"
value="
org.apache.derby.jdbc.ClientDriver"
/>
url"
jdbc:
derby:
//localhost:
1527/mycompany"
username"
app"
password"
admin"
/bean>
userDetailsService"
org.springframework.security.core.userdetails.jdbc.JdbcDaoImpl"
ref="
sec:
authentication-manager>
authentication-provideruser-service-ref="
/sec:
如代码清单2所示,首先定义了一个使用ApacheDerby数据库的数据源,SpringSecurity的类使用该数据源来加载用户信息。
最后需要配置认证管理器使用该UserDetailsService。
接着就可以配置用户对不同资源的访问权限了。
这里的资源指的是URL地址。
配置的内容如代码清单3所示。
sec是SpringSecurity的配置元素所在的名称空间的前缀。
清单3.配置对不同URL模式的访问权限
>
intercept-urlpattern="
/president_portal.do**"
access="
ROLE_PRESIDENT"
/manager_portal.do**"
ROLE_MANAGER"
/**"
ROLE_USER"
form-login/>
logout/>
第一个示例应用中一共定义了三种角色:
普通用户、经理和总裁,分别用ROLE_USER、ROLE_MANAGER和ROLE_PRESIDENT来表示。
代码清单3中定义了访问不同的URL模式的用户所需要的角色。
这是通过<
intercept-url>
元素来实现的,其属性pattern声明了请求URL的模式,而属性access则声明了访问此URL时所需要的权限。
需要按照URL模式从精确到模糊的顺序来进行声明。
因为SpringSecurity是按照声明的顺序逐个进行比对的,只要用户当前访问的URL符合某个URL模式声明的权限要求,该请求就会被允许。
如果把代码清单3中本来在最后的URL模式/**声明放在最前面,那么当普通用户访问/manager_portal.do的时候,该请求也会被允许。
这显然是不对的。
通过<
form-login>
元素声明了使用表单验证。
也就是说,当未认证的用户试图访问某个受限URL的时候,浏览器会跳转到一个登录页面,要求用户输入用户名和密码。
<
logout>
元素声明了提供用户注销登录的功能。
默认的注销登录的URL是/j_spring_security_logout,可以通过属性logout-url来修改。
当完成这些配置并运行应用之后,会发现SpringSecurity已经默认提供了一个登录页面的实现,可以直接使用。
开发人员也可以对登录页面进行定制。
的属性login-page、login-processing-url和authentication-failure-url就可以定制登录页面的URL、登录请求的处理URL和登录出现错误时的URL等。
从这里可以看出,一方面SpringSecurity对开发中经常会用到的功能提供了很好的默认实现,另外一方面也提供了非常灵活的定制能力,允许开发人员提供自己的实现。
在介绍如何用SpringSecurity实现基本的用户认证和授权之后,下面介绍其中的核心对象。
SecurityContext和Authentication对象
下面开始讨论几个SpringSecurity里面的核心对象。
接口表示的是当前应用的安全上下文。
通过此接口可以获取和设置当前的认证对象。
接口用来表示此认证对象。
通过认证对象的方法可以判断当前用户是否已经通过认证,以及获取当前认证用户的相关信息,包括用户名、密码和权限等。
要使用此认证对象,首先需要获取到SecurityContext对象。
通过类提供的静态方法getContext()就可以获取。
再通过SecurityContext对象的getAuthentication()就可以得到认证对象。
通过认证对象的getPrincipal()方法就可以获得当前的认证主体,通常是UserDetails接口的实现。
联系到上一节介绍的UserDetailsService,典型的认证过程就是当用户输入了用户名和密码之后,UserDetailsService通过用户名找到对应的UserDetails对象,接着比较密码是否匹配。
如果不匹配,则返回出错信息;
如果匹配的话,说明用户认证成功,就创建一个实现了Authentication接口的对象,如类的对象。
再通过SecurityContext的setAuthentication()方法来设置此认证对象。
代码清单4给出了使用SecurityContext和Authentication的一个示例,用来获取当前认证用户的用户名。
清单4.获取当前认证用户的用户名
publicstaticStringgetAuthenticatedUsername(){
Stringusername=null;
Objectprincipal=SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
if(principalinstanceofUserDetails){
username=((UserDetails)principal).getUsername();
}else{
username=principal.toString();
}
returnusername;
}
默认情况下,SecurityContextHolder使用ThreadLocal来保存SecurityContext对象。
因此,SecurityContext对象对于当前线程上所有方法都是可见的。
这种实现对于Web应用来说是合适的。
不过在有些情况下,如桌面应用,这种实现方式就不适用了。
SpringSecurity允许开发人员对此进行定制。
开发人员只需要实现接口并通过SecurityContextHolder的setStrategyName(String)方法让SpringSecurity使用此实现即可。
另外一种设置方式是使用系统属性。
除此之外,SpringSecurity默认提供了另外两种实现方式:
MODE_GLOBAL表示当前应用共享唯一的SecurityContextHolder;
MODE_INHERITABLETHREADLOCAL表示子线程继承父线程的SecurityContextHolder。
代码清单5给出了使用全局唯一的SecurityContextHolder的示例。
清单5.使用全局唯一的SecurityContextHolder
publicvoiduseGlobalSecurityContextHolder(){
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL);
在介绍完SpringSecurity中的SecurityContext和Authentication之后,下面介绍如何保护服务层的方法。
服务层方法保护
之前章节中介绍的是在URL这个粒度上的安全保护。
这种粒度的保护在很多情况下是不够的。
比如相同的URL对应的页面上,不同角色的用户所能看到的内容和执行的操作是有可能不同的。
在第一个示例应用中,系统中记录了每个员工的工资收入。
所有员工都可以查看自己的工资,但是只有员工的直接经理才可以修改员工的工资。
这就涉及到对应用中服务层的方法进行相应的权限控制,从而避免安全漏洞。
保护服务层方法涉及到对应用中的方法调用进行拦截。
通过Spring框架提供的良好面向方面编程(AOP)的支持,可以很容易的对方法调用进行拦截。
SpringSecurity利用了AOP的能力,允许以声明的方式来定义调用方式时所需的权限。
代码清单6中给出了对方法调用进行保护的配置文件示例。
清单6.对方法调用进行保护
userSalarySecurity"
org.springframework.security.access.intercept.aspectj.
AspectJMethodSecurityInterceptor"
authenticationManager"
accessDecisionManager"
securityMetadataSource"
value>
mycompany.service.UserService.raiseSalary=ROLE_MANAGER
/value>
/property>
如代码清单6所示,通过声明了类的raiseSalary方法只有具有角色ROLE_MANAGER的用户才能执行。
这就使得只具有角色ROLE_USER的用户无法调用此方法。
不过仅对方法名称进行权限控制并不能解决另外的一些问题。
比如在第一个示例应用中的增加工资的实现是通过发送POST请求到salary.do这个URL来完成的。
salary.do对应的控制器会调用类的raiseSalary方法来完成增加工资的操作。
存在的一种安全漏洞是具有ROLE_MANAGER角色的用户可以通过其它工具(如cURL或Firefox扩展Poster等)来创建POST请求来更改其它员工的工资。
为了解决这个问题,需要对raiseSalary的调用进行更加细粒度的控制。
通过SpringSecurity提供的AspectJ支持就可以编写相关的控制逻辑,如代码清单7所示。
清单7.使用AspectJ进行细粒度的控制
publicaspectSalaryManagementAspect{
privateAspectJMethodSecurityInterceptorsecurityInterceptor;
privateUserDaouserDao;
pointcutsalaryChange():
target(UserService)
&
&
execution(publicvoidraiseSalary(..))&
!
within(SalaryManagementAspect);
Objectaround():
salaryChange(){
if(this.securityInterceptor==null){
returnproceed();
AspectJCallbackcallback=newAspectJCallback(){
publicObjectproceedWithObject(){
};
Object[]args=thisJoinPoint.getArgs();
Stringemployee=(String)args[0];
//要修改的员工的用户名
Useruser=userDao.getByUsername(employee);
StringcurrentUser=UsernameHolder.getAuthenticatedUsername();
//当前登录用户
if(!
currentUser.equals(user.getManagerId())){
thrownewAccessDeniedException
("
Onlythedirectmanagercanchangethesalary."
);
returnthis.securityInterceptor.invoke(thisJoinPoint,callback);
如代码清单7所示,定义了一个切入点(pointcut)salaryChange和对应的环绕增强。
当方法raiseSalary被调用的时候,会比较要修改的员工的经理的用户名和当前登录用户的用户名是否一致。
当不一致的时候就会抛出AccessDeniedException异常。
在介绍了如何保护方法调用之后,下面介绍如何通过访问控制列表来保护领域对象。
访问控制列表
之前提到的安全保护和权限控制都是只针对URL或是方法调用,只对一类对象起作用。
而在有些情况下,不同领域对象实体所要求的权限控制是不同的。
以第一类示例应用来说,系统中有报表这一类实体。
由于报表的特殊性,只有具有角色ROLE_PRESIDENT的用户才可以创建报表。
对于每份报表,创建者可以设定其对于不同用户的权限。
比如有的报表只允许特定的几个用户可以查看。
对于这样的需求,就需要对每个领域对象的实例设置对应的访问控制权限。
SpringSecurity提供了对访问控制列表(AccessControlList,ACL)的支持,可以很方便的对不同的领域对象设置针对不同用户的权限。
SpringSecurity中的访问控制列表的实现中有3个重要的概念,对应于4张数据库表。
∙授权的主体:
一般是系统中的用户。
由ACL_SID表来表示。
∙领域对象:
表示系统中需要进行访问控制的实体。
由ACL_CLASS和ACL_OBJECT_IDENTITY表来表示,前者保存的是实体所对应的Java类的名称,而后者保存的是实体本身。
∙访问权限:
表示一个用户对一个领域对象所具有的权限。
由表ACL_ENTRY来表示。
SpringSecurity已经提供了参考的数据库表模式和相应的基于JDBC的实现。
在大多数情况下,使用参考实现就可以满足需求了。
类可以对访问控制列表进行查询、添加、更新和删除的操作,是开发人员最常直接使用的类。
该类的构造方法需要3个参数,分别是表示的数据源、表示的数据库的查询策略和表示的访问控制列表缓存。
数据源可以使用第一个示例应用中已有的数据源。
查询策略可以使用默认的实现。
缓存可以使用基于EhCache的缓存实现。
代码清单8中给出了相关代码。
清单8.使用JDBC的访问控制列表服务基本配置
aclService"
org.springframework.security.acls.jdbc.JdbcMutableAclService"
constructor-argref="
lookupStrategy"
aclCache"
classIdentityQuery"
valuesIDENTITY_VAL_LOCAL()"
/>
sidIdentityQuery"
如代码清单
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 使用 Spring Security 保护 Web 应用的安全 应用 安全