Web开发教程14-Spring Security   编程 / 阿男 / 浏览() / 评论() / 喜欢()
 

有关本系列教程的目录,源代码以及相关说明,请参考 附录


插图来源

在我们的报名系统中,有一个领域没有涉及,那就是认证授权。我们允许用户自由地报名,自由地添加课程,这显然不适用于实际的项目。我们需要调整一下添加待报名课程的功能,要求这一功能仅管理员可以在授权登录后进行使用。在这一章中,您将学习如何使用Spring Security模块来实现这一目的。

Spring Security介绍

Spring Security是Spring框架的一个组成部分,它的前身叫做Acegi Security。有关它的历史,在这里简单介绍一下。Acegi Security项目于2003年启动,那个时候,在Spring项目的邮件列表中,有人提出要求,问是否能在Spring工具包中添加一个处理认证授权的模块。由于那个时候Spring项目才刚刚开始,规模也很小,虽然大家都认为这个很重要,但无法花大精力去做。因此,Spring的开发小组开始默默地做出一个简单的认证授权模块,但没有把它开放给用户去试。就这样,随着用户对这方面的需要增多,在2004年3月,这个项目开始启动了。随着Acegi Security项目的不断发展,在2007年末,Acegi Security正式更名为Spring Security。

那么Sping Security到底为我们带来些什么呢?可以说,Spring Security既是一个认证模块,又是一个授权模块。认证和授权是安全领域两个非常重要的概念。认证即Authentication,它指对用户身份的确认;授权即Authorization,它是指当一个用户通过认证后,判断这个受信任的用户是否有权利去进行某种操作。举个例子来说,买票进入游乐园是认证过程,进入游乐园后,18岁以上玩家才能玩彩弹射击,这是个授权过程。你的票是认证凭据。而18岁以上这个条件则是一个授权标准。

随着安全领域不断标准化,认证与授权的两个概念也区分得越来越明确。在网上,您如果对安全领域比较关注,经常会看到A1、A2的说法。A1就是指认证,而A2则是指授权。常用的说法还有AuthN/AuthZ(认证/授权)及Au/Az(认证/授权)。

言归正传,Spring Security就是一个认证/授权模块,它同时对这两块提供宣告式管理,使认证授权过程从项目的逻辑代码中分离出来。在报名系统中,我们将使用Spring Security对整个系统进行保护。首先,只有tom这个用户可以添加待报名课程;其次,只有peter这个用户可以使用搜索报名信息的功能。这就是我们的业务需求,下面我们来把它分解成可以进行实际制作的设计方案 1

1 不知道您注意到没有,本系列文章讲设计方法与讲实际技术的比重是差不多的。如果没有好的设计,再好的技术也发挥不出它的优势。因此,在这里还需要再唐僧一番:请您时刻牢记OCP法则,MVC设计模式。

系统设计

我们要为报名系统添加安全层面,要达到的效果是:

  • 只有tom这个用户可以添加待报名课程
  • 只有peter这个用户可以使用搜索报名信息的功能

从设计的角度来讲,我们可以把这个需求分解如下:

  • addClazz.html及search.html需要用户认证登录才能使用
  • 其中,addClazz.html这个URL只授权给tom,密码为cat;而search.html只授权给peter,密码为dog

Spring Security的安装

安装Spring Security需要在Maven的pom.xml中添加dependency:

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-core</artifactId>
	<version>2.0.3</version>
</dependency>
<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-taglibs</artifactId>
	<version>2.0.3</version>
</dependency>

实际制作

为了达到上面的设计要求,我们需要做的就是在项目中添加一个配置文件。您一定会产生疑问,只需要一个配置文件?没错,就是这么简单。我们来看看这个配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
	http://www.springframework.org/schema/security 
	http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">


	<!-- Configure Spring Security -->
	<http auto-config="true">
		<intercept-url pattern="/addClazz.html*" access="ROLE_CLASS_MANAGER" />
		<intercept-url pattern="/search.html*" access="ROLE_SEARCHER" />
	</http>

	<authentication-provider>
		<password-encoder hash="md5" />
		<user-service>
			<user name="tom" password="d077f244def8a70e5ea758bd8352fcd8"
				authorities="ROLE_CLASS_MANAGER" />
			<user name="peter"
				password="06d80eb0c50b49a509b49f2424e8c805"
				authorities="ROLE_SEARCHER" />
		</user-service>
	</authentication-provider>

