在之前的一篇博客中,Tomcat认证授权与简单的SSO 我在tomcat cluster上搭建了一个简单的网站,并试验了各种认证授权BA,FBA,以及tomcat自身build-in的SSO机制。但就像摘要里面写的内容一样,tomcat自身的FBA并不好用。重复一遍FBA的缺点:
login的过程无法被干预。我们无法通过添加filter的形式进行干预。login完全交给web容器处理,页面也是有web容器负责展示。
没有login地址,用户无法bookmark一个Login页面。直接访问login.html是无法提交form的。login只能在访问受保护资源的时候才会被触发。
因此,我必须寻找一个新的认证方案。
以下几点是我需要考虑的:
JAVA平台。
克服了FBA的缺陷。
简单易用。
稳定,一直存在维护,且充满活力。
可以跟各种认证系统集成,比如CAS,OpenSSO, JAVASSO, Kerberos, SAML.
易于扩展,比如remember me, oauth2.
Spring Security 3则是很好的选择。另外不得不提一下SecurityFilter, SecurityFilter是一个非常老的,基于servlet filter设计的一个认证框架,非常的简单易上手。可惜它好像从2005年,就没再更新了。 如果想自己写一个认证框架,SecurityFilter是一个很好的模仿对象。它的source现在可以在这里下载: http://securityfilter.sourceforge.net
接下来,开始使用Spring Security 3来重新搭建我们的网站。
本篇文章和下一篇文章所生成的代码均可以在此下载:http://pan.baidu.com/s/1nthpuDN
Spring使用maven管理自己的发布。所以基于Spring的开发,最好使用maven,否则理清楚Spring控件之间的依赖关系都要花很大的功夫。我在附件里面上传了一个PPT,介绍maven的概念和使用,http://pan.baidu.com/s/1kauU 。
安装eclipse maven插件。help->install new software->输入http://download.eclipse.org/technology/m2e/releases。安装插件。
点击finish以后,一个maven的web项目就建成了。请在java目录下创建java包和类,在resources下面放置resources,而在webapp下面放置web页面文件与配置信息。
接下来,我们需要修改pom.xml,增加对Spring的依赖
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>3.2.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>3.2.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-ldap</artifactId> <version>3.2.0.RELEASE</version> </dependency>
在eclipse中,右键点击project firstWeb --->Maven--->Update project. 这样把项目所有依赖的jar包都自动下载到项目中。
在进行试验开发之前,我得嘚吧嘚吧Spring Security 3. 只有文字,没有代码的文章,不是好手册。只有代码,没有文字的博客,则不是可以被容易看懂的文章。
Spring在古老的记忆中,只是一个框架,它具有IoC和AoP的特性。然而近几年,它已经变成了一个平台。IoC与AoP依然成为它的一个子项目Spring Framework。在核心项目之上,又创建了十几个非常具有竞争力的项目,比如Spring MVC, Spring Security, 这些项目覆盖了J2EE领域中最常见的应用领域,包含Mobile,数据访问。
Spring的上手不是特别简单,但一旦上手,则是非常的简单。原因就在于它的IoC(控制反转,也称DI,依赖注入)和AoP(面向切面编程)。因为这两样东西,打破了一个程序的常规逻辑,使的逻辑和代码分散在各个角落。这让我们只能看到树叶,却看不到大树。然而,Spring的项目中高度使用了DI和AoP。这让我们在使用Spring的时候更注重使用形式而忽略了原理。
另外,Spring是一个很潮的社团,JAVA的新特性总是能得到大力应用。于是Java annotation在Spring中也广泛的用起来,这更加重了代码的支离破碎。这就是为什么Spring上手难的原因。
Spring Security3作为Spring下的一个子项目,它既可以跟Spring其它项目集成起来一起使用,也可以单独使用,两种情况下的配置是不一样的。另外,Spring Security3的配置文件上,又可以分两种, 基于XML的配置,和基于JAVA配置。
基于XML的配置易于修改,配置集中。基于Java的配置易于创建,语法简单。但无论它如何变化多端,我们都必须擦亮眼睛,看清它的实质。它跟SecurityFilter和Struts2一样,都是基于Servlet Filter(springSecurityFilterChain)来对所有的URL进行拦截的,然后再根据自己的配置,对各个URL进行转发。所有的config只是为了让我们能简单的声明URL的拦截和转发的逻辑。
说了这么多,恐怕大家都脑子乱掉了。接下来,我将从一个空项目开始,一步一步的搭建我的网站。注,本实验把Spring Security3.2单独使用,使用javaconfig。之所以使用javaconfig,是因为网上有太多的XML配置,但却没有javaconfig的复杂实例,甚至连官方文档都没有详细的解释。要想了解和Spring MVC的整合,以及XML的配置方式,请参考官方文档。
之前一篇文章已经有一个应用sessiontest.现在我不打算再用它。我将从上面新建的maven项目firstWeb开始重新创建一个网站。网站还是将部署在之前的tomcat集群上,使用前面文章配置好的OpenLDAP做用户存贮。OpenLDAP的搭建,请看之前的文章。
首先搭建没有安全保护的网站。如图所示:
在webapp下面,存在index.jsp用于默认页面。ajax目录下则是给API用的,将来使用BA认证。html目录下则是浏览网页,将使用FBA认证,登陆页面为login.jsp。index.jsp上有一个FORM将提交到submit.jsp.
index.jsp
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <html> <head> <title>Your selections</title> </head> <body> <a href="${pageContext.request.contextPath }/html/login.jsp">login</a> <a href="${pageContext.request.contextPath }/html/logout">logout</a> <% String selections = (String)session.getAttribute("selections"); selections=selections==null?"":selections; %> <form action="${pageContext.request.contextPath }/html/submit.jsp" method="post"> <p>What do you prefer?</p> <p><input type="checkbox" name="car" value="car" <% if(selections.indexOf("car")>-1) out.print("checked=\"checked\""); %> />Car</p> <p><input type="checkbox" name="bike" value="bike" <% if(selections.indexOf("bike")>-1) out.print("checked=\"checked\""); %> />Bike</p> <p><input type="checkbox" name="train" value="train" <% if(selections.indexOf("train")>-1) out.print("checked=\"checked\""); %> />Train</p> <p><input type="checkbox" name="plane" value="plane" <% if(selections.indexOf("plane")>-1) out.print("checked=\"checked\""); %> />Plane</p> <input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/> <input type="submit" value="Submit" /> </form> </body> </html>
submit.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> <title>Successful</title> </head> <body> <a href="${pageContext.request.contextPath }/index.jsp">Back</a> <a href="${pageContext.request.contextPath }/html/logout">logout</a> <% StringBuffer sb = new StringBuffer(); if (null !=request.getParameter("car"))sb.append("car;"); if (null !=request.getParameter("bike"))sb.append("bike;"); if (null !=request.getParameter("train"))sb.append("train;"); if (null !=request.getParameter("plane"))sb.append("plane;"); session.setAttribute("selections", sb.toString()); %> Successful, please go back to check.<br> </body> </html>
login.jsp
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page pageEncoding="UTF-8" %> <html> <head> <title>Login</title> </head> <body onload="document.f.username.focus();"> <h1>Login</h1> <c:if test="${not empty param.login_error}"> <font color="red"> Your login attempt was not successful, try again.<br/><br/> Reason: <c:out value="${SPRING_SECURITY_LAST_EXCEPTION.message}"/>. </font> </c:if> <form name="f" action="${pageContext.request.contextPath }/html/login" method="POST"> <table> <tr><td>User:</td><td><input type='text' name='username' value='<c:if test="${not empty param.login_error}">fbloggs</c:if>'/></td></tr> <tr><td>Password:</td><td><input type='password' name='password'></td></tr> <tr><td><input type="checkbox" name="remember-me"></td><td>Don't ask for my password for two weeks</td></tr> <tr><td colspan='2'><input name="submit" type="submit"></td></tr> <tr><td colspan='2'><input name="reset" type="reset"></td></tr> </table> <input type="hidden" name="<c:out value="${_csrf.parameterName}"/>" value="<c:out value="${_csrf.token}"/>"/> </form> </body> </html>
403.jsp
<%@ page import="org.springframework.security.core.context.SecurityContextHolder" %> <%@ page import="org.springframework.security.core.Authentication" %> <html> <head> <title>Access Denied</title> </head> <body> <h1>Sorry, access is denied</h1> <p> <%= request.getAttribute("SPRING_SECURITY_403_EXCEPTION")%> </p> <p> <% Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null) { %> Authentication object as a String: <%= auth.toString() %><br /><br /> <% } %> </p> </body> </html>
forbidden.html
<html> <body> If you see this page, that means web security succeeds. </body> </html>
status.jsp
<%@ page pageEncoding="UTF-8" contentType="application/json;charset=UTF-8"%> {}&&{user: "${pageContext.request.remoteUser}"}
创建以上代码页面以后,我们可以运行maven install来发布web包,然后将web包部署到一个tomcat上。可以通过http://localhost:8080/firstWeb 来开始浏览所有的页面。这时候,所有的页面都是公开的,没有任何保护。
接下来,我们使用Spring Security 3来配置认证和授权。 首先假设我们是用XML来配置的话,一般是经过以下几个步骤:
在web.xml中注册springSecurityFilterChain.
在web.xml中指定Spring Security所使用的XML配置文件路径。
创建XML配置文件。下面给出了一个XML的简单例子。其中使用的是我的OpenLDAP作为认证和授权。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:s="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.xsd"> <s:http> <s:intercept-url pattern="/**" access="ROLE_RED" /> <s:form-login /> <s:anonymous /> <s:logout /> </s:http> <s:authentication-manager> <s:authentication-provider ref='ldapAuthProvider' /> </s:authentication-manager> <!-- Traditional Bean version of the same configuration --> <bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource"> <constructor-arg value="ldap://127.0.0.1:389/dc=mycompany,dc=com"/> <property name="userDn" value="cn=admin,dc=mycompany,dc=com"/> <property name="password" value="admin"/> </bean> <bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> <constructor-arg> <bean class="org.springframework.security.ldap.authentication.BindAuthenticator"> <constructor-arg ref="contextSource"/> <property name="userDnPatterns"><list><value>uid={0},ou=people</value></list></property> </bean> </constructor-arg> <constructor-arg> <bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator"> <constructor-arg ref="contextSource"/> <constructor-arg value="ou=groups"/> <property name="groupRoleAttribute" value="cn"/> <property name="groupSearchFilter" value="uniqueMember={0}"/> </bean> </constructor-arg> </bean> </beans>
因为本文主要是javaconfig,所以上面的xml只是一个例子,对今天的项目并没有实际作用。但上面的xml可以轻松的教导我们如何生成javaconfig。在缺失大量javaconfig的文档的情况下,参考xml可以指导我们javaconfig所使用的类与方法。
进阶-使用Spring Security3.2搭建LDAP认证授权和Remember-me(2)