tomcat(10)安全性

【0】README
0.0)本文部分描述转自“深入剖析tomcat”,旨在学习 tomcat(10)安全性 的基本知识;
0.1)servlet技术支持通过配置部署描述器(web.xml)文件来对这些内容进行访问控制;(干货——web.xml又名 配置部署描述器)
0.2)servlet容器是通过一个名为验证器的阀来支持安全限制的,该阀会被添加到Context的管道中,并且会先于 Wrapper阀的调用;(干货——引入了验证器阀)
0.3)调用验证器阀:
case1)用户输入了正确的username 和 pwd:验证器阀会调用后续的阀;
case2)用户输入错误:验证器阀会返回,不会调用后续的阀;
0.4)验证器阀会调用Context容器的领域对象的 authenticate()方法,传入用户输入的username 和 pwd,来对用户进行身份验证;(干货——引入了领域对象)
0.5)for source code , please visit  https://github.com/pacosonTang/HowTomcatWorks/tree/master/chapter10

【1】领域(Realm,用来对用户进行身份验证的组件)
1)intro to 领域对象:该对象用来对用户进行身份验证的组件;
1.1)领域对象通常与一个Context容器相关联(setRealm方法来将领域对象与Context容器相关联);
1.2)领域对象如何验证用户身份呢?它保存了所有有效用户的username 和 pwd对,或者它会访问存储这些dta的存储器;(在tomcat中,有效info 存储在tomcat-user.xml文件中,现在你知道tomcat-user.xml文件的作用是什么了吧)
private void createUserDatabase() {// SimpleRealm.createUserDatabase() 创建了users 列表;User user1 = new User("ken", "blackcomb");user1.addRole("manager");user1.addRole("programmer");User user2 = new User("cindy", "bamboo");user2.addRole("programmer");users.add(user1);users.add(user2);}
2)领域对象:是 org.apache.catalina.Realm 接口的实例,该接口的 authenticate方法最为重要,方法签名如下:
public Principal authenticate (String username, String credentials); // generally used
public Principal authenticate (String username, byte[] credentials);
public Principal authenticate (String username, String digest, String nonce, String nc, String cnonce, String qop, String realm, String md5a2);
public Principal authenticate (X509Certificate cents[]);;
3)在catalina中, Realm接口的基本实现形式是org.apache.catalina.realm.RealmBase类;其UML类图如下:
3.1)default case:会使用 MemoryRealm 类的实例作为验证用的领域对象;(干货——MemoryRealm 类的实例作
为验证默认用的领域对象)
3.2)当第一次调用 MemoryRealm实例时,它会读取 tomcat-user.xml 文档的内容;
Attention)在catalina中, 验证器阀会调用附加到其中的领域对象的 authenticate()方法来验证用户身份;

【2】GenericPrincipal类(实现接口 java.security.Principal)
public class GenericPrincipal implements Principal { //org.apache.catalina.realm.GenericPrincipal,代表一个用户(封装了username,password,role,realm)public GenericPrincipal(Realm realm, String name, String password) {this(realm, name, password, null);}public GenericPrincipal(Realm realm, String name, String password,List roles) {super();this.realm = realm;this.name = name;this.password = password;if (roles != null) {this.roles = new String[roles.size()];this.roles = (String[]) roles.toArray(this.roles);if (this.roles.length > 0)Arrays.sort(this.roles);}}protected String name = null;public String getName() {return (this.name);}protected String password = null; public String getPassword() {return (this.password);}protected Realm realm = null; public Realm getRealm() {return (this.realm);}protected String roles[] = new String[0]; public String[] getRoles() {return (this.roles);}public boolean hasRole(String role) {  // highlight line.if (role == null)return (false);return (Arrays.binarySearch(roles, role) >= 0);}public String toString() {StringBuffer sb = new StringBuffer("GenericPrincipal[");sb.append(this.name);sb.append("]");return (sb.toString());}
}
1)intro to GenericPrincipal:
1.1)GenericPrincipal实例:必须始终与一个领域对象相关联;
1.2)GenericPrincipal实例:必须有一个用户名和密码对;且,该用户名和密码对所对应的角色列表是可选的;
1.3)调用hasRole()方法:传入1个字符串形式的角色名来检查该主体对象是否拥有该指定角色;