</beans:beans>

配置文件非常简单,也比较直白,但为了方便您理解,我们还是在下面进行一下逐行的说明:

  • 第2行,我们使用了Spring特殊的声明方式,来代替一般的声明。这样做有什么区别?其实很简单,就是把接下来在第7行及第8行定义的命名空间声明为默认的。声明成默认的是什么意思?举个例子来说吧,比如在前几章中讲解到的databaseContext.xml这个文件,里面有如下的声明:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
...
	xmlns:tx="http://www.springframework.org/schema/tx"
...
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

...
	<tx:annotation-driven transaction-manager="transactionManager" />	
...
</beans>

由于annotation-driven这个bean是定义在 http://www.springframework.org/schema/tx 中的,由于这个配置文件中有用到了很多命名空间中定义的bean 2 ,因此如果要正确引用到每个空间中的bean,必须明确地指定这个bean所在的命名空间。而命名空间需要自己来起个名字。在这个例子中,我们将 http://www.springframework.org/schema/tx 放在"tx"这个名字的空间里。而我们如果要引用 http://www.springframework.org/schema/tx 中的bean,如annotation-driven,就必须写成指定其空间。对于security.xml,由于我们在这个配置文件中仅用到了一个空间的bean,即 http://www.springframework.org/schema/security 中的bean,因此使用:

2 这就相当于一个代码中引用了很多package中的类

<beans:beans>

可以把它声明成本文件的唯一空间,我们就不用再指定前缀了,直接使用security中的bean即可。我们在这里探讨了一下内部原理,如果您不是很理解也没有关系,这并不影响您的使用。我们不一定非得f会做发动机才能学开车,因此请您继续往下看。

  • 第12行中使用了http bean,并且声明auto-config="true"。这样,Spring Security就会帮
    我们做完绝大部分的装配工作,如提供登录页面等,这对于我们初步使用足够了。
  • 在第13及第14行中,定义
    addClazz.html及search.html的鉴权策略。这里为什么要在addClazz.html及search.html后面加上"*"呢?我们在这里使用的匹配模式叫做Apache Ant模式。其实这种匹配模式很简单,比正则表达式更易于掌握,也很适合用来匹配各种URL地址,当然也没有正则表达式功能那么强大。这里简单做一下说明,您便可以掌握。首先,
/addClazz.html*

匹配根目录下 addClazz.html 为开头的各种URL,如 addClazz.html?xxx=yyy ,这些都匹配 addClazz.html** 。再举一个例子:

/aaa/*

这个匹配 /aaa/ 目录下的所有URL地址,如: /aaa/b.html , /aaa/a.html , /aaa/a.html?xxx=yyy 等。
再举一个例子:

/aaa/**

这个匹配 /aaa/ 目录下所有的URL地址,及/aaa所有下一级目录的地址,比如 /aaa/bbb/b.html ,就可以匹配上,而 /aaa/** 只能匹配 /aaa/ 本级URL地址。
再来一个例子看看:

/*/aaa/**/*.html*

这个匹配一级目录随意,二级目录必须为aaa,后续任意多级目录中 *.html 的URL地址。回来看看我们的定义,其中 addClazz.html 只有具备ROLE_CLASS_MANAGER角色的用户才能使用。而 search.html 则需要用户具备ROLE_SEARCHER的角色。

  • 在第17行,我们使用了authentication-provider bean。这个bean为鉴权策略提供认证源,说明白点,就是提供认证用户。Spring Security支持很多种认证源,如关系型数据库中的用户信息,LDAP中的用户信息,或是直接写在配置文件中的认证信息。认证方式也有很多种,最常用的当然就是登录名/密码认证。我们这个应用比较简单,就两个用户,因此使用配置文件声明的认证源,并且使用用户名/密码的认证方式足够了。
  • 第17行告诉系统用户的密码使用md5方式存储。
  • 在第20声明了一个用户tom,这个用户
    的密码为cat 3 。在第第21行声明tom这个用户的角色为ROLE_CLASS_MANAGER。接下来相同的方法声明了用户peter,密码为dog。

3 md5转码后的值

做好配置后,把它加载进web.xml:

