Java WEB 安全认证

用户权限问题是绝大部分应用都要考虑的问题,在这些应用中想必都会定义role,user等来控制资源的访问。今天这里要说的并不是如何去设计权限的管理系统,而是来说明一下Web应用的安全域等。

         在与Java web相关的JSR中,对于Java Web的安全访问有也相关规定,具体内容就不再这里阐述。与些相关的配置有:security-constraint,security-role,login-config。 

         Web安全访问的机制其实就是一套权限处理系统。它包括了:role、user、resources、group等概念。接下来就心Tomcat的manager应用为例来了解一下web安全相关内容。

 技术分享

 

1、在Web.xml中使用security-role定义角色

在Manager应用在web.xml中定义了下列角色:

<security-role>
    <description>
      The role that is required to access the HTML Manager pages
    </description>
    <role-name>manager-gui</role-name>
  </security-role>
  <security-role>
    <description>
      The role that is required to access the text Manager pages
    </description>
    <role-name>manager-script</role-name>
  </security-role>
  <security-role>
    <description>
      The role that is required to access the HTML JMX Proxy
    </description>
    <role-name>manager-jmx</role-name>
  </security-role>
  <security-role>
    <description>
      The role that is required to access to the Manager Status pages 
    </description>
    <role-name>manager-status</role-name>
  </security-role>
  <security-role>
    <description>
      Deprecated role that can access all Manager functionality
    </description>
    <role-name>manager</role-name>
  </security-role>

 从上面的例子中,就直接可以看出角色定义是很简单的,只需要设置role-name就可以了。

2、在web.xml中使用security-constraint配置资源访问

Manager应用中定义了一些安全访问的限制如下:

<security-constraint>
    <web-resource-collection>
      <web-resource-name>Manager commands</web-resource-name>
      <url-pattern>/list</url-pattern>
      <url-pattern>/expire</url-pattern>
      <url-pattern>/sessions</url-pattern>
      <url-pattern>/start</url-pattern>
      <url-pattern>/stop</url-pattern>
      <url-pattern>/install</url-pattern>
      <url-pattern>/remove</url-pattern>
      <url-pattern>/deploy</url-pattern>
      <url-pattern>/undeploy</url-pattern>
      <url-pattern>/reload</url-pattern>
      <url-pattern>/save</url-pattern>
      <url-pattern>/serverinfo</url-pattern>
      <url-pattern>/roles</url-pattern>
      <url-pattern>/resources</url-pattern>
      <url-pattern>/findleaks</url-pattern>
    </web-resource-collection>
    <auth-constraint>
       <!-- NOTE: 1. These roles are not present in the default users file
                  2. The manager role is deprecated, it will be removed in
                     Tomcat 7.
                  3. Use the manager-script role to take advantage of the new
                     CSRF protection. Using the manager role or assigning both
                     the manager-script and manager-gui roles to the same user
                     will bypass the CSRF protection. -->
       <role-name>manager-script</role-name>
       <role-name>manager</role-name>
    </auth-constraint>
  </security-constraint>

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>HTML Manager commands</web-resource-name>
      <url-pattern>/html/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
       <!-- NOTE: 1. These roles are not present in the default users file
                  2. The manager role is deprecated, it will be removed in
                     Tomcat 7.
                  3. Use just the manager-gui role to take advantage of the new
                     CSRF protection. Assigning the manager role or manager-gui
                     role along with either the manager-script or manager-jmx
                     roles to the same user will bypass the CSRF protection. -->
       <role-name>manager-gui</role-name>
       <role-name>manager</role-name>
    </auth-constraint>
  </security-constraint>

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>JMX proxy</web-resource-name>
      <url-pattern>/jmxproxy/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
       <!-- NOTE: 1. These roles are not present in the default users file
                  2. The manager role is deprecated, it will be removed in
                     Tomcat 7.
                  3. Use the manager-jmx role to take advantage of the new
                     CSRF protection. Using the manager role or assigning both
                     the manager-jmx and manager-gui roles to the same user
                     will bypass the CSRF protection. -->
       <role-name>manager-jmx</role-name>
       <role-name>manager</role-name>
    </auth-constraint>
  </security-constraint>

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>Status</web-resource-name>
      <url-pattern>/status/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
       <!-- NOTE: 1. These roles are not present in the default users file
                  2. The manager role is deprecated, it will be removed in
                     Tomcat 7. -->
       <role-name>manager-status</role-name>
       <role-name>manager-gui</role-name>
       <role-name>manager-script</role-name>
       <role-name>manager-jmx</role-name>
       <role-name>manager</role-name>
    </auth-constraint>
  </security-constraint>

 从上面的例子中很容易可以理解:security-constraint是用于对指定的角色进行url资源访问上的限制。

 

3、使用Realm来配置user

到目前为止,resources、role已经配置完毕,那么怎么去指定访问资源的用户呢?以及这些用户是拥有哪些role呢?