【3】LoginConfig类(org.apache.catalina.deploy.LoginConfig)
1)intro to LoginConfig:登录配置是 LoginConfig类的实例,其中包含一个领域对象的名字,其实例封装了领域对象名和所要使用的身份验证方法;(getRealmName()方法用来获取领域对象的名字);
2)getAuthName()方法获取身份验证方法的名字:名字范围是,BASIC, DIGEST, FORM 或 CLIENT-CERT;
3)实际部署中:tomcat在启动时需要读取 web.xml 文件的内容;
step1)如果web.xml 文件包含 login-config 元素的配置,则tomcat会创建一个 LoginConfig对象,并设置其相应属性;(干货——tomcat创建一个 LoginConfig对象的条件)
step2)验证器阀会调用 LoginConfig.getRealmName() 获取领域对象名,并将领域对象名发送到 browser,显示在登录对话框中;
case2.1)如果getReamlName()方法的返回值是null,则会将服务器名和相应端口发送给 browser;
看个荔枝)下图展示了 XP 系统中使用 IE6 进行基本身份验证的登录对话框
public final class LoginConfig { //org.apache.catalina.deploy.LoginConfigpublic LoginConfig() {super();}    public LoginConfig(String authMethod, String realmName,String loginPage, String errorPage) {super();setAuthMethod(authMethod);setRealmName(realmName);setLoginPage(loginPage);setErrorPage(errorPage);}// ------------------------------------------------------------- Properties   private String authMethod = null;public String getAuthMethod() {return (this.authMethod);}public void setAuthMethod(String authMethod) {this.authMethod = authMethod;}private String errorPage = null;public String getErrorPage() {return (this.errorPage);}public void setErrorPage(String errorPage) {        this.errorPage = RequestUtil.URLDecode(errorPage);}    private String loginPage = null;public String getLoginPage() {return (this.loginPage);}public void setLoginPage(String loginPage) {       this.loginPage = RequestUtil.URLDecode(loginPage);}    private String realmName = null;public String getRealmName() {return (this.realmName);}public void setRealmName(String realmName) {this.realmName = realmName;}   public String toString() {StringBuffer sb = new StringBuffer("LoginConfig[");sb.append("authMethod=");sb.append(authMethod);if (realmName != null) {sb.append(", realmName=");sb.append(realmName);}if (loginPage != null) {sb.append(", loginPage=");sb.append(loginPage);}if (errorPage != null) {sb.append(", errorPage=");sb.append(errorPage);}sb.append("]");return (sb.toString());}
}
【4】Authenticator接口(org.apache.catalina.Authenticator)
1)intro to Authenticator:验证器接口只是起到了标记作用,这样其他组件就可以使用 instanceof 来检查某个组件是否是一个验证器;(干货——验证器接口只是起到了标记作用)
2)org.apache.catalina.Authenticator的UML类图如下:

对上图的分析(Analysis):
A1)BasicAuthenticator:用来支持基本的身份验证;
A2)FormAuthenticator:提供了基于表单的身份验证;
A3)DigestAuthenticator:提供了基于信息摘要的身份验证;
A4)SSLAuthenticator:用于对SSL 进行身份验证;
A5)当tomcat 用户没有指定验证方法名时,NonLoginAuthenticator类用于对来访者的身份进行验证。NonLoginAuthenticator类实现的验证器只会检查安全限制,而不会涉及用户身份的验证;
Attention)
A1)验证器的重要工作:是对用户进行身份验证;(干货——验证器的重要工作是对用户进行身份验证)
A2)当看到 AuthenticatorBase.invoke() 方法调用 authenticate() 抽象方法时:后者的实现依赖于子类;(而authenticate()方法会使用基本身份验证来验证用户的身份信息);
public class BasicAuthenticator extends AuthenticatorBase {  <span style="font-family: SimSun;">//org.apache.catalina.authenticator.BasicAuthenticator,这里仅以BasicAuthenticator 为例po出 source code.</span>// ----------------------------------------------------- Instance Variables    protected static final Base64 base64Helper = new Base64();    protected static final String info ="org.apache.catalina.authenticator.BasicAuthenticator/1.0";   public String getInfo() {return (this.info);}public boolean authenticate(HttpRequest request,HttpResponse response,LoginConfig config)throws IOException {// Have we already authenticated someone?Principal principal =((HttpServletRequest) request.getRequest()).getUserPrincipal();String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);if (principal != null) {if (debug >= 1)log("Already authenticated '" + principal.getName() + "'");// Associate the session with any existing SSO sessionif (ssoId != null)associate(ssoId, getSession(request, true));return (true);}// Is there an SSO session against which we can try to reauthenticate?if (ssoId != null) {if (debug >= 1)log("SSO Id " + ssoId + " set; attempting reauthentication");           if (reauthenticateFromSSO(ssoId, request))return true;}// Validate any credentials already included with this requestHttpServletRequest hreq =(HttpServletRequest) request.getRequest();HttpServletResponse hres =(HttpServletResponse) response.getResponse();String authorization = request.getAuthorization();String username = parseUsername(authorization);String password = parsePassword(authorization);principal = context.getRealm().authenticate(username, password);if (principal != null) {register(request, response, principal, Constants.BASIC_METHOD,username, password);return (true);}// Send an "unauthorized" response and an appropriate challengeString realmName = config.getRealmName();if (realmName == null)realmName = hreq.getServerName() + ":" + hreq.getServerPort();    hres.setHeader("WWW-Authenticate","Basic realm=\"" + realmName + "\"");hres.setStatus(HttpServletResponse.SC_UNAUTHORIZED);//      hres.flushBuffer();return (false);}// ------------------------------------------------------ Protected Methodsprotected String parseUsername(String authorization) {if (authorization == null)return (null);if (!authorization.toLowerCase().startsWith("basic "))return (null);authorization = authorization.substring(6).trim();// Decode and parse the authorization credentialsString unencoded =new String(base64Helper.decode(authorization.getBytes()));int colon = unencoded.indexOf(':');if (colon < 0)return (null);String username = unencoded.substring(0, colon);//        String password = unencoded.substring(colon + 1).trim();return (username);}protected String parsePassword(String authorization) {if (authorization == null)return (null);if (!authorization.startsWith("Basic "))return (null);authorization = authorization.substring(6).trim();// Decode and parse the authorization credentialsString unencoded =new String(base64Helper.decode(authorization.getBytes()));int colon = unencoded.indexOf(':');if (colon < 0)return (null);//        String username = unencoded.substring(0, colon).trim();String password = unencoded.substring(colon + 1);return (password);}
}
【5】安装验证器阀
1)一个Context实例:只能有一个 LoginConfig实例和利用一个验证类的实现;
2)也就是说:在部署描述器(tomcat-user.xml)中,login-config元素只出现一次;login-config元素包含一个auth-method 元素来指定身份验证方法;
3)使用AuthenticatorBase类的哪个子类作为Context实例中的验证器阀:依赖于 部署描述器中 auth-method元素;
4)下表给出了 auth-method 元素的值和对应的验证器的类名:

