zoukankan      html  css  js  c++  java
  • tomcat源码阅读之安全机制

    一、领域(Realm):

    1、Principal接口代表角色信息,包含了三个成员:用户名、密码、role列表(以逗号分隔),对应了tomcat-users.xml文件中一行user信息:

     

    GenericPrincipal作为Principal接口的默认实现类,提供了hasRole函数,通过这个函数可以判断该角色是否支持指定的role;

    2、Realm接口代表领域对象,是一个用来对用户进行身份验证的组件;可以认为Realm是Principal的管理器,包含了用户角色的集合;每个Realm对象都会与一个context容器对象相关联;hasRole方法判断指定的用户角色是否支持指定的role,authenticate用来判断进行用户身份验证,该方法重载了四个实现方法,其中最重要和最常用的是用户名和密码验证

    3、Realm接口的基本实现形式是RealmBase类,这是一个抽象类,提供了具体领域实现类的相同操作部分,不同部分由具体子类自己去实现,默认情况下会使用MemoryRealm类的实例作为验证用的领域对象;

    4、当第一次调用MemoryRealm实例时,它会读取tomcat-users.xml文件的内容,创建Digester对象读取:

     

    可以看到读取配置文件时使用了MemoryRuleset规则对象,在MemoryRuleset对象读取时:

     

    可以看到该规则会读取tomcat-users/下的username, password, roles到一个GenericPrincipal对象中并添加到领域对象Realm中;

    二、安全验证机制:

     

    • loginConfig是登录配置类,loginConfig实例封装了领域对象的名字和身份验证方法,身份验证方法有:BASIC, FORM, DIGEST和CLIENT-CERT;如果身份验证方法是FORM,还需要指定loginpage和errorPage属性;
    • loginConfig实例会读取web项目的WEB-INF/web.xml文件里面的login-config配置,比如在web.xml项目里添加如下配置,则在浏览器中访问该项目时会弹出身份验证的对话框:
        <security-constraint>
    	  <web-resource-collection>		
    		 <web-resource-name>MySecurityTest</web-resource-name>
    		 <url-pattern>/*</url-pattern>
    	  </web-resource-collection>
    	  <auth-constraint>
    		 <role-name>admin-gui</role-name>
    		 <role-name>manager-gui</role-name>
    	  </auth-constraint>
       </security-constraint>
    
       <login-config>
    	    <auth-method>BASIC</auth-method>
    	    <realm-name>MySecurityTest</realm-name>
       </login-config>
    

      

      1)  web-resource-collection:

      此元素确定应该保护的资源,所有security-constraint元素都必须包含至少一个web-resource-collection项.此元素由一个给出任意标识名称的web-resource-name元素、一个确定应该保护URL    的url-pattern元素、一个指出此保护所适用的HTTP命令(GET、POST等,缺省为所有方法)的http-method元素和一个提供资料的可选description元素组成。

      2)   auth-constraint:

      指出哪些用户应该具有受保护资源的访问权。此元素应该包含一个或多个标识具有访问权限的用户类别role-name元素,以及包含(可选)一个描述角色的description元素。

      3)   user-data-constraint:

      这是个可选的元素,指出在访问相关资源时使用任何传输层保护。它必须包含一个transport-guarantee子元素(合法值为NONE、INTEGRAL或CONFIDENTIAL),并且可选地包含一个description元素。transport-guarantee为NONE值将对所用的通讯协议不加限制。INTEGRAL值表示数据必须以一种防止截取它的人阅读它的方式传送。虽然原理上(并且在未来的HTTP版本中),在INTEGRAL和CONFIDENTIAL之间可能会有差别,但在当前实践中,他们都只是简单地要求用SSL。

      4)   login-config:

      auth-method指出了认证的方法,一共有四种:BASIC,FORM,DIGEST,CLIENT-CERT,realm-name表示域名,对于FORM的认证方式还需要指定loginpage和errorpage,对于下面的form表单:

    <form name="loginform" method="post" action="j_security_check">   
    
        <INPUT name="j_username" type="text">   
    
        <INPUT name="j_password" TYPE="password">   
    
        <input type="submit" value="登 录" >   
    
    </form> 

      form 的action 必须是j_security_check, method="post", 用户名 name="j_username" , 密码name="j_password"  这些都是固定的元素;

    • loginConfig类对应上面xml配置文件里面的login-config项,SecurityCollection类对应配置文件里面的web-resource-collection,SecurityConstraint对应配置文件里面的security-constraint(web-resource-collection配置是作为security-constraint的子配置项存在的),将配置文件解析到对应的对象里面是通过Digester对象解析,解析时的规则类是WebRuleSet类;
    • Authenticator是代表验证的接口,这个只是一个空的接口,起到一个标识的作用,AuthenticatorBase实现了这个接口,实现了不同验证方式的公共代码,不同的验证方式均是从这个类派生出子类来实现的;这个类同时也是一个阀(valve),他会安装到context容器的pipeline列表中,这样在访问web项目时会调用pipeline.invoke,也就是会调用验证阀进行身份验证;
    • 安全认证访问流程:

        1)   standardcontext.start():调用lifecycle.fireLifecycleEvent(START_EVENT, null);发送START_EVENT事件;

        2)   ContextConfig接收到start事件后调用start方法;

        3)   Start方法中调用ContextConfig.authenticatorConfig方法,加载BasicAuthenticator对象并添加到context.pipeline.valves[]中;

        4)   当有连接请求到达时,会调用HttpProcessor.parseHeaders方法,里面header里面有authorization字段,则设置request.authorization标记为true;

        5)Context.invoke方法会调用pipeline.invoke,然后会调用StandardPipelineValveContext.invokeNext方法,在这里面会调用到验证阀AuthenticatorBase.invoke方法,其中代码段如下:

          

          这里的authenticate方法会调用不同验证子类的authenticate方法来验证;

    • BASIC验证:

        BASIC的验证方式相对不是很安全,验证字符串以“Authorization: Basic username:password”的形式发送,其中username:password以base64编码的形式存在,如果被截获后将这个字符串进行base64解码,即可查看到用户名和密码明文(如果请求是在安全的SSL层上,则BASIC验证方式依然是安全的);

        认证规则如下:

          1)   客户端访问受保护的资源;

          2)   服务器返回401 Unauthorized状态,响应头信息如下图所示,其中WWW-Authenticate:Basic realm="MyRealm"表示该资源的受保护信息。

          3)   浏览器根据响应弹出窗口,提示用户输入用户名和密码。

          4)   浏览器将客户端将输入的用户名、密码用Base64算法进行加密后发送给服务器。例如,使用用户名、密码都是“java”进行登录,浏览器则发送的请求头中包含“Authorization: Basic amF2YTpqYXZh”,其中“amF2YTpqYXZh”是用户名、密码组成的字符串“java:java”进行Base64加密得到的结果。

          5)   如果认证成功,则返回相应的受保护资源。如果认证失败,则仍返回401 Unauthorized状态,要求重新进行认证。

    • DIGEST验证:

        Digest认证提供了一种不使用明文发送用户名密码的方式。Digest认证的规则如下:    

          1)   客户端访问受保护的资源。

          2)   服务器返回401 Unauthorized状态,响应头信息如下图所示,其中

            WWW-Authenticate:Digest realm="MyRealm", qop="auth", nonce="1454307975468:a0aefce3e84d69723e6f04fda5674ad0", opaque="23BB4CB60BFE2CD08B490A16B86C9661"

            表示相关的安全域信息、随机数信息(nonce)等。

          3)   浏览器根据响应弹出窗口,提示用户输入用户名和密码。

          4)   浏览器将客户端将输入的用户名以明文的方式、密码等其他信息以摘要的方式返回给服务端。

          5)   服务端将用户名、正确的密码等信息按规则进行摘要加密,与客户端提供的信息进行比对。如果认证成功,则返回相应的受保护资源。如果认证失败,则仍返回401 Unauthorized状态,要求重新进行认证。

    • FORM验证:    

        Basic和Digest认证由于其自身的设计,各浏览器的实现都是弹出一个无所谓美观的对话框,对用户体验有很大的影响。Form认证中定义了采集用户信息的登录页面、登录失败页面,通过用户自定义实现这两个页面,能够完成美观的登录操作。在web.xml中配置Form认证方式及登录页面示例如下:

    <login-config>
    
        <auth-method>FORM</auth-method>
    
        <realm-name>file</realm-name>
    
        <form-login-config>
    
            <form-login-page>/login.xhtml</form-login-page>
    
            <form-error-page>/error.xhtml</form-error-page>
    
        </form-login-config>
    
    </login-config>  

      在Servlet规范中规定,使用Form认证时,表单提交的action必须为j_security_check,而获取登录信息的字段必须为j_username和j_password。

      认证流程如下:      

        1)   查看是否已经对当前用户进行了认证,避免重复认证造成资源浪费:checkForCachedAuthentication(request, response, true)。

        2)   如果没有认证,则需要保存当前用户需要保存的页面:saveRequest(request, session),然后跳转到登录页面:forwardToLoginPage(request, response, config)。

        3)   用户提交了用户名和密码,进行认证工作:principal = realm.authenticate(username, password);如果认证失败,则跳转至失败页面:forwardToErrorPage(request, response, config);如果认证成功,则跳转至第二步保存的页面:response.sendRedirect(response.encodeRedirectURL(uri))。

        4)   浏览器接收到302重定向状态码后,将页面跳转至最初访问的页面。

        5)   再次走进Form认证器的认证流程,通过判断条件matchRequest(request)将认证主体(Principal)保存在Request和Session中,判断条件为:已经通过了认证;存在一个已保存的页面且与当前请求页面路径相同。然后将本次请求的所有信息都重置为最初的请求信息:restoreRequest(request, session)。此后的访问在第一步即直接返回了。

    • CLIENT-CERT验证:    

        Client认证依赖于HTTPS,因此是Java EE安全规范中安全性最高的一种认证方式。使用Client认证需要在web.xml中配置如下:

        <login-config>

            <auth-method>CLIENT-CERT</auth-method>

        </login-config>

  • 相关阅读:
    百度云管家开机启动如何取消
    双语小说阅读:《谁动了我的奶酪》
    [Swift]方法
    Swift中的类型属性(静态变量)
    Swift 学习之二十一:?和 !(详解)
    苹果Swift可为Windows/Android开发软件了
    iOS7下滑动返回与ScrollView共存二三事
    swift c++ oc 混编
    RTOS
    STM32 RTC
  • 原文地址:https://www.cnblogs.com/laoxia/p/7976822.html
Copyright © 2011-2022 走看看