Spring Security 为基于 J2EE 企业应用软件提供了全面安全服务。安全主要包括两个操作“认证”与“验证”(有时候也会叫做权限控制)。“认证”是为用户建立一个其声明的角色的过程,这个角色可以一个用户、一个设备或者一个系统。“验证”指的是一个用户在你的应用中能够执行某个操作。在到达授权判断之前,角色已经在身份认证过程中建立了。

1.常用的权限控制方法

搜索网络与整理相关的文档,主要有以下几个方法来使用 spring security 进行权限控制:

1)全部利用配置文件,将用户、权限、资源硬编码在 xml 文件中。

2)用户和权限用数据库存储,而资源(url)和权限的对应采用硬编码配置。

3)细分用户和权限,并将用户、角色、权限和资源均采用数据库存储,并且自定义过滤器,代替原有的FilterSecurityInterceptor过滤器,并分别实现AccessDecisionManager、InvocationSecurityMetadataSource 和 UserDetailsService,并在配置文件中进行相应的配置。

附:InvocationSecurityMetadataSource 将配置文件或数据库中存储的资源 url 提取出来加工成 url 和 权限列表的 Map 供 Security 使用,UserDetailsService 是提取用户名和权限组成一个完整的(UserDetails) User 对象,该对象可以提供用户的详细信息,供 AuthentationManager 进行认证与授权使用。

2.配置

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产生的)。

     

3、进阶1

在上面的例子里,简单的演示了一下如何使用 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 页面上了。
image
随即会发现,当输错密码之后,没有错误的提示,怎么办??这时,可以借助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>

 image

对发生的错误,还可以自定义输出的提示信息,这里可以参考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);

 

 

模型如下:

image

插入数据:

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&amp;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>
      配置完成之后,看下各种效果,如下:
image image image
修改基本满足了我们要求,可 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>

4.获取用户信息

获取用户信息,一般来说有两种方法,一种是通过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>