27.2. Spring Security

27.2.1. 概述

Spring Security 作为一款能够为基于Spring的企业应用提供一种强大和灵活的安全访问控制解决方案的框架,使开发人员不必编写大量的安全代码,即能为企业应用程序提供身份验证和授权等服务。Operamasks作为一个MVC框架,安全和认证一直是其未涉足领域.本文试图提供一种Operamasks和Spring Security整合的方案,以弥补这种不足。

27.2.2. 开始整合

通过上面的简介我们可以得知Spring Security 是一个基于Spring的强大的安全框架,而我们的Operamasks作为一个强大的MVC框架,如果能把这两者强强联合,不失为一件美事。现在我们就以最简单,最易上手的方式来一步步整合我们的Operamasks和Spring Security。以下我们将会建立一个小示例来介绍整合过程:

27.2.2.1. 下载Spring Security包

首先先下载Spring Security的包,本文的示例是在Spring Security 2.0.4 的环境下测试的,因为下载的是Spring Security 2.0.4的包,具体地址为:http://sourceforge.net/project/showfiles.php?group_id=73357&package_id=270072&release_id=630203,下完后会得到spring-security-2.0.4.zip,解压该包,进入该包下的dist目录,为了快速获得所需的lib包,我们只需找到dist目录下的spring-security-samples-tutorial-2.0.4.war,然后将其解压,到WEB-INF\lib目录下,把其目录下所有夹包先拷到另一个目录待备用。spring-security-samples-tutorial-2.0.4.war是Spring Security的官方示例,里面有运行环境所需的包,因为到其目录下拷贝。

27.2.2.2. 建立一个aom_springsecurity项目

好了,我们已经把包准备好了,接下来打开Operamasks Studio,新建一个Apusic工程并命名为aom_springsecurity,为其添加web模块,接着我们把3.1中拷贝出来的待备用的包全部拷到aom_springsecurity工程的lib目录下,然后在WEB-INF目录下建立spring 的配置文件applicationContext.xml,到此就准备开始配置了。

27.2.2.3. 配置web.xml

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

  <listener>
      <listener-class>
          org.springframework.web.context.ContextLoaderListener
      </listener-class>
  </listener>

  <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>
      <dispatcher>FORWARD</dispatcher>
      <dispatcher>REQUEST</dispatcher>
  </filter-mapping>

 <welcome-file-list>
   <welcome-file>enter.xhtml</welcome-file>
 </welcome-file-list>

把上述配置加到web.xml中。

配置说明:listener和context-param的配置是标准spring的配置,是为加载applicationContext.xml用的,在此就不多说

了。而下面的filter和filter-mapping则是Spring Security的配置,所有的用户在访问项目之前,都要先通过Spring Security的检测,这从第一时间把没有授权的请求排除在系统之外,保证系统资源的安全,welcome-file定义一个入口页面。

27.2.2.4. 建立页面

配置完web.xml 后,我们开始在WebContent根目录下建立几个测试页面,先建立enter.xhtml,代码如下:

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE HTML PUBLIC "" "">
 <f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
    xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
    xmlns:h="http://java.sun.com/jsf/html" xmlns:ajax="http://www.apusic.com/jsf/ajax"
    renderKitId="AJAX">
    <w:head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </w:head>
    <w:page title="Operamasks与Spring Security整合示例">
      <a href="admin.faces">受保护页面</a>
      <a href="index.faces">普通页面</a>
    </w:page>
 </f:view>

该页面是入口页面,页面上有两个链接,一个是受保护页面,一个是普通页面,点击受保护页面链接会进入权限保护,只有有权限的人才能进入,普通页面则是任何人都可以访问的。接下来建立admin.xhtml,代码如下:

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE HTML PUBLIC "" "">
 <f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
    xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
    xmlns:h="http://java.sun.com/jsf/html" xmlns:ajax="http://www.apusic.com/jsf/ajax"
    renderKitId="AJAX">
    <w:head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </w:head>
    <w:page title="Operamasks与Spring Security整合示例">
      恭喜你,你通过了验证进入了一个受保护的页面。
    </w:page>
 </f:view>

该页面是受保护页面,最后我们建立index.xhtml页面,代码如下:

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE HTML PUBLIC "" "">
 <f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
    xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
    xmlns:h="http://java.sun.com/jsf/html" xmlns:ajax="http://www.apusic.com/jsf/ajax"
    renderKitId="AJAX">
    <w:head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </w:head>
    <w:page title="Operamasks与Spring Security整合示例">
      这是一个任何人都可以访问的页面。
    </w:page>
 </f:view>