Realm安全域,简单的理解就是一个数据存储介质(例如xml-文件、数据库、JNDI数据源等),或者说是数据来源。它配置了username、password、所属roles的这样一个简单的数据库。

         第一种服务器中间件都会支持多种Realm的配置方式。这里只说一下在使用Tomcat时,可以基于哪些数据存储介质来配置:

 技术分享

相信看了这个类的继承关系图,就可以知道Tomcat中支持哪些配置Realm的方式了。

 

MemoryRealm 是使用tomcat/conf/tomcat-users.xml来配置的。

JNDIRealm 是基于JNDI资源的方式配置的,

JDBCRealm 是将用户存储在数据库里。

 

3.1  JDBCRealm

在一个数据库里创建下面两张表:

create table users (
  user_name         varchar(15) not null primary key,
  user_pass         varchar(15) not null
);

create table user_roles (
  user_name         varchar(15) not null,
  role_name         varchar(15) not null,
  primary key (user_name, role_name)
);

然后在tomcat/conf/server.xml中配置插入数据.

<Realm className="org.apache.catalina.realm.JDBCRealm"
      driverName="org.gjt.mm.mysql.Driver"
   connectionURL="jdbc:mysql://localhost/authority?user=dbuser;password=dbpass"
       userTable="users" userNameCol="user_name" userCredCol="user_pass"
   userRoleTable="user_roles" roleNameCol="role_name"/>

 


3.2 DataSourceRealm
 

在tomcat中定义数据源的方式就是使用JNDI Resources。

1、创建相关表

create table users (
  user_name         varchar(15) not null primary key,
  user_pass         varchar(15) not null
);

create table user_roles (
  user_name         varchar(15) not null,
  role_name         varchar(15) not null,
  primary key (user_name, role_name)
);

2、server.xml配置一个Global Resources or Context Resources

<Resource name="jdbc/authority" auth="Container"
            type="javax.sql.DataSource"
username="dbuser" password="dbpass" url= "jdbc:mysql://localhost/authority?useEncoding=UTF-8"
/>

 

如果不会配置datasource可以参考Tomcat:定义JNDI资源,访问数据库

 

3、在server.xml中配置Realm

<Realm className="org.apache.catalina.realm.DataSourceRealm"
   dataSourceName="jdbc/authority"
   userTable="users" userNameCol="user_name" userCredCol="user_pass"
   userRoleTable="user_roles" roleNameCol="role_name"/>

 


3.3 JNDIRealm
 

虽然tomcat中使用resource定义的资源都是通过jndi方式来访问的,但是这里说的JNDIRealm是特指使用了LDAP 目录服务的数据源。

 

3.4 UserDatabaseRealm

UserDatabaseRealm是Realm的一个子类,它是通过JNDI资源的方式来获取数据的。所以需要采用Resource的定义方式来定义。例如Tomcat中默认的配置

    <Resource auth="Container" description="User database that can be updated and saved" factory="org.apache.catalina.users.MemoryUserDatabaseFactory" name="UserDatabase" pathname="conf/tomcat-users.xml" type="org.apache.catalina.UserDatabase"/>

它定义了名为UserDatabase的资源,然后在Engine范围内使用这个Realm:

<Engine defaultHost="localhost" name="Catalina">
<Realm 
className="org.apache.catalina.realm.UserDatabaseRealm" 
resourceName="UserDatabase" />

只不过数据来源于tomcat-users.xml。

 

3.5 MemoryRealm

如果要使用MemoryRealm,需要在server.xml中配置相关Realm,MemoryRealm默认是使用tomcat/conf/tomcat-users.xml文件。其实也可以是其它的xml文件,只要格式与tomcat-user.xml 文件格式一样即可。也就是说,满足在tomcat-users元素下定义user(和role)即可。

<tomcat-users>
<!--
  NOTE:  By default, no user is included in the "manager-gui" role required
  to operate the "/manager/html" web application.  If you wish to use this app,
  you must define such a user - the username and password are arbitrary.
-->
<!--
  NOTE:  The sample user and role entries below are wrapped in a comment
  and thus are ignored when reading this file. Do not forget to remove
  <!.. ..> that surrounds them.
-->

  <role rolename="tomcat"/>
  <role rolename="role1"/>

  <user username="both" password="tomcat" roles="tomcat,role1"/>
  <user username="role1" password="tomcat" roles="role1"/>

    <user username="tomcat" password="tomcat" roles="manager-gui,manager" />
    <user username="tomcat2" password="tomcat" roles="manager-gui,manager" />
    <user username="tomcat3" password="tomcat" roles="manager-gui,manager" />
    <user username="tomcat4" password="tomcat" roles="manager-gui,manager" />
</tomcat-users>

 


3.6 JAASRealm
 

如果你了解JAAS的话,JAASRealm的方式你就很容易学会的。JAAS 是Java Authentication & Authorization Service (JAAS) framework ,是J2SE API的一部分。这个就不再详细说明。

 

