星期六, 九月 06, 2008

JBOSS JAAS和tomcat j_security_check CMS关系

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>