tomcat 安全认证 Realm 及 多种类型 Realm 配置

tomcat 专栏收录该内容
12 篇文章 0 订阅

web.xml 安全配置

Servlet规范支持安全地访问 web 资源,只需要通过 web.xml 简单配置即可,其功能由服务器提供商实现,

web.xml

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>some name</web-resource-name>

            <url-pattern>*.jsp</url-pattern>
            <url-pattern>*.do</url-pattern>

            <http-method>GET</http-method>
            <http-method>PUT</http-method>
            <http-method>HEAD</http-method>
            <http-method>TRACE</http-method>
            <http-method>POST</http-method>
            <http-method>DELETE</http-method>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>

        <auth-constraint>
            <role-name>tomcat</role-name>
            <role-name>admin</role-name>
        </auth-constraint>

        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
            <!--
                这个可选的元素指出在访问相关资源时使用任何传输层保护。
                它必须包含一个transport-guarantee子元素(合法值为NONE、INTEGRAL或CONFIDENTIAL),
                transport-guarantee为NONE值将对所用的通讯协议不加限制。
                INTEGRAL值表示数据必须以一种防止截取它的人阅读它的方式传送。
                虽然原理上(并且在未来的HTTP版本中),在INTEGRAL和CONFIDENTIAL之间可能会有差别,
                但在当前实践中,他们都只是简单地要求用SSL。
            -->
        </user-data-constraint>

    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>admin page</web-resource-name>
            <url-pattern>/admin.jsp</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>admin</role-name>
        </auth-constraint>
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>tomcat page</web-resource-name>
            <url-pattern>/tomcat.jsp</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>tomcat</role-name>
        </auth-constraint>
    </security-constraint>

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

        <auth-method>FORM</auth-method>
        <form-login-config>
            <form-login-page>/login.html</form-login-page>
            <form-error-page>/error.html</form-error-page>
        </form-login-config>
    </login-config>
    <security-role>
        <role-name>tomcat</role-name>
    </security-role>
    <security-role>
        <role-name>admin</role-name>
    </security-role>

</web-app>

其内容表示:

安全规则:admin.jsp 只能由 admin 角色访问,tomcat.jsp 只能由 tomcat 角色访问,其它任意 *.jsp  *.do 可由 admin 或 tomcat 角色访问。

认证方式:BASIC 是基础认证方式,由浏览器厂商实现的用户名和密码接收界面;FORM 是应用定制的用户名和密码接收页面;

角色声明:必需声明出所有用到的角色


tomcat 用户和角色配置

在tomcat的conf目录下找到 tomcat-users.xml,添加以下内容:
  <role rolename="tomcat"/>
  <role rolename="admin"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user username="both" password="both" roles="tomcat,admin"/>
  <user username="admin" password="admin" roles="admin"/>


Realm配置

tomcat支持多种Realm:
JDBCRealm
DataSourceRealm
JNDIRealm
UserDatabaseRealm
MemoryRealm
JAASRealm
CombinedRealm
LockOutRealm
每种不同的 Realm 采用了不同的 用户名和密码存储和使用方式,tomcat默认使用的是 UserDatabaseRealm。

JDBCRealm

这个 Realm 是基于数据库的,数据库保存了 用户名/密码和用户的角色,通过建立数据库的通信来维持用户角色信息
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)
);

insert into users ( user_name , user_pass ) values ( 'admin','admin')
insert into users ( user_name , user_pass ) values ( 'tomcat','admin')
insert into user_roles values ( 'admin','admin')
insert into user_roles values ( 'tomcat','tomcat')

2.修改tomcat server.xml 添加安全域配置:
<Realm className="org.apache.catalina.realm.JDBCRealm"
   driverName="org.h2.Driver"
   connectionURL="jdbc:h2:tcp://localhost//home/conquer/mine/work_space/h2-dbpath/tomcat"
   connectionName="sa"
   connectionPassword=""
   userTable="users" userNameCol="user_name" userCredCol="user_pass"
   userRoleTable="user_roles" roleNameCol="role_name"/>
</Realm>


DataSourceRealm