对上表的分析(Analysis):
A1)若没有设置 auth-method元素:则 LoginConfig 对象 的 atuh-method属性的值默认为 NONE,使用 NonLoginAuthenticator 进行安全验证;
A2)由于使用的验证器类是在运行时才确定的,故该类是动态载入的;
A3)StandardContext容器使用 org.apahce.catalina.startup.ContextConfig类来对 StandardContext 实例的属性进行设置:这些设置包括 实例化一个验证器类,并将该实例与Context实例相关联;

【6】应用程序
【6.1】SimpleContextConfig类
1)intro:public class SimpleContextConfig implements LifecycleListener ,仅仅是个监听器而已,不要惊慌;
2)SimpleContextConfig.authenticatorConfig()方法:该方法实例化BasicAuthenticator类,并将其作为阀添加到 StandardContext实例的管道中;(干货——注意其实例化BasicAuthenticator类的技巧
// Identify the class name of the Valve we should configureString authenticatorName = "org.apache.catalina.authenticator.BasicAuthenticator";// Instantiate and install an Authenticator of the requested classValve 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) {}
3)下面对SimpleContextConfig.authenticatorConfig()方法的调用过程 进行 detailed analysis:
step1)先检查在相关联的Context容器是否有安全限制,若没有直接返回,而不会安装验证器;
// Does this Context require an Authenticator?SecurityConstraint constraints[] = context.findConstraints();if ((constraints == null) || (constraints.length == 0))return;
step2)若当前Context容器有一个或多个安全限制,authenticatorConfig() 方法会检查该Context实例是否有 LoginConfig对象。若没有,则它会创建一个新的 LoginConfig实例:
LoginConfig loginConfig = context.getLoginConfig();if (loginConfig == null) {loginConfig = new LoginConfig("NONE", null, null, null);context.setLoginConfig(loginConfig);}
step3)检查管道中的基础阀或附加阀是否是验证器。因为一个Context实例只能有一个验证器,所以当发现某个阀是验证器后,直接返回:
// 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 valvereturn;}
step4)查找当前Context实例是否有与之关联的领域对象(Realm)。如果没有领域对象,就不需要安装验证器了,因为用户是无法通过身份验证的;
// Has a Realm been configured for us to authenticate against?if (context.getRealm() == null) {return;}
step5)若找到了领域对象,则会动态载入 BasicAuthenticator类,创建该类的一个实例,并将其作为阀添加到 StandardContext实例中;
// Identify the class name of the Valve we should configureString authenticatorName = "org.apache.catalina.authenticator.BasicAuthenticator";// Instantiate and install an Authenticator of the requested classValve 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) {}}
【6.2】SimpleRealm类(简单领域对象,领域对象是用来对用户身份验证的组件)
1)SimpleRealm类实现了Realm接口;
2)在构造函数中,它调用了createUserDatabase()方法创建两个用户,并将这两个用户添加到users中;(干货——创建用户,并设置角色,功能同tomcat-users.xml 的内容)
public class SimpleRealm implements Realm {public SimpleRealm() {createUserDatabase(); //highlight line.}private void createUserDatabase() { // there are 2 roles.User user1 = new User("ken", "blackcomb");user1.addRole("manager"); // manager role.user1.addRole("programmer"); // programmer role.User user2 = new User("cindy", "bamboo");user2.addRole("programmer");users.add(user1);users.add(user2);// private ArrayList users = new ArrayList();}
}
3)再看其 authenticate() 验证方法:该方法由验证器调用,若用户提供的用户名或密码无效,则返回null,否则返回一个代表该用户的 Principal对象;
public Principal authenticate(String username, String credentials) {System.out.println("SimpleRealm.authenticate()");if (username==null || credentials==null)return null;User user = getUser(username, credentials);if (user==null)return null;return new GenericPrincipal(this, user.username, user.password, user.getRoles()); // highlight line.}
public class GenericPrincipal implements Principal { // 该类封装了用户的一些信息,如username,pass,role,realm等info;public GenericPrincipal(Realm realm, String name, String password) { this(realm, name, password, null);}    public GenericPrincipal(Realm realm, String name, String password,List roles) {super();this.realm = realm;this.name = name;this.password = password;if (roles != null) {this.roles = new String[roles.size()];this.roles = (String[]) roles.toArray(this.roles);if (this.roles.length > 0)Arrays.sort(this.roles);}}protected String name = null;public String getName() {return (this.name);}protected String password = null;public String getPassword() {return (this.password);}protected Realm realm = null;public Realm getRealm() {return (this.realm);}protected String roles[] = new String[0];public String[] getRoles() {return (this.roles);}       public boolean hasRole(String role) {if (role == null)return (false);return (Arrays.binarySearch(roles, role) >= 0);}    public String toString() {StringBuffer sb = new StringBuffer("GenericPrincipal[");sb.append(this.name);sb.append("]");return (sb.toString());} 
}
【6.3】SimpleUserDatabaseRealm(它是SimpleRealm的变体类)
1)intro:SimpleUserDatabaseRealm类表示一个复杂一点的领域对象,它并不将用户列表存储到对象自身中。相反,它会读取conf 目录下的 tomcat-users.xml文件,将内容载入内存,然后依据该列表进行身份验证。(干货——读取conf 目录下的 tomcat-users.xml文件,将内容载入内存,然后依据该列表进行身份验证
// tomcat-users.xml 的源码如下:
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users><role rolename="tomcat"/><role rolename="role1"/><role rolename="manager"/><role rolename="admin"/><user username="tomcat" password="tomcat" roles="tomcat"/><user username="role1" password="tomcat" roles="role1"/><user username="both" password="tomcat" roles="tomcat,role1"/><user username="admin" password="admin" roles="admin,manager"/>
</tomcat-users>
2)在实例化SimpleUserDatabaseRealm类后, 必须调用它的 createDatabase()方法,并向包含用户列表的xml 文档传递路径。
// 以下代码是 Bootstrap2.java 中的源码Realm realm = new SimpleUserDatabaseRealm();String filedir = new File(System.getProperty("user.dir")).getParent() + File.separator +"conf" + File.separator + "tomcat-users.xml";((SimpleUserDatabaseRealm) realm).createDatabase(filedir); // highlight line.
public void createDatabase(String path) { //  SimpleUserDatabaseRealm.createDatabase().database = new MemoryUserDatabase(name);((MemoryUserDatabase) database).setPathname(path);try {database.open(); // highlight line.}catch (Exception e)  {}}
public void open() throws Exception { // org.apache.catalina.users.MemoryUserDatabase.open().synchronized (groups) {synchronized (users) {// Erase any previous groups and usersusers.clear();groups.clear();roles.clear();// Construct a reader for the XML input file (if it exists)File file = new File(pathname);if (!file.isAbsolute()) {file = new File(System.getProperty("catalina.base"),pathname);}if (!file.exists()) {return;}FileInputStream fis = new FileInputStream(file);// Construct a digester to read the XML input fileDigester digester = new Digester();digester.addFactoryCreate("tomcat-users/group",new MemoryGroupCreationFactory(this));digester.addFactoryCreate("tomcat-users/role",new MemoryRoleCreationFactory(this));digester.addFactoryCreate("tomcat-users/user",new MemoryUserCreationFactory(this));// Parse the XML input file to load this databasetry {digester.parse(fis);fis.close();} catch (Exception e) {try {fis.close();} catch (Throwable t) {;}throw e;}             }        }     }
【6.4】Bootstrap1.java(应用程序1)
1)Bootstrap1.java 的源代码
public final class Bootstrap1 {public static void main(String[] args) {//invoke: http://localhost:8080/Modern or  http://localhost:8080/PrimitiveSystem.setProperty("catalina.base", System.getProperty("user.dir"));System.out.println("user.dir = " + System.getProperty("user.dir"));Connector connector = new HttpConnector();Wrapper wrapper1 = new SimpleWrapper();wrapper1.setName("Primitive");wrapper1.setServletClass("servlet.PrimitiveServlet");Wrapper wrapper2 = new SimpleWrapper();wrapper2.setName("Modern");wrapper2.setServletClass("servlet.ModernServlet");Context context = new StandardContext(); // highlight line.// StandardContext's start method adds a default mappercontext.setPath("/myApp");context.setDocBase("myApp");LifecycleListener listener = new SimpleContextConfig();// highlight line.((Lifecycle) context).addLifecycleListener(listener);context.addChild(wrapper1);context.addChild(wrapper2);// for simplicity, we don't add a valve, but you can add// valves to context or wrapper just as you did in Chapter 6Loader loader = new WebappLoader();context.setLoader(loader);// context.addServletMapping(pattern, name);context.addServletMapping("/Primitive", "Primitive");context.addServletMapping("/Modern", "Modern");// add ContextConfig. This listener is important because it configures// StandardContext (sets configured to true), otherwise StandardContext// won't start// add constraintSecurityCollection securityCollection = new SecurityCollection();// highlight line.securityCollection.addPattern("/");// highlight line.securityCollection.addMethod("GET");// highlight line.SecurityConstraint constraint = new SecurityConstraint();// highlight line.constraint.addCollection(securityCollection);// highlight line.constraint.addAuthRole("manager");// highlight line.LoginConfig loginConfig = new LoginConfig();// highlight line.loginConfig.setRealmName("Simple Realm");// add realmRealm realm = new SimpleRealm();// highlight line.context.setRealm(realm);// highlight line.context.addConstraint(constraint);// highlight line.context.setLoginConfig(loginConfig);// highlight line.connector.setContainer(context);// add a ManagerManager manager = new StandardManager();context.setManager(manager);try {connector.initialize();((Lifecycle) connector).start();((Lifecycle) context).start();// make the application wait until we press a key.System.in.read();((Lifecycle) context).stop();}catch (Exception e) {e.printStackTrace();}}
}
对以上代码的分析(Analysis)
A1)创建StandardContext对象:设置其path属性和 documentBase属性,并添加一个 SimpleContextConfig 类的监听器。该监听器会把一个 BasicAuthenticator 对象安装到 StandardContext 对象中;
A2)创建SecurityColleciton对象:并调用其addPattern和 addMethod方法,addPattern方法指定某个url 要遵循哪个安全限制, 而addMethod方法会指定该安全限制要使用哪种验证方法;在addMethod()方法中设置为GET, 则使用GET 方法提交的http 请求会遵循安全限制;
A3)创建 SecurityManager对象:并将其添加到 安全限制集合中,且还设置了哪种角色可以访问这些受限资源。
A4)创建LoginConfig对象 和 SimpleRealm对象;
A5)将领域对象,安全限制对象,登录配置对象 与 StandardContext实例相关联;
A6)接着,启动Context实例。。。。。。。
Supplement-补充)
S1)本文还是给出了如何验证用户合法性的调用过程




