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

web.xml 安全配置

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

web.xml




    
        
            some name

            *.jsp
            *.do

            GET
            PUT
            HEAD
            TRACE
            POST
            DELETE
            OPTIONS
        

        
            tomcat
            admin
        

        
            NONE
            
        

    

    
        
            admin page
            /admin.jsp
        
        
            admin
        
    

    
        
            tomcat page
            /tomcat.jsp
        
        
            tomcat
        
    

    
        

        FORM
        
            /login.html
            /error.html
        
    
    
        tomcat
    
    
        admin
    



其内容表示:

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

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

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


tomcat 用户和角色配置

在tomcat的conf目录下找到 tomcat-users.xml,添加以下内容:
  
  
  
  
  


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 添加安全域配置:




DataSourceRealm

这个安全域和上面的JDBCRealm实现基本一致,只不过不是创建数据库连接,而是从jndi上下文获取数据源,它所需要的数据库脚本和JDBCRealm一致。
修改tomcat server.xml 添加安全域配置:
1.在 GlobalNamingResources 节点下添加:
2.继续添加安全域配置(这里会使用jndi引用上面注册的数据源资源):


MemoryRealm

这个是最简单的的配置,默认是读取 tomcat-users.xml (可通过pathname属性配置为其它文件)里面配置的用户角色信息。
修改server.xml 添加配置:


UserDatabaseRealm

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

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

这个安全域的配置:
1. sever.xml 的 GlobalNamingResources 节点下添加:

2.启用该安全域的配置:


JAASRealm

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

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

这个安全域的配置如下:

可以看到,上面的配置中指定了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 LoginModule.
     */
    public void initialize(Subject subject,
                           CallbackHandler callbackHandler,
                           Map sharedState,
                           Map 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 角色访问。

你可能感兴趣的:(tomcat)