至此,我们所有页面都建完了,现在到了最关键的一步了:配置权限。

27.2.2.5. 配置权限

还记得我们前面步骤曾经建立了一个applicationContext.xml吧,spring的精髓在于通过配置就可获得很多功能,权限也不例外。我们的需求是,受保护页面admin.xhtml必须有管理员的权限才可查看,普通页面index.xhtml则是任何用户都可以查看,因此我们只需在applicationContext.xml文件里加入以下代码:

  <?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.4.xsd">

        <!-- 设置权限 -->
    <http auto-config='true'>
      <intercept-url pattern="/admin.faces" access="ROLE_ADMIN" />
    </http>
    
    <!-- 定义角色 -->
    <authentication-provider>
      <user-service>
        <user name="admin" password="admin" authorities="ROLE_ADMIN" />
      </user-service>
    </authentication-provider>
  </beans:beans>

怎么样?很精简吧,从上面代码可以看出定义了一个“admin”的具有ROLE_ADMIN权限的用户,然后只有ROLE_ADMIN权限的用户才能访问admin.faces页面。

好了,到此为止,一个Operamasks与Spring Security整合的最简示例完成了,接下来我们启动我们的Apusic应用服务器部署我们的aom_springsecurity工程,然后打开以下网址:

http://127.0.0.1:6888/aom_springsecurity/enter.faces 或 http://127.0.0.1:6888/aom_springsecurity 即可看到以下页面:

enter.xhtml

图 27.4. enter.xhtml


login.xhtml

图 27.5. login.xhtml


admin.xhtml

图 27.6. admin.xhtml


index.xhtml

图 27.7. index.xhtml


27.2.3. 自定义登录页面

经过上面的实践,是不是觉得很好入门呢?但有人可能还存在一些疑问,比如说登陆页面哪里来的?从来都没有建过这个页面啊?呵呵,的确我们从来没有建过这个页面,这个登录页面是Spring Security 自动生成的,在Spring Security 2.0后会自动生成,这是为了避免自定义登录页面所引起的其他问题才加入的。但又有人说了,这个默认登录页面太丑了,难道就不能自己定义一个吗?肯定可以,下面我们来介绍如何自定义登录页面:

27.2.3.1. 建立页面和ManagedBean

首先,我们肯定要有自己的登录页面,新建一个login.xhtml页面,在新建login.xhtml页面的同时也新建了一个LoginBean,代码如下:

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE HTML PUBLIC "" "">
 <f:view xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core"
    xmlns:w="http://www.apusic.com/jsf/widget" xmlns:layout="http://www.apusic.com/jsf/layout"
    xmlns:h="http://java.sun.com/jsf/html" xmlns:ajax="http://www.apusic.com/jsf/ajax"
    renderKitId="AJAX">
    <w:head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </w:head>
    <w:page title="Operamasks与Spring Security整合示例">
      1<w:form prependId="false">
        2<w:textField id="j_username" fieldLabel="用户名:" />
        <w:textField id="j_password" fieldLabel="密码:" inputType="password" />
        <w:button id="login" value="登录" />
      </w:form>
    </w:page>
 </f:view>
1

prependId="false" 是为了告诉OperaMasks,用户自己定义构件ID。

2

j_username ,j_password 这两个ID是供Spring Security 验证时用的。

 package demo;

 import java.io.IOException;
 import java.io.Serializable;

 import javax.faces.context.FacesContext;
 import javax.servlet.RequestDispatcher;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;

 import org.operamasks.faces.annotation.Action;
 import org.operamasks.faces.annotation.ManagedBean;
 import org.operamasks.faces.annotation.ManagedBeanScope;

 @ManagedBean(name = "loginBean", scope = ManagedBeanScope.REQUEST)
 public class LoginBean implements Serializable {
    @Action(id = "login")
    public void login() {
       ServletContext servletContext = (ServletContext) FacesContext.getCurrentInstance().getExternalContext().getContext();
       HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
       HttpServletResponse respone = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
       1RequestDispatcher rd = servletContext.getRequestDispatcher("/j_spring_security_check");
       try {
         2rd.forward(request, respone);
       } catch (ServletException e) {
        // should log exception
       } catch (IOException e) {
        // should log exception
       } finally {
         3FacesContext.getCurrentInstance().responseComplete();
       }
    }
 }
1

j_spring_security_check 是form提交的action,这个action会被Spring Security拦截。

2