S2)结合S1中的第一张图,本文关联性地给出了 server处理http 客户端请求的调用过程


2)打印结果
<pre name="code" class="java">E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-common
jar;lib/commons-collections.jar;lib/naming-resources.jar;lib/;lib/catalina.jar;E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\webroot com.tomc
t.chapter10.startup.Bootstrap1
HttpConnector Opening server socket on all host IP addresses
HttpConnector[8080] Starting background thread
WebappLoader[/myApp]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\_\myApp
Starting Wrapper Primitive
Starting Wrapper Modern
Added authenticator valve to Context
StandardManager[/myApp]: Seeding random number generator class java.security.SecureRandom
StandardManager[/myApp]: Seeding of random number generator has been completed
StandardManager[/myApp]: IOException while loading persisted sessions: java.io.EOFException 
// 这是从文件中加载 session对象到内存,由于没有相关文件,所以加载失败,抛出异常,但这不会影响我们访问servlet,大家不要惊慌;
java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at com.tomcat.chapter10.startup.Bootstrap1.main(Bootstrap1.java:84)
StandardManager[/myApp]: Exception loading sessions from persistent storage
java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at com.tomcat.chapter10.startup.Bootstrap1.main(Bootstrap1.java:84)
SimpleRealm.authenticate()
SimpleRealm.authenticate()
ModernServlet -- init
SimpleRealm.authenticate()
SimpleRealm.authenticate()
SimpleRealm.authenticate()
SimpleRealm.authenticate()
SimpleRealm.authenticate()
SimpleRealm.authenticate()
init
SimpleRealm.authenticate()
from service
from service

