Spring Security教程系列(一)基础篇-1
第 1 章 一个简单的HelloWorld
Spring Security中可以使用Acegi-1.x时代的普通配置方式,也可以使用从2.0时代才出现的命名空间配置方式,实际上这两者实现的功能是完全一致的,只是新的命名空间配置方式可以把原来需要几百行的配置压缩成短短的几十行。我们的教程中都会使用命名空间的方式进行配置,凡事务求最简。
<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的检测,这从第一时间把没有授权的请求排除在系统之外,保证系统资源的安全。关于过滤器配置的更多讲解可以参考http://www.family168.com/tutorial/jsp/html/jsp-ch-07.html#jsp-ch-07-03-01。
在applicationContext.xml中使用Spring Security提供的命名空间进行配置。
spring 2.0的旧的命名空间
<?xml version="1.0" encoding="UTF-8"?> <beans:beans xmlns="http://www.springframework.org/schema/security"1 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.4.xsd"> <http auto-config='true'>2 <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />3 <intercept-url pattern="/**" access="ROLE_USER" /> </http> <authentication-provider> <user-service> <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />4 <user name="user" password="user" authorities="ROLE_USER" /> </user-service> </authentication-provider> </beans:beans>
使用spring securety3.0以上的命名空间
<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-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <http auto-config="true">2 <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" />3 <intercept-url pattern="/**" access="ROLE_USER" /> <form-login/> </http> <authentication-provider> <user-service> <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" />4 <user name="user" password="user" authorities="ROLE_USER" /> </user-service> </authentication-provider> </beans:beans>
声明在xml中使用Spring Security提供的命名空间。 |
|
http部分配置如何拦截用户请求。auto-config='true'将自动配置几种常用的权限控制机制,包括form, anonymous, rememberMe。 |
|
我们利用intercept-url来判断用户需要具有何种权限才能访问对应的url资源,可以在pattern中指定一个特定的url资源,也可以使用通配符指定一组类似的url资源。例子中定义的两个intercepter-url,第一个用来控制对/admin.jsp的访问,第二个使用了通配符/**,说明它将控制对系统中所有url资源的访问。 在实际使用中,Spring Security采用的是一种就近原则,就是说当用户访问的url资源满足多个intercepter-url时,系统将使用第一个符合条件的intercept-url进行权限控制。在我们这个例子中就是,当用户访问/admin.jsp时,虽然两个intercept-url都满足要求,但因为第一个intercept-url排在上面,所以Spring Security会使用第一个intercept-url中的配置处理对/admin.jsp的请求,也就是说,只有那些拥有了ROLE_ADMIN权限的用户才能访问/admin.jsp。 access指定的权限部分比较有趣,大家可以注意到这些权限标示符都是以ROLE_开头的,实际上这与Spring Security中的Voter机制有着千丝万缕的联系,只有包含了特定前缀的字符串才会被Spring Security处理。目前来说我们只需要记住这一点就可以了,在教程以后的部分中我们会详细讲解Voter的内容。 |
|
user-service中定义了两个用户,admin和user。为了简便起见,我们使用明文定义了两个用户对应的密码,这只是为了当前演示的方便,之后的例子中我们会使用Spring Security提供的加密方式,避免用户密码被他人窃取。 最最重要的部分是authorities,这里定义了这个用户登陆之后将会拥有的权限,它与上面intercept-url中定义的权限内容一一对应。每个用户可以同时拥有多个权限,例子中的admin用户就拥有ROLE_ADMIN和ROLE_USER两种权限,这使得admin用户在登陆之后可以访问ROLE_ADMIN和ROLE_USER允许访问的所有资源。 与之对应的是,user用户就只拥有ROLE_USER权限,所以他只能访问ROLE_USER允许访问的资源,而不能访问ROLE_ADMIN允许访问的资源 |
因为Spring Security是建立在Spring的基础之上的,所以web.xml中除了需要配置我们刚刚提到的过滤器,还要加上加载Spring的相关配置。最终得到的web.xml看起来像是这样:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>spring_security_1</display-name> <context-param> <param-name>contextConfigLocation</param-name> <!-- 应用上下文配置文件 --> <!-- 两种位置写法 --> <!-- springMvc和spring security 两个配置文件 --> <!-- <param-value>/WEB-INF/spring-servlet.xml</param-value> --> <param-value>classpath:spring-servlet2.xml,classpath:applicationContext-security.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- spring securit start --> <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 securit start --> <!-- 配置spring核心servlet --> <servlet> <servlet-name>spring</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- url-pattern配置为/ 拦截 --> <servlet-mapping> <servlet-name>spring</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
演示不同权限的用户登陆之后可以访问不同的资源,我们为项目添加了两个jsp文件,admin.jsp和index.jsp。其中admin.jsp只有那些拥有ROLE_ADMIN权限的用户才能访问,而index.jsp只允许那些拥有ROLE_USER权限的用户才能访问。
最终我们的整个项目会变成下面这样:
等到项目启动完成后。打开浏览器访问http://localhost:8080/spring_security_1/就可以看到登陆页面。
这个简陋的页面是Spring Security自动生成的,一来为了演示的方便,二来避免用户自己编写登陆页面时犯错,Spring Security为了避免可能出现的风险,连测试用的登录页面都自动生成出来了。在这里我们就省去编写登陆页面的步骤,直接使用默认生成的登录页面进行演示吧。
如果输入的是正确的用户名和密码,比如user/user,系统在登陆成功后会默认跳转到index.jsp。
index。jsp的页面
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>登录首页</title> </head> <body> <span color="red">登录成功!</span> </body> </html>
直接打开http://localhost:8080/spring_security_1/admin.jsp
很遗憾,user用户是无法访问/admin.jsp这个url资源的,这在上面的配置文件中已经有过深入的讨论。我们在这里再简要重复一遍:user用户拥有ROLE_USER权限,但是/admin.jsp资源需要用户拥有ROLE_ADMIN权限才能访问,所以当user用户视图访问被保护的/admin.jsp时,Spring Security会在中途拦截这一请求,返回拒绝访问页面。
为了正常访问admin.jsp,使用admin/admin登陆系统,然后再次点击admin.jsp链接就会显示出admin.jsp中的内容。
根据我们之前的配置,admin用户拥有ROLE_ADMIN和ROLE_USER两个权限,因为他拥有ROLE_USER权限,所以可以访问/index.jsp,因为他拥有ROLE_ADMIN权限,所以他可以访问/admin.jsp。
至此,我们很高兴的宣布,咱们已经正式完成,并运行演示了一个最简单的由Spring Security保护的web系统,下一步我们会深入讨论Spring Security为我们提供的其他保护功能,多姿多彩的特性。
第 2 章 使用数据库管理用户权限
上一章节中,我们把用户信息和权限信息放到了xml文件中,这是为了演示如何使用最小的配置就可以使用Spring Security,而实际开发中,用户信息和权限信息通常是被保存在数据库中的,为此Spring Security提供了通过数据库获得用户权限信息的方式。
为了从数据库中获取用户权限信息,我们所需要的仅仅是修改配置文件中的authentication-provider部分。
将上一章配置文件中的user-service替换为jdbc-user-service,替换内容如下所示:
<authentication-provider> <jdbc-user-service data-source-ref="dataSource"/> <!-- <user-service> <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="user" password="user" authorities="ROLE_USER" /> </user-service> --> </authentication-provider>
现在只要再为jdbc-user-service提供一个dataSource就可以让Spring Security使用数据库中的权限信息了。在此我们使用spring创建一个演示用的dataSource实现,这个dataSource会连接到mysql数据库,从中获取用户权限信息。[1]
<beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <beans:property name="driverClassName" value="com.mysql.jdbc.Driver"/> <beans:property name="url" value="jdbc:mysql://localhost/spring_security?useUnicode=true&characterEncoding=utf-8"/> <beans:property name="username" value="cqyusp_dev"/> <beans:property name="password" value="cqyusp_dev"/> </beans:bean>
最终的配置文件如下所示:
<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-3.0.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd"> <http auto-config="true"> <intercept-url pattern="/admin.jsp" access="ROLE_ADMIN" /> <intercept-url pattern="/**" access="ROLE_USER" /> <form-login/> </http> <authentication-manager> <authentication-provider> <jdbc-user-service data-source-ref="dataSource"/> <!-- <user-service> <user name="admin" password="admin" authorities="ROLE_USER, ROLE_ADMIN" /> <user name="user" password="user" authorities="ROLE_USER" /> </user-service> --> </authentication-provider> </authentication-manager> <beans:bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <beans:property name="driverClassName" value="com.mysql.jdbc.Driver"/> <beans:property name="url" value="jdbc:mysql://localhost/spring_security?useUnicode=true&characterEncoding=utf-8"/> <beans:property name="username" value="cqyusp_dev"/> <beans:property name="password" value="cqyusp_dev"/> </beans:bean> </beans:beans>
Spring Security默认情况下需要两张表,用户表和权限表。以下是mysql中的建表语句
CREATE TABLE users( username VARCHAR(50) NOT NULL PRIMARY KEY, PASSWORD VARCHAR(50) NOT NULL, enabled BOOLEAN NOT NULL ); CREATE TABLE authorities ( username VARCHAR(50) NOT NULL, authority VARCHAR(50) NOT NULL, CONSTRAINT fk_authorities_users FOREIGN KEY(username) REFERENCES users(username) ); CREATE UNIQUE INDEX ix_auth_username ON authorities (username,authority); INSERT INTO users(username,PASSWORD,enabled) VALUES('admin','admin',TRUE); INSERT INTO users(username,PASSWORD,enabled) VALUES('user','user',TRUE); INSERT INTO authorities(username,authority) VALUES('admin','ROLE_ADMIN'); INSERT INTO authorities(username,authority) VALUES('admin','ROLE_USER'); INSERT INTO authorities(username,authority) VALUES('user','ROLE_USER');
users:用户表。包含username用户登录名,password登陆密码,enabled用户是否被禁用三个字段。 其中username用户登录名为主键。 |
|
authorities:权限表。包含username用户登录名,authorities对应权限两个字段。 其中username字段与users用户表的主键使用外键关联。 |
|
对authorities权限表的username和authority创建唯一索引,提高查询效率。 |
Spring Security会在初始化时,从这两张表中获得用户信息和对应权限,将这些信息保存到缓存中。其中users表中的登录名和密码用来控制用户的登录,而权限表中的信息用来控制用户登陆后是否有权限访问受保护的系统资源。
上述sql中,我们创建了两个用户admin和user,其中admin拥有ROLE_ADMIN和ROLE_USER权限,而user只拥有ROLE_USER权限。这和我们上一章中的配置相同,因此本章实例的效果也和上一章完全相同,这里就不再赘述了
使用user/user1登录,成功!
[1] javax.sql.DataSource是一个用来定义数据库连接池的统一接口。当我们想调用任何实现了javax.sql.DataSource接口的连接池,只需要调用接口提供的getConnection()就可以获得连接池中的jdbc连接。javax.sql.DataSource可以屏蔽连接池的不同实现,我们使用的连接池即可能由第三方包单独提供,也可能是由j2ee容器统一管理提供的。
第 3 章 自定义数据库表结构
Spring Security默认提供的表结构太过简单了,其实就算默认提供的表结构很复杂,也无法满足所有企业内部对用户信息和权限信息管理的要求。基本上每个企业内部都有一套自己的用户信息管理结构,同时也会有一套对应的权限信息体系,如何让Spring Security在这些已有的数据结构之上运行呢?
假设我们实际使用的表结构如下所示:
-- 角色 CREATE TABLE role( id BIGINT, NAME VARCHAR(50), descn VARCHAR(200) ); ALTER TABLE role ADD CONSTRAINT pk_role PRIMARY KEY(id); ALTER TABLE role MODIFY id BIGINT AUTO_INCREMENT; -- 用户 CREATE TABLE USER( id BIGINT, username VARCHAR(50), PASSWORD VARCHAR(50), STATUS INTEGER, descn VARCHAR(200) ); ALTER TABLE USER ADD CONSTRAINT pk_user PRIMARY KEY(id); ALTER TABLE USER MODIFY id BIGINT AUTO_INCREMENT; -- 用户角色连接表 CREATE TABLE user_role( user_id BIGINT, role_id BIGINT ); ALTER TABLE user_role ADD CONSTRAINT pk_user_role PRIMARY KEY(user_id, role_id); ALTER TABLE user_role ADD CONSTRAINT fk_user_role_user FOREIGN KEY(user_id) REFERENCES USER(id); ALTER TABLE user_role ADD CONSTRAINT fk_user_role_role FOREIGN KEY(role_id) REFERENCES role(id); INSERT INTO USER(id,username,PASSWORD,STATUS,descn) VALUES(1,'admin','admin',1,'管理员'); INSERT INTO USER(id,username,PASSWORD,STATUS,descn) VALUES(2,'user','user',1,'用户'); INSERT INTO role(id,NAME,descn) VALUES(1,'ROLE_ADMIN','管理员角色'); INSERT INTO role(id,NAME,descn) VALUES(2,'ROLE_USER','用户角色'); INSERT INTO user_role(user_id,role_id) VALUES(1,1); INSERT INTO user_role(user_id,role_id) VALUES(1,2); INSERT INTO user_role(user_id,role_id) VALUES(2,2);
上述共有三张表,其中user用户表,role角色表为保存用户权限数据的主表,user_role为关联表。user用户表,role角色表之间为多对多关系,就是说一个用户可以有多个角色。ER图如下所示:
现在我们要在这样的数据结构基础上使用Spring Security,Spring Security所需要的数据只是为了处理两种情况,一是判断登录用户是否合法,二是判断登陆的用户是否有权限访问受保护的系统资源。
我们所要做的工作就是在现有数据结构的基础上,为Spring Security提供这两种数据。
select username,password,status as enabled from user where username=?
用户登陆之后,系统需要获得该用户的所有权限,根据用户已被赋予的权限来判断哪些系统资源可以被用户访问,哪些资源不允许用户访问。
以下SQL就可以获得当前用户所拥有的权限
select u.username,r.name as authority from user u join user_role ur on u.id=ur.user_id join role r on r.id=ur.role_id where u.username=?
将这两条SQL语句配置到xml中,就可以让Spring Security从我们自定义的表结构中提取数据了。最终配置文件如下所示:
<authentication-provider> <jdbc-user-service data-source-ref="dataSource" 1users-by-username-query="select username,password,status as enabled from user where username=?" 2authorities-by-username-query="select u.username,r.name as authority from user u join user_role ur on u.id=ur.user_id join role r on r.id=ur.role_id where u.username=?"/> </authentication-provider>
users-by-username-query为根据用户名查找用户,系统通过传入的用户名查询当前用户的登录名,密码和是否被禁用这一状态。 |
|
authorities-by-username-query为根据用户名查找权限,系统通过传入的用户名查询当前用户已被授予的所有权限。 |