这个安全域和上面的JDBCRealm实现基本一致,只不过不是创建数据库连接,而是从jndi上下文获取数据源,它所需要的数据库脚本和JDBCRealm一致。
修改tomcat server.xml 添加安全域配置:
1.在 GlobalNamingResources 节点下添加:
<Resource 
  name="jdbc/h2"
  type="javax.sql.DataSource"
  username="sa"
  password=""
  driverClassName="org.h2.Driver"
  url="jdbc:h2:tcp://localhost//home/conquer/mine/work_space/h2-dbpath/tomcat"/>
2.继续添加安全域配置(这里会使用jndi引用上面注册的数据源资源):
<Realm className="org.apache.catalina.realm.DataSourceRealm"
   dataSourceName="jdbc/h2"
   userTable="users" userNameCol="user_name" userCredCol="user_pass"
   userRoleTable="user_roles" roleNameCol="role_name"/>
</Realm>

MemoryRealm

这个是最简单的的配置,默认是读取 tomcat-users.xml (可通过pathname属性配置为其它文件)里面配置的用户角色信息。
修改server.xml 添加配置:
<Realm className="org.apache.catalina.realm.MemoryRealm"/>


UserDatabaseRealm

这个是基于上述 MemoryRealm 扩展出来的,默认也是读取tomcat-users.xml (可通过pathname属性配置为其它文件)里面配置的用户角色信息,
不过他是通过应用jndi的方式实现的,从设计上支持多种实现方式,默认采用了类似MemoryRealm的实现方式,但是又对其进行了扩展,主要是增加了 “用户组”的概念,即用户除了有所属角色外,还可以有所属 “用户组”,,用户组可以关联其它多个角色,例如它可以使用下面的tomcat-users.xml配置:
  <role rolename="tomcat"/>
  <role rolename="admin"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <group groupname="one" roles="tomcat"/>
  <user username="admin" password="admin" roles="admin" groups="one"/>

admin用户由于关联了 “one”这个用户组,“one”用户组包含角色“tomcat”,所以 admin用户就拥有了“tomcat”角色。

这个安全域的配置:
1. sever.xml 的 GlobalNamingResources 节点下添加:
<Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />

2.启用该安全域的配置:
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
               resourceName="UserDatabase"/>

JAASRealm

这个是基于java的 jaas 认证和授权服务而设计的,主要就是用到了 jaas的认证部分,不涉及“授权”,通过向 javax.security.auth.Subject#getPrincipals()

里面添加用户和角色来完成用户和角色的认证,注意这里的技巧在于,tomcat要在server.xml的该安全域里配置哪些java类型的 Principal 表示用户,哪些java类型的Principal表示角色,因为jaas的登录模块在验证成功后只能将用户和角色信息都放入到javax.security.auth.Subject#getPrincipals(),通过事先的类型约定来让tomcat识别用户和角色信息。

这个安全域的配置如下:
<Realm className="org.apache.catalina.realm.JAASRealm"
                appName="Sample"
			    userClassNames="jaas.SamplePrincipal"
			    roleClassNames="jaas.SampleRolePrincipal"/>

可以看到,上面的配置中指定了jaas.SamplePrincipal表示用户,而jaas.SampleRolePrincipal表示角色。
可以通过 confiFile 属性来配置 jaas 需要的认证配置文件,或者使用java默认 -Djava.security.auth.login.config=xx/jaas.config 参数来指定。
关于jaas的登录模块的实现,请看: Java认证和授权服务 JAAS 之 认证 http://blog.csdn.net/conquer0715/article/details/78204889
Java认证和授权服务 JAAS 之 授权 http://blog.csdn.net/conquer0715/article/details/78205755

注意:如果要使用 Java认证和授权服务 JAAS 之 认证中的例子,需要进行如下更改:

1. MyLoginModule 文件:
package jaas;

import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import java.security.Principal;
import java.util.Map;

public class MyLoginModule implements LoginModule {

    // username and password
    private String username;
    private char[] password;

    // the authentication status
    private boolean userPwdSucceeded = false;
    private boolean commitSucceeded = false;

    // user's Principal
    private Principal userPrincipal;


    // initial state
    private Subject subject;
    private CallbackHandler callbackHandler;


    /**
     * Initialize this <code>LoginModule</code>.
     */
    public void initialize(Subject subject,
                           CallbackHandler callbackHandler,
                           Map<java.lang.String, ?> sharedState,
                           Map<java.lang.String, ?> options) {

        this.subject = subject;
        this.callbackHandler = callbackHandler;
    }