【6.5】Bootstrap2.java(应用程序2)
1)Bootstrap2的源程序和 Bootstrap1.java 的源程序差不多;
public final class Bootstrap2 {public static void main(String[] args) {//invoke: http://localhost:8080/Modern or  http://localhost:8080/PrimitiveSystem.setProperty("catalina.base", System.getProperty("user.dir"));Connector connector = new HttpConnector();Wrapper wrapper1 = new SimpleWrapper();wrapper1.setName("Primitive");wrapper1.setServletClass("servlet.PrimitiveServlet");Wrapper wrapper2 = new SimpleWrapper();wrapper2.setName("Modern");wrapper2.setServletClass("servlet.ModernServlet");Context context = new StandardContext(); // highlight line.// StandardContext's start method adds a default mappercontext.setPath("/myApp");context.setDocBase("myApp");LifecycleListener listener = new SimpleContextConfig();// highlight line.((Lifecycle) context).addLifecycleListener(listener);context.addChild(wrapper1);context.addChild(wrapper2);// for simplicity, we don't add a valve, but you can add// valves to context or wrapper just as you did in Chapter 6Loader loader = new WebappLoader();context.setLoader(loader);// context.addServletMapping(pattern, name);context.addServletMapping("/Primitive", "Primitive");context.addServletMapping("/Modern", "Modern");// add ContextConfig. This listener is important because it configures// StandardContext (sets configured to true), otherwise StandardContext// won't start// add constraintSecurityCollection securityCollection = new SecurityCollection();// highlight line.securityCollection.addPattern("/");// highlight line.securityCollection.addMethod("GET");// highlight line.SecurityConstraint constraint = new SecurityConstraint();// highlight line.constraint.addCollection(securityCollection);// highlight line.constraint.addAuthRole("manager");// highlight line.constraint.addAuthRole("tomcat");// highlight line.注意这里的角色,必要要和tomcat-users.xml 中的users 列表相对应,如果要设置某个用户有权限访问servlet资源,则需要添加其所属的角色;LoginConfig loginConfig = new LoginConfig();// highlight line.loginConfig.setRealmName("Simple User Database Realm");// add realmRealm realm = new SimpleUserDatabaseRealm();// highlight line.String filedir = new File(System.getProperty("user.dir")).getParent() + File.separator + "conf" + File.separator + "tomcat-users.xml"; ((SimpleUserDatabaseRealm) realm).createDatabase(filedir); // highlight line.设置tomcat-users.xml 的文件路径 以载入其内容到内存进行身份验证工作;context.setRealm(realm);// highlight line.context.addConstraint(constraint);// highlight line.context.setLoginConfig(loginConfig);// highlight line.connector.setContainer(context);try {connector.initialize();((Lifecycle) connector).start();((Lifecycle) context).start();// make the application wait until we press a key.System.in.read();((Lifecycle) context).stop();}catch (Exception e) {e.printStackTrace();}}}
// tomcat-users.xml 的源码如下:
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users><role rolename="tomcat"/><role rolename="role1"/><role rolename="manager"/><role rolename="admin"/><user username="tomcat" password="tomcat" roles="tomcat"/><user username="role1" password="tomcat" roles="role1"/><user username="both" password="tomcat" roles="tomcat,role1"/><user username="admin" password="admin" roles="admin,manager"/>
</tomcat-users>
2)它们唯一的差别在于以不同的方式获取用户列表:Bootstrap1.java 是在对象中创建用户列表保存在 ArrayList中;而Bootstrap2.java 通过读取conf目录下的 tomcat-users.xml文件,将其内容载入到内存中;(干货——Bootstrap1.java 和 Bootstrap2.java的唯一差别)(干货——你现在知道 tomcat-users.xml 的作用了,以及如何利用它进行安全性访问的限制)
E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src>java -cp .;lib/servlet.jar;lib/catalina_4_1_24.jar;lib/catalina-5.5.4.jar;lib/naming-common.
jar;lib/commons-collections.jar;lib/naming-resources.jar;lib/;lib/catalina.jar;lib\commons-digester.jar;lib\commons-logging.jar;E:\bench-cluster\cloud
-data-preprocess\HowTomcatWorks\webroot com/tomcat/chapter10/startup/Bootstrap2
HttpConnector Opening server socket on all host IP addresses
HttpConnector[8080] Starting background thread
WebappLoader[/myApp]: Deploying class repositories to work directory E:\bench-cluster\cloud-data-preprocess\HowTomcatWorks\src\work\_\_\myApp
Starting Wrapper Primitive
Starting Wrapper Modern
Added authenticator valve to Context
StandardManager[/myApp]: Seeding random number generator class java.security.SecureRandom
StandardManager[/myApp]: Seeding of random number generator has been completed
StandardManager[/myApp]: IOException while loading persisted sessions: java.io.EOFException // 这是从文件中加载 session对象到内存,由于没有相关文件,所以加载失败,抛出异常,但这不会影响我们访问servlet,大家不要惊慌;
java.io.EOFExceptionat java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at com.tomcat.chapter10.startup.Bootstrap2.main(Bootstrap2.java:84)
StandardManager[/myApp]: Exception loading sessions from persistent storage  
java.io.EOFException  at java.io.ObjectInputStream$PeekInputStream.readFully(Unknown Source)at java.io.ObjectInputStream$BlockDataInputStream.readShort(Unknown Source)at java.io.ObjectInputStream.readStreamHeader(Unknown Source)at java.io.ObjectInputStream.<init>(Unknown Source)at org.apache.catalina.util.CustomObjectInputStream.<init>(CustomObjectInputStream.java:103)at org.apache.catalina.session.StandardManager.load(StandardManager.java:408)at org.apache.catalina.session.StandardManager.start(StandardManager.java:655)at org.apache.catalina.core.StandardContext.start(StandardContext.java:3570)at com.tomcat.chapter10.startup.Bootstrap2.main(Bootstrap2.java:84)
ModernServlet -- init
3)打印结果


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/331210.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SonarQube 8.3.x中的Maven项目的测试覆盖率报告