<context-param>
	<param-name>contextConfigLocation</param-name>
	<param-value>
...
	/WEB-INF/security.xml
	</param-value>
</context-param>

...

<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>
		org.springframework.web.filter.DelegatingFilterProxy
	</filter-class>
</filter>

<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>
...

这里有一点需要注意的是,Spring Security是以Filter的机制来进行工作的。DelegatingFilterProxy负责接收所有的HTTP请求,请求进来后,security.xml中的配置生效,如果URL匹配策略中需要进行鉴权的点,那么配置中的内容就会开始生效。

接下来我们就来看看配置完的效果。

成果

我们现在试着访问 http://127.0.0.1:8080/reg/search.html ,这时系统自动发现我们没有登录,自动将我们转向登录页面,如下图所示:

现在您可能会觉得有些奇怪,这个登录界面是怎么出来的?还记得security.xml中的配置吗:

<http auto-config="true">

auto-config帮我们做了很多事情,这其中就包括提供登录界面。我们如果对登录界面有特殊需要,那么就可以去掉auto-config,自己定制各种细节,包括登录页面。在这一章先讲解最简单的方法,使用自动提供的登录页面。我们使用peter账号进行认证登录,如下图所示:

认证通过后,Spring会进行鉴权,判断peter是否有访问search.html的权限,根据配置中的定义,peter便可以使用search.html提供的搜索功能了。如果peter想使用不属于他的addClazz.html,结果会是怎么样?效果如下图所示:

答案是鉴权失败。让我们来分析一下这个结果:首先,由于我们已经用peter登录了,因此,认证过程通过,系统不再弹出登录界面。其次,由于peter不具有访问addClazz.html的权限,仅tom具备这一权力。因此,系统弹出鉴权失败的界面。

视图层鉴权

所谓视图层鉴权,就是在JSP页面中添加Spring Security的控制标签,对页面中的某一部分实行特定的鉴权要求定制。在页面中的鉴权可以说比基于URL的方式颗粒度更细一些。因为URL的匹配方式就相当于整页面的匹配,f而基于页面标签的匹配,可以为页面中某一部分进行鉴权配置。我们在这一节中主要介绍两个Spring Security提供的页面标签:

<authz:authentication>

以及

<authz:authorize>

其中,第一个是用于在页面中显示用户鉴权信息的。第二个则是控制鉴权。我们来讲解一下它们的使用方法。实际上非常简单,比如我们希望控制页面中的某一块,只有具备ROLE_ADMIN角色的用户才能看到,那么就这样:

<authz:authorize ifAllGranted="ROLE_ADMIN"> 
	<h1>Hello, World!</h1>
</authz:authorize> 

如果希望具备ROLE_ADMIN或ROLE_USER的用户能够看到,那么就:

<authz:authorize ifAnyGranted="ROLE_ADMIN,ROLE_USER">
	<h1>Hello, World!</h1>
</authz:authorize> 

如果希望角色为ROLE_USER的人不能看到某段页面,那么就:

<authz:authorize ifNotGranted="ROLE_USER">
	<h1>Hello, World!</h1>
</authz:authorize> 

这些标记中的配置项可以混用,实现更为复杂的鉴权策略,比如:

<authz:authorize ifAllGranted="ROLE_ADMIN" 
    ifAnyGranted="ROLE_USER" 
    ifNotGranted="ROLE_OTHER"> 
  <h1>Hello, World!</h1>
</authz:authorize>

下面我们来看看显示鉴权信息的标签用法:

Welcome <authz:authentication operation="username"/> 

我们通过这个标签,把登录用户的用户名显示在页面上。

小结

在这一章,您初步学习了如何使用Spring Security为您的项目添加认证及授权的能力。在这一章中,我们介绍了写在配置文件中的用户认证源,使用了自动生成的登录页面,可说是学习了非常初步的使用方法。在下一章中,我们将进一步学习Spring Security的使用方法。

标签: spring secutiry 安全 java 系列 教学 web 开发
 
本文短地址:http://bluedash.net/t/xqxp7 [复制]
 
 
—— 编程 / 阿男 / 2010-11-29
分享新浪围脖 / 开心 / 豆瓣 / 人人
 
 
 
 
 
 

随便看看

最新文章

^返回页首
^返回页首