tomcat 安全認證 Realm 及 多種型別 Realm 配置
阿新 • • 發佈:2019-02-07
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 不進去。