参考资料:
[1]. 深入剖析Tomcat(How Tomcat works)书籍代码下载地址
第八章:载入器
第九章:Session管理器
第十章:安全性
- 为什么Tomcat需要自定义Loader?
- 因为使用系统的类加载器,servlet可以访问所有的类,这是非常危险的,servlet应该只允许载入WEB-INF/classes目录下的所有类和库。2. 为了提供自动重载的功能,当类的目录下的类方式变化时,WEB应用会重新载入这些类。类加载器使用一个额外的线程来不断地检查servlet类及其他类的文件的时间戳,若要支持自动重载功能,类加载器要实现Reloader接口。类记载器要实现Loader接口。
-
为什么Java类的加载要加载双亲委派机制?
为了解决安全问题。安全管理器用来限制某个类对某个路径的访问,如果JVM是新人Object类的,没有双亲委派机制,用户可以加载自定义的Object类,安全管理器就这样被轻易绕过。也就是系统和用户自定义类有优先级的概念,优先记载系统的类,用户的类就不能混淆到系统的类,带来一些问题。 -
自定义类加载器可能有以下的原因:
- 为了在加载器中指定某些规则
- 为了缓存已经载入的类
- 为了实现类的预载入,方便使用
-
载入器
Tomcat载入器通常会与一个Context级别的servlet容器相关联。 -
Session管理器
Session跟一个Context容器相关联,有些Session是可以存储到磁盘文件或者数据库中的。还有个Manager接口,差不多相当于Session的工厂。 -
安全相关类
GenericPrincipal:实现了Principal接口,它代表的是一个用户的身份,包括用户的名字,角色,密码等。
Realm:用来进行身份验证,它的主要方法有autheticate,Realm跟Context一对一绑定。
LoginConfig:类包含了登录的所有配置信息,验证方法authMethod、错误界面errorPage、登录界面loginPage、realmName。 -
Context容器安全验证配置
下面这段代码是关于Context容器在安全验证方面的配置,大致看一下。
public final class Bootstrap1 {
public static void main(String[] args) {
LifecycleListener listener = new SimpleContextConfig();
((Lifecycle) context).addLifecycleListener(listener);
// add constraint
SecurityCollection securityCollection = new SecurityCollection();
securityCollection.addPattern("/");
securityCollection.addMethod("GET");
SecurityConstraint constraint = new SecurityConstraint();
constraint.addCollection(securityCollection);
constraint.addAuthRole("manager");
LoginConfig loginConfig = new LoginConfig();
loginConfig.setRealmName("Simple Realm");
// add realm
Realm realm = new SimpleRealm();
context.setRealm(realm);
context.addConstraint(constraint);
context.setLoginConfig(loginConfig);
}
}
- 验证器的安装
验证器的安装是采用监听器的方式实现的,SimpleContextConfig类继承自LifecycleListener,监听方法lifecycleEvent中只对开始时间做了处理,调用了authenticatorConfig方法进行验证器的安装。
public class SimpleContextConfig implements LifecycleListener {
private Context context;
public void lifecycleEvent(LifecycleEvent event) {
if (Lifecycle.START_EVENT.equals(event.getType())) {
context = (Context) event.getLifecycle();
authenticatorConfig();
context.setConfigured(true);
}
}
private synchronized void authenticatorConfig() {
// Does this Context require an Authenticator?
SecurityConstraint constraints[] = context.findConstraints();
if ((constraints == null) || (constraints.length == 0))
return;
LoginConfig loginConfig = context.getLoginConfig();
if (loginConfig == null) {
loginConfig = new LoginConfig("NONE", null, null, null);
context.setLoginConfig(loginConfig);
}
// Has an authenticator been configured already?
Pipeline pipeline = ((StandardContext) context).getPipeline();
if (pipeline != null) {
Valve basic = pipeline.getBasic();
if ((basic != null) && (basic instanceof Authenticator))
return;
Valve valves[] = pipeline.getValves();
for (int i = 0; i < valves.length; i++) {
if (valves[i] instanceof Authenticator)
return;
}
}
else { // no Pipeline, cannot install authenticator valve
return;
}
// Has a Realm been configured for us to authenticate against?
if (context.getRealm() == null) {
return;
}
// Identify the class name of the Valve we should configure
String authenticatorName = "org.apache.catalina.authenticator.BasicAuthenticator";
// Instantiate and install an Authenticator of the requested class
Valve authenticator = null;
try {
Class authenticatorClass = Class.forName(authenticatorName);
authenticator = (Valve) authenticatorClass.newInstance();
((StandardContext) context).addValve(authenticator);
System.out.println("Added authenticator valve to Context");
}
catch (Throwable t) {
}
}
}
authenticatorConfig方法首先检查了是否配置了SecurityConstraint,是否已经实现了安装了Authenticator,Context是否已经配置了Realm(因为Authenticator最后验证还是调用的Realm),最后再给Context配置一个阀门BasicAuthenticator。
- 安全验证的流程
从上面我们知道安全验证最后配置为一个阀门,那么我们找到阀门的invoke方法就可以知道安全验证到底做了什么。
是否缓存在Request,Session中,如果是获取principal,往下;
是否是表格验证,如果是进行验证;
有无限制SecurityConstraint,没有调用后面的阀门,然后返回;
还有几个Constraint相关的验证,包括密码(用子类的authenticate方法),角色等;
如果上面都通过验证,调用阀门继续向前处理。
public abstract class AuthenticatorBase
extends ValveBase
implements Authenticator, Lifecycle {
public void invoke(Request request, Response response,
ValveContext context)
throws IOException, ServletException {
// If this is not an HTTP request, do nothing
if (!(request instanceof HttpRequest) ||
!(response instanceof HttpResponse)) {
context.invokeNext(request, response);
return;
}
if (!(request.getRequest() instanceof HttpServletRequest) ||
!(response.getResponse() instanceof HttpServletResponse)) {
context.invokeNext(request, response);
return;
}
HttpRequest hrequest = (HttpRequest) request;
HttpResponse hresponse = (HttpResponse) response;
if (debug >= 1)
log("Security checking request " +
((HttpServletRequest) request.getRequest()).getMethod() + " " +
((HttpServletRequest) request.getRequest()).getRequestURI());
LoginConfig config = this.context.getLoginConfig();
// Have we got a cached authenticated Principal to record?
if (cache) {
Principal principal =
((HttpServletRequest) request.getRequest()).getUserPrincipal();
if (principal == null) {
Session session = getSession(hrequest);
if (session != null) {
principal = session.getPrincipal();
if (principal != null) {
if (debug >= 1)
log("We have cached auth type " +
session.getAuthType() +
" for principal " +
session.getPrincipal());
hrequest.setAuthType(session.getAuthType());
hrequest.setUserPrincipal(principal);
}
}
}
}
// Special handling for form-based logins to deal with the case
// where the login form (and therefore the "j_security_check" URI
// to which it submits) might be outside the secured area
String contextPath = this.context.getPath();
String requestURI = hrequest.getDecodedRequestURI();
if (requestURI.startsWith(contextPath) &&
requestURI.endsWith(Constants.FORM_ACTION)) {
if (!authenticate(hrequest, hresponse, config)) {
if (debug >= 1)
log(" Failed authenticate() test");
return;
}
}
// Is this request URI subject to a security constraint?
SecurityConstraint constraint = findConstraint(hrequest);
if ((constraint == null) /* &&
(!Constants.FORM_METHOD.equals(config.getAuthMethod())) */ ) {
if (debug >= 1)
log(" Not subject to any constraint");
context.invokeNext(request, response);
return;
}
if ((debug >= 1) && (constraint != null))
log(" Subject to constraint " + constraint);
// Make sure that constrained resources are not cached by web proxies
// or browsers as caching can provide a security hole
if (!(((HttpServletRequest) hrequest.getRequest()).isSecure())) {
HttpServletResponse sresponse =
(HttpServletResponse) response.getResponse();
sresponse.setHeader("Pragma", "No-cache");
sresponse.setHeader("Cache-Control", "no-cache");
sresponse.setDateHeader("Expires", 1);
}
// Enforce any user data constraint for this security constraint
if (debug >= 1)
log(" Calling checkUserData()");
if (!checkUserData(hrequest, hresponse, constraint)) {
if (debug >= 1)
log(" Failed checkUserData() test");
// ASSERT: Authenticator already set the appropriate
// HTTP status code, so we do not have to do anything special
return;
}
// Authenticate based upon the specified login configuration
if (constraint.getAuthConstraint()) {
if (debug >= 1)
log(" Calling authenticate()");
if (!authenticate(hrequest, hresponse, config)) {
if (debug >= 1)
log(" Failed authenticate() test");
// ASSERT: Authenticator already set the appropriate
// HTTP status code, so we do not have to do anything special
return;
}
}
// Perform access control based on the specified role(s)
if (constraint.getAuthConstraint()) {
if (debug >= 1)
log(" Calling accessControl()");
if (!accessControl(hrequest, hresponse, constraint)) {
if (debug >= 1)
log(" Failed accessControl() test");
// ASSERT: AccessControl method has already set the appropriate
// HTTP status code, so we do not have to do anything special
return;
}
}
// Any and all specified constraints have been satisfied
if (debug >= 1)
log(" Successfully passed all security constraints");
context.invokeNext(request, response);
}
}
网友评论