JAAS是JAVA强大的安全框架模型,JBOSS 对其的支持也是完全和强劲的。
但JAAS总体来说是基于组件的AAA体系(Authentication(验证) Authorization(授权) Accounting(审计)).
Web应用中,往往是基于角色的安全控制(RBAC Role-Based Access Control)比较多,往往授权关系是树形的和矩阵性的复杂关系。
tomcat实现了一个容器类的范围内的验证系统,也就是所谓的CMS(Container Managed Security),这种方式的好处是比较符合要么全有,
要么全无的安全认证,粒度不如JAAS控制的细致。web.xml通过
<security-constraint>
<web-resource-collection><auth-constraint>
三个标志来控制不同角色访问的内容范围.
JBOSS中支持这两种验证方式,但是就出现了一个比较严重的问题。
两种方式是独立的,有各自的LoginContext上下文,必须通过程序的方式在JAAS模块中对Container来进行验证。JBOSS 4.2.3通过class:
org.jboss.web.tomcat.security.login.WebAuthentication;
来实现了这两者对接。综合了两种方式的有点。
另外一种常见的技术是用login proxy的方式来手工调用jsp让内置的j_security_check起作用来综合JAAS和CMS的有点,这里也加入简单说明
在servlet或JSF的backing bean的action对应的方法中,加入如下语句:
String url="/login.jsp"
RequestDispatcher dispatcher = request.getRequestDispatcher(url);
dispatcher.forward(request, response);
login.jsp包含如下内容:
<html>
<head>
<meta
http-equiv="Content-Type"
content="text/html; charset=windows-1252"/>
<title>Logging in</title>
</head>
<body
onload="document.forms[0].submit()">
<form
method="post" action="j_security_check">
<input type="hidden" name="j_username"
value="${param.j_username}"/>
<input type="hidden" name="j_password"
value="${param.j_password}"/>
</form>
</body>
</html>上面的j_username和j_password都是从上页用户输入后传递过来的
上述的方式比较简陋,所以下面就是程序完全控制的方式。
以一个JSF的完全应用为例子
首先编写:login.jsp
<?xml version="1.0" encoding="UTF-8"?>
<jsp:root version="2.0"
xmlns:jsp="http://java.sun.com/JSP/Page"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:h="http://java.sun.com/jsf/html"
>
<jsp:directive.page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" />
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8"></meta>
<title>Login Wizard</title>
</head>
<body><f:view >
<h:form >
<h:outputLabel value="#{SysRes.username}"/><h:inputText value="#{loginManager.userID}" id="j_username" /><br/>
<h:outputLabel value="#{SysRes.password}"/><h:inputSecret value="#{loginManager.password}" id="j_password" /><br/>
<h:commandButton id="checkout" value="${SysRes.OK}" action="#{loginManager.loginAction}" />
</h:form>
</f:view>
</body>
</html>
</jsp:root>
其次配置faces-config.xml这个是默认的配置文件,可以通过在web.xml中定义改变之,后面举例
<managed-bean>
<managed-bean-name>loginManager</managed-bean-name>
<managed-bean-class>org.security.login.LoginManager</managed-bean-class>
<managed-bean-scope>session</managed-bean-scope>
</managed-bean>
三:再看managed-bean,代码如下
package org.security.login;
import java.security.Principal;
import java.util.Set;import javax.faces.application.Application;
import javax.faces.application.NavigationHandler;
import javax.faces.context.FacesContext;import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.security.auth.login.LoginContext;
import javax.security.auth.callback.CallbackHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;import javax.faces.event.ActionEvent;
import javax.security.auth.Subject ;
import javax.servlet.RequestDispatcher;import org.jboss.security.SimplePrincipal;
import org.jboss.web.tomcat.security.login.WebAuthentication;
import org.security.login.jass.*;public class LoginManager
{
private String _uid = "";
private String _pwd = "";
private String requestURI="";
public String getUserID() { return _uid; }
public void setUserID(String uid) { _uid = uid; }
public String getPassword() { return _pwd; }
public void setPassword(String pwd) { _pwd = pwd; }
public String loginAction() throws Exception
{
FacesContext context = FacesContext.getCurrentInstance();
HttpServletResponse response =
(HttpServletResponse) context.getExternalContext().getResponse();
HttpServletRequest request =
(HttpServletRequest) context.getExternalContext().getRequest();
String securityDomain = "seamAC";//安全区域,在logic-config.xml中定义
CallbackHandler callbackHandler = new StandardCallbackHandler(getUserID(),getPassword());
try
{
LoginContext lc =
new LoginContext( securityDomain,
callbackHandler );
lc.login();
WebAuthentication pwl = new WebAuthentication();
boolean flag=pwl.login(getUserID(),getPassword());
System.out.println("WebAuthentication is?"+flag);
System.out.println("User Principal="+request.getUserPrincipal());
}
catch ( Exception e )
{
e.printStackTrace();
// FacesContext context = FacesContext.getCurrentInstance();
// HttpServletResponse response =
// (HttpServletResponse) context.getExternalContext().getResponse();
// response.sendError(100);
// context.responseComplete();
// FacesContext.getCurrentInstance().addMessage("checkout",new FacesMessage(e.getMessage()));
// throw e;
return "loginFail";
}
String url=request.getHeader("referer");
String cUrl=request.getRequestURI();
//再次获取
request =(HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
System.out.println("referer:"+url+" curl:"+cUrl);
System.out.println("用户角色sysAdmin:"+request.isUserInRole("sysAdmin"));
System.out.println("用户角色guest:"+request.isUserInRole("guest"));// Principal principal = new SimplePrincipal(getUserID());
// ObjectName jaasMgr = new ObjectName("jboss.security:service=JaasSecurityManager");
// Object[] params = { securityDomain, principal };
// String[] signature = { "java.lang.String", Principal.class.getName() };
// MBeanServer server = (MBeanServer) MBeanServerFactory.findMBeanServer(null).get(0);
// server.invoke(jaasMgr, "flushAuthenticationCache", params, signature);
if(!(url==null ||url.trim().equals("")))
{
String host=request.getHeader("host");
String cpath=request.getContextPath();
url=url.replace("http://"+host+cpath,"");
System.out.println("url now:"+url+" cpath:"+cpath);
response.sendRedirect(cpath+"/"+url);
//use j_security_check proxy tech,j_username,j_password
// RequestDispatcher dispatcher = request.getRequestDispatcher(url);
// dispatcher.forward(request, response);
}
return "loginPass";
}
public void logout(){
FacesContext fc = javax.faces.context.FacesContext.getCurrentInstance();
((HttpSession)fc.getExternalContext().getSession(false)).invalidate();
Application ap = fc.getApplication();
NavigationHandler nh = ap.getNavigationHandler();
nh.handleNavigation(fc,null,"logout");
}}
四:配置web.xml 加入受保护的资源
<!-- 安全部分的配置 -->
<security-constraint>
<web-resource-collection>
<web-resource-name>Secure Pages</web-resource-name>
<url-pattern>/Genesis/*</url-pattern>
<url-pattern>/Guest/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
<!-- http-method>HEAD</http-method>
<http-method>PUT</http-method>
<http-method>OPTIONS</http-method>
<http-method>TRACE</http-method>
<http-method>DELETE</http-method -->
</web-resource-collection>
<!-- only Admin role can access -->
<auth-constraint>
<role-name>sysAdmin</role-name>
</auth-constraint>
</security-constraint><!-- 登录验证方式 -->
<login-config>
<auth-method>FORM</auth-method>
<realm-name>seamAC</realm-name>
<form-login-config>
<form-login-page>/Login.jsp</form-login-page>
<form-error-page>/Login.jsp</form-error-page>
</form-login-config>
</login-config>
<!-- Declare all Role List -->
<security-role>
<role-name>sysAdmin</role-name>
</security-role>
<security-role>
<role-name>sysGuest</role-name>
</security-role>
五:配置jboss相关部分.使用Jboss 4.2.3 GA JDK1.6 update 10
JAAS部分:conf/login-config.xml,注意seamAC就是LoginManager class中的login方法中引用的,Realm名字.
<!--JAAS -->
<application-policy name = "seamAC">
<authentication>
<login-module code = "org.jboss.security.auth.spi.DatabaseServerLoginModule"
flag = "required">
<module-option name = "unauthenticatedIdentity">guest</module-option>
<module-option name = "dsJndiName">java:/SecMasterDS</module-option>
<module-option name = "principalsQuery">select password from sysUser where userid=? </module-option>
<module-option name = "rolesQuery">SELECT A.roleID as Role,'Roles' FROM sysRoles A,UserRoles B WHERE A.roleID=B.roleID AND B.userId=?</module-option>
<module-option name = "debug">true</module-option>
<!--Password is md5 encrypt-->
<module-option name="hashAlgorithm">MD5</module-option>
<module-option name ="hashEncoding">HEX</module-option>
</login-module>
</authentication>
</application-policy>注意:rolesQuery部分,根据Jboss的class org/jboss/security/auth/spi/DatabaseServerLoginModule.java中有关角色部分的说明,必须有一个roleGroup的字段值为Roles,否则出现用户没有角色的错误,小心注意,这是很愚蠢的假定,往往配置了很久,不能工作,就是这些小地方。
141 /** Overriden by subclasses to return the Groups that correspond to the
142 to the role sets assigned to the user. Subclasses should create at
143 least a Group named "Roles" that contains the roles assigned to the user.
144 A second common group is "CallerPrincipal" that provides the application
145 identity of the user rather than the security domain identity.
146 @return Group[] containing the sets of roles
147 */
数据库部分:deploy/mysql-ds.xml(使用MYSQL 5.1),注意SecMasterDS
<local-tx-datasource>
<jndi-name>SecMasterDS</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/gate?useUnicode=TRUE</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>root</user-name>
<password></password>
<exception-sorter-class-name>org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter</exception-sorter-class-name>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</local-tx-datasource>
支持,繁琐的配置终于可以工作了,访问一个目录,然后经过验证,显示出来内容,实在是非常惬意的事情。更加人性化的界面,比如不具有权限的人向访问一个特定角色的资源,可以在web.xml中截获403错误来做到:
< error-page>
<error-code>403</error-code>
<location>/roleerror.jsp</location>
</error-page>