    /**
     * Authenticate the user by prompting for a user name and password.
     */
    public boolean login() throws LoginException {
        // prompt for a user name and password
        if (callbackHandler == null)
            throw new LoginException("Error: no CallbackHandler available " +
                    "to garner authentication information from the user");

        Callback[] callbacks = new Callback[2];
        callbacks[0] = new NameCallback("user name");
        callbacks[1] = new PasswordCallback("password", false);
//        callbacks[2] = new TextOutputCallback(TextOutputCallback.INFORMATION, "hello, just a msg!");
//        callbacks[3] = new TextOutputCallback(TextOutputCallback.WARNING, "just warn you!");

        try {
            callbackHandler.handle(callbacks);
            NameCallback nameCallback = (NameCallback) callbacks[0];
            PasswordCallback passwordCallback = (PasswordCallback) callbacks[1];

            username = nameCallback.getName();

            char[] tmpPassword = passwordCallback.getPassword();
            passwordCallback.clearPassword();// clean password in memory space
            if (tmpPassword == null) {
                tmpPassword = new char[0];// treat a NULL password as an empty password
            }
            password = new char[tmpPassword.length];
            System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length);
        } catch (Exception e) {
            e.printStackTrace();
        }


        // verify the username/password
//        boolean usernameCorrect = false;
//        if (username.equals("user")) usernameCorrect = true;
//
//        if (usernameCorrect &&
//                password.length == 3 &&
//                password[0] == 'p' &&
//                password[1] == 'w' &&
//                password[2] == 'd') {
//
//            userPwdSucceeded = true;
//        } else {
//            userPwdSucceeded = false;
//            cleanUserAndPwdData();
//            if (!usernameCorrect) {
//                throw new FailedLoginException("User Name Incorrect");
//            } else {
//                throw new FailedLoginException("Password Incorrect");
//            }
//        }
//        return userPwdSucceeded;
        userPwdSucceeded=true;
        return true;
    }

    public boolean commit() throws LoginException {
        if (!userPwdSucceeded) return false;

        // add a Principal (authenticated identity) to the Subject
        userPrincipal = new SamplePrincipal(username);
        subject.getPrincipals().add(userPrincipal);
        // for tomcat jaas realm
        if (username.equals("admin")) {
            subject.getPrincipals().add(new SampleRolePrincipal("admin"));
        } else if (username.equals("tomcat")) {
            subject.getPrincipals().add(new SampleRolePrincipal("tomcat"));
        }

        // in any case, clean out state
        cleanUserAndPwdData();

        return commitSucceeded = true;
    }

    public boolean abort() throws LoginException {
        if (!userPwdSucceeded) return false;

        if (commitSucceeded) {
            logout();
        } else {
            cleanState();
        }

        return true;
    }

    public boolean logout() throws LoginException {
        subject.getPrincipals().remove(userPrincipal);
        cleanState();
        userPwdSucceeded = commitSucceeded;
        return true;
    }

    private void cleanState() {
        userPwdSucceeded = false;
        cleanUserAndPwdData();
        userPrincipal = null;
    }

    private void cleanUserAndPwdData() {
        username = null;
        if (password != null) {
            for (int i = 0; i < password.length; i++)
                password[i] = ' ';
            password = null;
        }
    }
}

SamplePrincipal 文件:

package jaas;

import java.security.Principal;

public class SamplePrincipal implements Principal {
    private String name;

    public SamplePrincipal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

}

SampleRolePrincipal文件:
package jaas;

public class SampleRolePrincipal extends SamplePrincipal {
    public SampleRolePrincipal(String name) {
        super(name);
    }
}


说明:
另外注意 userClassNames="jaas.SamplePrincipal" 和 roleClassNames="jaas.SampleRolePrincipal" 两个实现类的 equals 和 hascode 方法,如果覆盖不好最好不要覆盖,否则容易 subject.getPrincipals().add 不进去。

测试:

启动tomcat,部署web应用后,浏览器访问不同的jsp,可以看到安全规则已经生效:
admin.jsp 只能由 admin 角色访问,tomcat.jsp 只能由 tomcat 角色访问,其它任意 *.jsp  *.do 可由 admin 或 tomcat 角色访问。

  • 3
    点赞
  • 0
    评论
  • 3
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

conquer0715

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值