根据提交的用户名和密码转向到 j_spring_security_check 这个action。

3

跳过OperaMasks的生命周期,直接到渲染阶段。

也许有人会问,怎么LoginBean里面没有任何的验证逻辑?是的,我们用这个LoginBean的目的是,收集登录页面提交的用户名和密码,然后把它转向Spring Security 的验证框架去验证,这样我们只需编写页面即可,接下来就是配置Spring Security 用我们的登录页面,而不用默认的登录页面。

27.2.3.2. 配置我们的登录页面

前面工作做好后,我们就修改applicationContext.xml文件,以便验证时进入我们的登录页面,修改后代码如下:

    <?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.4.xsd">

        <!-- 设置权限 -->
    <http auto-config='true'>
      <intercept-url pattern="/admin.faces" access="ROLE_ADMIN" />

            <!-- 自定义登陆页面 -->
            1<form-login login-page="/login.faces" authentication-failure-url="/login.faces" />
    </http>
    
    <!-- 定义角色 -->
    <authentication-provider>
      <user-service>
        <user name="admin" password="admin" authorities="ROLE_ADMIN" />
      </user-service>
    </authentication-provider>
  </beans:beans>
1

login-page属性定义了我们的登录页面,authentication-failure-url属性定义了我们验证错误后跳回登录页面。

到此,我们配置完了,是不是很简单呢?然后我们再次打开http://127.0.0.1:6888/aom_springsecurity/enter.faces 或http://127.0.0.1:6888/aom_springsecurity,点击受保护页面链接后即可看到:

自定义页面

图 27.8. 自定义页面


呵呵,是不是看到我们自己定义的登录页面了。

27.2.4. 数据库权限

还记得前面我们把权限都定义在applicationContext.xml中吧,但现实应用中,我们的权限信息一般放在数据库里,下面我们将提供一种简单模型,把权限信息存在数据库里。

27.2.4.1. 建立数据源

首先,我们先建立一个数据库springsecurity(本示例基于mysql 5.0),在这个数据库里建两张表并插入测试数据,一张是authorities(角色表),一张是user(用户表),这两张表的表名不能改,因为在默认情况下,spring security会默认读这两张表,脚本如下:

 CREATE DATABASE springsecurity;

  CREATE TABLE `users` (
    `username` varchar(50) NOT NULL,
    `password` varchar(50) NOT NULL,
    `enabled` tinyint(1) NOT NULL,
    PRIMARY KEY  (`username`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  insert  into `users`(`username`,`password`,`enabled`) values ('admin','admin',1);

  CREATE TABLE `authorities` (
    `username` varchar(50) NOT NULL,
    `authority` varchar(50) NOT NULL,
    UNIQUE KEY `ix_auth_username` (`username`,`authority`),
    CONSTRAINT `FK_authorities` FOREIGN KEY (`username`) REFERENCES `users` (`username`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  insert  into `authorities`(`username`,`authority`) values ('admin','ROLE_ADMIN');

27.2.4.2. 配置数据源

在上面我们已经创建数据源了,我们接下来要做的就是在applicationContext.xml配置数据源让Spring Security验证权限时从数据库读取权限信息,把applicationContext.xml中原先定义的权限信息删掉或注释掉,加入连接数据库的配置,修改后如下:

   <?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.4.xsd">

        <!-- 设置权限 -->
    <http auto-config='true'>
      <intercept-url pattern="/admin.faces" access="ROLE_ADMIN" />

            <!-- 自定义登陆页面 -->
            <form-login login-page="/login.faces" authentication-failure-url="/login.faces" />
    </http>

    <!-- 权限信息放在数据库中 -->
    <authentication-provider>
      1<jdbc-user-service data-source-ref="dataSource" />
    </authentication-provider>

    2<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:3306/springsecurity?useUnicode=true&amp;characterEncoding=UTF-8" />
      <beans:property name="username" value="root" />
      <beans:property name="password" value="root" />
    </beans:bean>
  </beans:beans>
1

定义一个jdbc-user-service,然后赋给他一个数据源,这样在验证时,Spring Security会根据数据源去查找权限信息。

2

定义一个数据源。

好了,配置完后就大功告成了,再次访问http://127.0.0.1:6888/aom_springsecurity/enter.faces 或http://127.0.0.1:6888/aom_springsecurity,是不是和之前把权限定义在文件中的一样呢?

本节中只是对Operamasks与Spring Security进行整合提供一个入门级的例子,更多详细的配置请访问Operamasks官方网站和Spring Security官方网站。