tomcat(10)安全性
生活随笔
收集整理的這篇文章主要介紹了
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)打印結果
【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)打印結果
總結
以上是生活随笔為你收集整理的tomcat(10)安全性的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何在工作繁重、睡眠较少的情况下保持旺盛
- 下一篇: 电脑怎么进行屏幕检测(怎么检查电脑屏幕)