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 角色访问。