几年前&#xff0c;我写了一篇博客文章&#xff0c;介绍如何在SonarQube中生成测试报告&#xff0c;该报告独立于单元测试和集成测试的测试报告中。 从SonarQube 6.2开始&#xff0c;测试报告不再在这些类别中分开&#xff08;请参阅SonarQube的博客文章 &#xff09;。 SonarQ…

单例模式懒汉、饿汉和登记

转载自 JAVA设计模式之单例模式本文继续介绍23种设计模式系列之单例模式。 概念&#xff1a;  java中单例模式是一种常见的设计模式&#xff0c;单例模式的写法有好几种&#xff0c;这里主要介绍三种&#xff1a;懒汉式单例、饿汉式单例、登记式单例。  单例模式有以下特点…

量角器中Selenium定位器的完整指南(示例)

在测试网站的功能时&#xff0c;特别是Web元素&#xff08;例如单选按钮&#xff0c;文本框&#xff0c;下拉列表等&#xff09;&#xff0c;您需要确保能够访问这些元素。 Selenium定位器正是出于这个目的&#xff0c;通过使用此命令&#xff0c;我们可以识别这些Web元素DOM&a…

MySQL的自然联结+外部联结(左外连接,右外连接)+内部联结

【0】README0.1&#xff09;本文旨在review MySQL的自然联结外部联结&#xff08;左外连接&#xff0c;右外连接&#xff09;内部联结 的相关知识&#xff1b;【1】自然联结1&#xff09;自然联结定义&#xff1a;无论何时对表进行联结&#xff0c;应该至少有一个列出现不止一个…