3.7 CombinedRealm

顾名思义,就是结合了多种Realm方式。例如:

<Realm className="org.apache.catalina.realm.CombinedRealm" >
    <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"/>
    <Realm className="org.apache.catalina.realm.DataSourceRealm"
             dataSourceName="jdbc/authority"
             userTable="users" userNameCol="user_name" userCredCol="user_pass"
             userRoleTable="user_roles" roleNameCol="role_name"/>

</Realm>

 

3.8 LockOutRealm 

LockOutRealm 是 CombinedRealm的子类,它额外的提供了锁出机制。这里就不再详细说明了。

 

4 认证方式

         到目前为此,user( & group) 、role、resouces都配置完毕了,那么要让服务器采用哪种认证方式呢?

         目前的服务器中间件都支持下列几种方式: BASIC、DIGEST、CLIENT_CERT、FORM。这几种方式是标准的。

对于tomcat来说,它同样也支持这几种。

 技术分享

要配置采用哪种认证方式很简单,只需要在web.xml的login-config中配置认证方式为上面几种即可。下面来简单的分别说一下这几种认证方式。

 

4.1 BASIC

Basic方式是最简单的方式,用户若访问受限的资源时,浏览器就会弹出一个对话框,让输入用户名和密码。浏览器在发送请求时,会使用Base64进行编码。

<login-config>
        <auth-method>BASIC</auth-method>
    </login-config>

查看认证过程的部分源码: 

if (authorization != null) {
            authorization.toBytes();
            ByteChunk authorizationBC = authorization.getByteChunk();
            if (authorizationBC.startsWithIgnoreCase("basic ", 0)) {
                authorizationBC.setOffset(authorizationBC.getOffset() + 6);
                // FIXME: Add trimming
                // authorizationBC.trim();
                
                CharChunk authorizationCC = authorization.getCharChunk();
                Base64.decode(authorizationBC, authorizationCC);
                
                // Get username and password
                int colon = authorizationCC.indexOf(‘:‘);
                if (colon < 0) {
                    username = authorizationCC.toString();
                } else {
                    char[] buf = authorizationCC.getBuffer();
                    username = new String(buf, 0, colon);
                    password = new String(buf, colon + 1, 
                            authorizationCC.getEnd() - colon - 1);
                }
                
                authorizationBC.setOffset(authorizationBC.getOffset() - 6);
            }

            principal = context.getRealm().authenticate(username, password);
            if (principal != null) {
                register(request, response, principal, Constants.BASIC_METHOD,
                         username, password);
                return (true);
            }
        }
        

 认证前,使用Base64解码取得用户名和密码,然后进行认证。

 

4.2 FORM

使用FORM方式时,就需要在自己定制登录页面:

<login-config>
        <auth-method>FORM</auth-method>
        <form-login-config>
            <form-login-page>/login.html</form-login-page>
            <form-error-page>/error.jsp</form-error-page>
        </form-login-config>
    </login-config>

login页面如下:

<form name="loginform" method="post" action="j_security_check">
<INPUT name="j_username" type="text">
<INPUT name="j_password" TYPE="password">
<input type="submit" value="登 录" >
</form>

其中表单的action值,以及代表用户名、密码的input的name值是固定的。

 

下面是部分源码:

String username = request.getParameter(Constants.FORM_USERNAME);
        String password = request.getParameter(Constants.FORM_PASSWORD);
        if (log.isDebugEnabled())
            log.debug("Authenticating username ‘" + username + "‘");
        principal = realm.authenticate(username, password);
        if (principal == null) {
            forwardToErrorPage(request, response, config);
            return (false);
        }

 认证失败就会转到error-page了。

 

4.3 DIGEST

<login-config>
        <auth-method>DIGEST</auth-method>
    </login-config>

Digest,采用的是MD5摘要算法。可以参考源码:

            // Second MD5 digest used to calculate the digest :
            // MD5(Method + ":" + uri)
            String a2 = method + ":" + uri;

            byte[] buffer;
            synchronized (md5Helper) {
                buffer = md5Helper.digest(a2.getBytes());
            }
            String md5a2 = md5Encoder.encode(buffer);

            return realm.authenticate(userName, response, nonce, nc, cnonce,
                    qop, realmName, md5a2);

 

 

4.4 CLIENT_CERT

<login-config>
        <auth-method>CLIENT-CERT</auth-method>
    </login-config>

采用的是X509证书认证。 

 

 

5、安全认证流程

      当用户访问的是一个受限的资源(也就是security-contistans中配置的url)时,应付触发安全认证。安全认证的过程:Server通过不同形式(基于认证方式的不同)取得用户的username和password,与Realm中存储的值匹配,匹配成功后就得知访问用户所拥有的roles,再根据roles判断是否能够访问的资源。如果这些流程都通过,会将用户信息存储在session中,这样就不需要每次访问受限资源都输入用户名和密码,之后就能够访问到资源了。中间过程有一步出错,就会访问失败。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。