Spring Security 为基于 J2EE 企业应用软件提供了全面安全服务。安全主要包括两个操作“认证”与“验证”(有时候也会叫做权限控制)。“认证”是为用户建立一个其声明的角色的过程,这个角色可以一个用户、一个设备或者一个系统。“验证”指的是一个用户在你的应用中能够执行某个操作。在到达授权判断之前,角色已经在身份认证过程中建立了。
搜索网络与整理相关的文档,主要有以下几个方法来使用 spring security 进行权限控制:
1)全部利用配置文件,将用户、权限、资源硬编码在 xml 文件中。
2)用户和权限用数据库存储,而资源(url)和权限的对应采用硬编码配置。
3)细分用户和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义过滤器,代替原有的FilterSecurityInterceptor过滤器,并分别实现AccessDecisionManager、InvocationSecurityMetadataSource 和 UserDetailsService,并在配置文件中进行相应的配置。
附:InvocationSecurityMetadataSource 将配置文件或数据库中存储的资源 url 提取出来加工成 url 和 权限列表的 Map 供 Security 使用,UserDetailsService 是提取用户名和权限组成一个完整的(UserDetails) User 对象,该对象可以提供用户的详细信息,供 AuthentationManager 进行认证与授权使用。
spring security 可以通过简单的配置,以声明式的方法加强应用的 url 访问安全。它向 http 请求应用 servlet 过滤器来安全问题。你可以使用 spring security schema 中定义的 xml 元素 在 spring 的bean 配置文件中配置这些过滤器。同时,由于servlet 过滤器 必须在 web 部署描述符中注册才能生效,所以你必须在 web.xml 中注册一个 DelegatingFilerProxy 示例,这个 servlet 过滤器会将请求委派给spring 应用上下文中一个过滤器。
DelegatingFilterProxy所做的事情是代理Filter的方法,从application context里获得bean(这些bean就是Spring Security 中的核心部分,过滤器。这些过滤器被定义在了Spring容器中)。 这让bean可以获得spring web application context的生命周期支持,使配置较为轻便。 bean必须实现javax.servlet.Filter接口,它必须和filter-name里定义的名称是一样的。
<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 的配置文件中引入 spring security schema 命名空间,这需要修改原来的application.xml 文件,如下:<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
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-3.1.xsd">
<!-- 相关配置 -->
</beans>在实际项目的时候,为了区分各种配置,常常会将安全配置单独放到一个文件里(如application-security.xml),同时会使用另外一种命名空间的形式(beans),如下:<?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-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<!-- 相关配置 -->
</beans:beans>接着往下的介绍,我们都使用 beans 命名空间.往下讲解,将从最简单的开始.首先spring security 允许通过 <http> 元素配置 web 应用安全性。假设你的 web 应用的 安全需求是典型的,可以将改元素的 autoconfig 属性设置为 true ,这样 spring security 将自动注册和配置一下几个基本的安全服务。
- 基于表单的登录服务:提供包含用户应用登录表单的默认页面。
- 注销服务:提供一个映射到用于用户退出应用的 url 的处理程序。
- http 基本验证:处理 http 请求头目标中存在的基本验证凭据,还能用于远程协议的 web 服务发出的验证请求。
- 匿名登录:为匿名用户指派一个角色并授予权限,可以将匿名用户作为常规用户处理。
- Remember-me 支持:在多个浏览器会话中记忆用户的身份,通常在用户浏览器中存储一个 Cookie 来实现。
- Servlet API 集成:允许通过标准 Servlet API 如 HttpServletRequest.isUserInRole() 和 HttpServletRequest.getUserPrincipal(),访问 web 应用的安全信息。
注册了这些安全服务,就可以指定需要特殊权限才能访问的URL 模式。spring security 将根据你的配置进行安全检查。用户在访问安全的 url 之前必须登录到应用,除非这些 url 开放给匿名访问。
配置如下:
<http auto-config='true'><intercept-url pattern="/**" access="ROLE_USER" /></http>这中设置表示我们会保护所有 url,只用拥有ROLE_USER 角色(权限)的用户才能访问。
<http> 元素所有 web 相关的命名空间的上级元素。在<intercept-url> 元素定义了pattern ,用来匹配进入请求的url,access 定义可访问此 pattern 下url 的角色,这个一般都是一个逗号分隔的角色队列。前缀“ROLE_”表示一个用户应该拥有的权限比对。
上面配置中表示只有 ROLE_USER 才能访问,那 ROLE_USER 应该在哪里定义呢??下面的工作就是定义 ROLE_USER:
<!-- 配置认证管理器 -->
<authentication-manager><authentication-provider><user-service><user name="user" password="123456" authorities="ROLE_USER" /></user-service></authentication-provider></authentication-manager>如果用户名为user,密码为123456的用户成功登录了,它的角色是ROLE_USER ,那么他可以访问指定的资源。到此已经算是基本完成了,为了测试,可以自己添加一个首页,在访问的时候,会重定向到一个登陆的页面(由 spring security 框架里过滤器DefaultLoginPageGeneratingFilter产生的)。
在上面的例子里,简单的演示了一下如何使用 spring security,不过其离实际使用还远着。在实际开发的时候,我们需要自定义自己的登录页面,同时用户名和密码不是配置在xml 文件里,而是写在在数据库里。
第一步:指定登录页面
由于配置是spring security 自动完成验证,所以我们需要遵循spring 在登录表单页面的 name,可以查看一个 spring security 的登录页面,如下:
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>登录页面</title></head><body><h3>用户登录</h3><form action="/springAuthority/j_spring_security_check" method="post">user:<input type="text" name="j_username"/><br />password:<input type="password" name="j_password" /><input type="submit" value="登录" /></form></body></html>
我们仿照来写一个,如下:
<%@ 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><h3>用户登录</h3><form action="${pageContext.request.contextPath}/j_spring_security_check" method="post">user:<input type="text" name="j_username"/><br />password:<input type="password" name="j_password" /><input type="submit" value="登录" /></form></body></html>
写完登录页面之后,需要让 spring security 知道哪一个是登录页面,所以得在配置文件里进行配置,如下:
<http auto-config="true"><form-login login-page="/login.jsp" /> <!-- 指定登录页面 --><intercept-url pattern="/**" access="ROLE_USER" /></http>指定登录页面之后,还需要设置不拦截登录页面(“/**”表示拦截所有页面),配置如下:
<!-- 不拦截登录页面,至于为什么加一个 *,是因为请求这个页面的时候可能会带有一些参数 -->
<http pattern="/login.jsp*" security="none" /><http auto-config="true"><form-login login-page="/login.jsp" /> <!-- 指定登录页面 --><intercept-url pattern="/**" access="ROLE_USER" /></http>启动浏览器访问 index.jsp,会发现自动跳转到 login.jsp 页面上了。随即会发现,当输错密码之后,没有错误的提示,怎么办??这时,可以借助spring security 的国际化输出,spring security 框架将所有的错误信息都定义成异常,并提供国际化的资源文件,这个资源文件在spring-security-core-xxx.jar文件中。这时我们需要配置文件中指定使用的资源文件,如下:<!-- 这里定义的messageSource对象供spring security 框架输出异常信息 -->
<bean id="messageSource"class="org.springframework.context.support.ReloadableResourceBundleMessageSource"><property name="basename" value="classpath:/org/springframework/security/messages_zh_CN" /></bean>
指定了资源文件之后,我们还需要在jsp文件里输出Spring Security 框架将抛出的异常对象放到了session范围中,key是:SPRING_SECURITY_LAST_EXCEPTION,取出的是异常对象,所以还要调用getMessage()方法取出真正的错误信息。如下:
<h3>用户登录</h3>${sessionScope.SPRING_SECURITY_LAST_EXCEPTION.message} <!-- 输出异常信息 -->
<form action="${pageContext.request.contextPath}/j_spring_security_check" method="post">user:<input type="text" name="j_username"/><br />password:<input type="password" name="j_password" /><input type="submit" value="登录" /></form>
对发生的错误,还可以自定义输出的提示信息,这里可以参考spring security 原有的 messages_zh_CN.properties 来定义输出的异常信息,范例如下,在 src 下定义个 创建一个 messages_zh_CN.properties 文件,指定输出的异常信息,如下:
#你无权访问该资源,请登录AbstractUserDetailsAuthenticationProvider.badCredentials=\u4F60\u65E0\u6743\u8BBF\u95EE\u8BE5\u8D44\u6E90\uFF0C\u8BF7\u767B\u5F55
第二步:将权限保存到数据中
前面时间用户名和密码定义在 xml 配置文件中,那又该如何将这些信息存进数据库里呢??同样地,从简单谈起,spring security 将表结构已经定义好了,可以参考发行文档的附录 A,也可以点击 这里 到官网查看。
<!-- 用户表 -->
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);
模型如下:
插入数据:
INSERT INTO users(username,PASSWORD,enabled) VALUES('admin','123456',1),('user','123456',1),('user2','123456',0);
INSERT INTO authorities VALUES('admin','ROLE_ADMIN'),('user','ROLE_USER'),('user2','ROLE_USER');
接着在 spring 的配置文件里配置数据源,且需要加入数据库驱动(这里使用 mysql),如下:
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver" /><property name="username" value="user" /><property name="password" value="root" /><property name="url"value="jdbc:mysql://localhost:3306/car?useUnicode=true&characterEncoding=utf8" /></bean>接着就是将替换掉原来的<user-service>,如下:
<!-- 配置认证管理器 -->
<authentication-manager><authentication-provider><jdbc-user-service data-source-ref="dataSource"/><!-- <user-service> <user name="user" password="123456" authorities="ROLE_USER" /> </user-service> -->
</authentication-provider></authentication-manager>现在再回头看一下<http>元素(用来配置 web 应用安全)
<!-- 不拦截登录页面,至于为什么加一个 *,是因为请求这个页面的时候可能会带有一些参数 -->
<http pattern="/login.jsp*" security="none" /><http auto-config="true"><form-login login-page="/login.jsp" /> <!-- 指定登录页面 --><intercept-url pattern="/**" access="ROLE_USER" /></http>假设要实现如下功能,
1)除了登录页面 login.jsp 可以直接访问之外,其他页面都需要权限才能进入。
2)index.jsp 页面 ROLE_USER 和 ROLE_ADMIN 都可以访问。
3)admin.jsp 页面只用 ROLE_ADMIN 才可以访问。
配置如下:
<!-- 不拦截登录页面,至于为什么加一个 *,是因为请求这个页面的时候可能会带有一些参数 -->
<http pattern="/login.jsp*" security="none" /><http auto-config="true"><form-login login-page="/login.jsp" /> <!-- 指定登录页面 --><intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/><intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" /></http>修改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>这是首页,欢迎你!<br><a href="admin.jsp">访问admin.jsp</a></body></html>新增admin.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>你好,管理员!!<br></body></html>修改基本满足了我们要求,可 403 信息不太友好,我们可以选择自定义 403 页面,首先还是先新建一个403页面403.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>403页面</title></head><body>你的访问被拒绝,无权访问该资源<br></body></html>
接着就是在spring 的配置文件里指定403页面,如下:
<!-- 不拦截登录页面,至于为什么加一个 *,是因为请求这个页面的时候可能会带有一些参数 -->
<http pattern="/login.jsp*" security="none" /><http auto-config="true" access-denied-page="/403.jsp"><form-login login-page="/login.jsp" /> <!-- 指定登录页面 --><intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/><intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" /></http>
获取用户信息,一般来说有两种方法,一种是通过java 代码获取,一种是通过spring security 标签获取。
第一种通过 java 代码获取。
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();if (principal instanceof UserDetails) {String username = ((UserDetails)principal).getUsername();} else {
String username = principal.toString();}
在spring security里,指定默认的登录页面时,还可以设置一个默认的提交目标。意思就是如果在进行表单登陆之前, 没有试图去访问一个被保护的资源, default-target-url 就会起作 用 。 这 是 用 户 登 陆 后 会 跳 转 到 的 URL , 默 认 是 "/" 。 你 也 可 以 把always-use-default-target 属性配置成"true",这样用户就会一直跳转到这一页(无论登陆是“跳转过来的”还是用户特定进行登陆) 。 如果你的系统一直需要用户从首页进入, 就可以使用它了。配置如下:
<http auto-config="true" access-denied-page="/403.jsp"><!-- 当系统一直需要用户从首页进入时,可以设置always-use-default-target -->
<form-login login-page="/login.jsp" default-target-url="/index.jsp" always-use-default-target="true"/><intercept-url pattern="/admin.jsp" access="ROLE_ADMIN"/><intercept-url pattern="/**" access="ROLE_USER,ROLE_ADMIN" /></http>