MySQL 添加列+修改列+删除列

【0】REAMDE 0.1&#xff09;本文部分文字描述转自 http://blog.163.com/zhangjie_0303/blog/static/99082706201191911653778/ 0.2&#xff09;本文旨在review mysql 对列的相关操作&#xff1a;如添加&#xff0c;修改&#xff0c;删除以及重命名表名等操作&#xff1b; 【1】…

compose应用_带有PostgreSQLDocker Compose for Spring Boot应用程序

compose应用在此博客文章中&#xff0c;您将学习如何使用PostgreSQL配置Spring Boot应用程序以与Docker Compose一起运行。 这篇博客文章涵盖&#xff1a; Spring Boot应用程序Dockerfile配置&#xff0c;在依赖项和资源之间进行了清晰的分离 用于通过PostgreSQL运行应用程序…

单例模式面试题

转载自 单例模式面试题&#xff08;特点、理解&#xff09; (1)单例模式特点&#xff08;什么是单例模式&#xff09;&#xff1f;  a.单例类只能有一个实例。  b.单例类必须自己创建自己的唯一实例。  c.单例类必须给所有其他对象提供这一实例。 (2)单例模式的作用&#x…

MySQL的source命令不加分号和delimiter的使用

【0】README 0.1&#xff09;本文旨在 review source 命令&#xff0c; 这一直是我的痛&#xff0c;为什么一直导入 sql 文件不成功&#xff0c;一直没有写 blog 吧他 记录下来&#xff08;事实上&#xff0c;也间接证明我就是个小白&#xff09;&#xff1b; 0.2&#xff09…

selenium自动化测试_维持Selenium测试自动化的完美方法

selenium自动化测试毫无疑问&#xff0c; 自动浏览器测试已改变了软件开发的工作方式。 如果不是Selenium&#xff0c;我们将无法像我们一样使用各种各样的无错误的Web应用程序。 但是有时&#xff0c;甚至IT部门也误解了自动化一词。 大多数人认为计算机将为他们完成所有测试…

单例模式的优与劣

转载自 大话设计模式(四)单例模式的优与劣前言首先来明确一个问题&#xff0c;那就是在某些情况下&#xff0c;有些对象&#xff0c;我们只需要一个就可以了&#xff0c;比如&#xff0c;一台计算机上可以连好几个打印机&#xff0c;但是这个计算机上的打印程序只能有一个&…

MySQL存储过程+游标+触发器

【0】README0.1&#xff09;本文旨在 arrange mysql 存储过程及如何在存储中使用游标 的相关知识&#xff1b;0.2&#xff09;delimieter的用法&#xff1a;参见 http://blog.csdn.net/pacosonswjtu/article/details/51407756&#xff1b;【1】存储过程基础1&#xff09;intro…

java迭代器退出迭代_使用Java迭代器修改数据时要小心

java迭代器退出迭代随着本学期的结束&#xff0c;我想我会分享一个关于如何非常熟悉Java迭代器的小故事。 现实世界语境 就上下文而言&#xff0c;我开设了第二年软件组件课程&#xff0c;这是尝试进入该专业的学生的最后障碍。 当然&#xff0c;这门课程对学生来说压力很大&a…

pojo 带参构造函数_带有Java Pojo作为输入输出示例的AWS Lambda函数

pojo 带参构造函数在上一个教程中&#xff0c;我们看到了如何使用Java创建AWS Lambda函数&#xff0c;我们传递了String作为输入&#xff0c;还返回了String作为Output。如果您是第一次创建lambda函数&#xff0c;我建议先阅读该教程。 在本教程中&#xff0c;我们将看到如何传…

MySQL检索数据(过滤+通配符+正则表达式)

【0】README0.1&#xff09;本文部分文字描述转自“MySQL 必知必会”&#xff0c;旨在review “MySQL的基础知识”&#xff1b;【1】检索数据1&#xff09;检索单个列&#xff1a;select a_name from table_name;2&#xff09;检索多个列&#xff1a;select a_name,b_name from…

创建者模式

转载自 设计模式之创建者模式创建者模式又叫建造者模式&#xff0c;是将一个复杂的对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程&#xff0c;它把复杂对象的创建过程加以抽象&#xff0c;通过子类继承或者重…

buildpack_使用Buildpack容器化Spring Boot应用程序

buildpack在本文中&#xff0c;我们将看到如何使用Buildpacks容器化Spring Boot应用程序。 在先前的一篇文章中&#xff0c;我讨论了Jib 。 Jib允许我们在不使用Dockerfile的情况下将任何Java应用程序构建为Docker映像。 现在&#xff0c;从Spring Boot 2.3开始&#xff0c;我们…

MySQL创建字段+数据处理函数+汇总数据(聚集函数)+分组数据

【0】README0.1&#xff09;本文部分文字描述转自“MySQL 必知必会”&#xff0c;旨在review“MySQL创建字段数据处理函数汇总数据&#xff08;聚集函数&#xff09;分组数据” 的基础知识&#xff1b;【1】创建计算字段1&#xff09;problemsolution1.1&#xff09;problem&am…

apache.camel_Apache Camel 3.2 – Camel的无反射配置

apache.camel在Apache Camel项目中&#xff0c;我们正在努力开发下一个即将发布的Apache Camel 3.2.0版本。 我们在Camel 3中努力研究的问题之一就是使其变得更小&#xff0c;更快。 其中一个方面是配置管理。 您可以按照12要素原则以多种方式完全配置Camel&#xff0c;以使配…

MySQL数据检索+查询+全文本搜索

【0】README0.1&#xff09;本文部分文字描述转自“MySQL 必知必会”&#xff0c;旨在review“MySQL数据检索查询全文本搜索” 的基础知识&#xff1b;【1】使用子查询1&#xff09;查询定义&#xff1a;任何sql 语句都是查询。但此术语一般指 select语句&#xff1b;SQL 还允许…

selenium自动化测试_49自动化测试中最常见的Selenium异常

selenium自动化测试开发人员将始终在编写代码时牢记不同的场景&#xff0c;但是在某些情况下&#xff0c;实现可能无法按预期工作。 相同的原则也适用于测试代码&#xff0c;该代码主要用于测试现有产品的功能&#xff0c;发现bug并确保产品100&#xff05;不受bug影响。 正确…