一、领域(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>

tomcat源码阅读之安全机制的更多相关文章

  1. Tomcat源码阅读(二)初始化

    近来,我开始阅读tomcat的源码,感觉还挺清晰易懂:为了方便理解,我参考了网上的一些文章,把tomcat的组成归纳一下:整个tomcat的组成如下图所示: Tomcat在接收到用户请求时,将会通过以 ...

  2. tomcat源码阅读

    1      工具准备 需要SVN.Maven.JDK.Eclipse.Eclipse M2插件 2      下载源码及发布包 源码在这里:http://svn.apache.org/repos/a ...

  3. tomcat源码阅读之过滤器

    一.Servlet过滤器: 1.介绍: Servlet过滤器本身并不生成请求和响应对象,它只提供过滤作用. Servlet过滤器能够在Servlet被调用之前检查Request对象,修改Request ...

  4. tomcat源码阅读之SingleThreadModel

    一.接口简介: 实现了SingleThreadModel接口的servlet类只能保证在同一时刻,只有一个线程执行该servlet实例的service方法,在tomcat实现中会创建多个servlet ...

  5. tomcat源码阅读之载入器(Loader)

    一.Java类的载入器: 双亲委派模型: 1.JVM提供了三种类型的类加载器:引导类载入器(bootstrap class loader).扩展类载入器(extension class loader) ...

  6. Python3 源码阅读 - 垃圾回收机制

    Python的垃圾回收机制包括了两大部分: 引用计数(大部分在 Include/object.h 中定义) 标记清除+隔代回收(大部分在 Modules/gcmodule.c 中定义) 1. 引用计数 ...

  7. tomcat源码阅读之BackupManager

    一. 配置: <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOpti ...

  8. tomcat源码阅读之集群

    一. 配置: 在tomcat目录下的conf/Server.xml配置文件中增加如下配置: <!-- Cluster(集群,族) 节点,如果你要配置tomcat集群,则需要使用此节点. clas ...

  9. tomcat源码阅读之StandardContext

    Context实例表示一个具体的web应用程序,其中包含一个或者多个Wrapper实例,每个Wrapper表示一个具体的servlet定义.StandardContext类是Context接口的标准实 ...

随机推荐

  1. python-day21--os模块

     os模块是与操作系统交互的一个接口''' os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径 os.chdir("dirname") 改变当前脚本工作 ...

  2. ajax中文乱码问题的总结

    ajax中文乱码问题的总结 2010-12-11 22:00 5268人阅读 评论(1) 收藏 举报 ajaxurljavascriptservletcallback服务器 本章解决在AJAX中常见的 ...

  3. poj2895

    题解: splay,维护当前第k大 并查集维护当前集合 合并x,y时,del(num[x]),del(num[y]),insert(num[x]+num[y]) 代码: #include<cst ...

  4. oracle查询在当前数据库下当前用户拥有的表语句

    1.查询表的数目: select count(*) from tabs select count(*) from user_tables 2.查询用户拥有哪些表: select * from tabs ...

  5. Delphi中的文件扩展名

    Filename Extensions in Delphi http://delphi.about.com/od/beginners/a/aa032800a.htm Try building a sm ...

  6. CDMA LTE FAQ2

    1.UE等级 LTE CAT4,应该指的是LTE Category4,字面意思是LTE的ue-Category设置为4.ue-Category指的是UE的接入能力等级.也就是UE能够支持的传输速率的等 ...

  7. node 常见的一些系统问题

    nodde正风生火起,很多介绍却停留在入门阶段,无法投入生产 许多文章在讲第三方类库,可是这些库质量差距较大,一旦遇到问题怎么办 全面了解node核心才能成为一名合格的node开发人员 1. node ...

  8. SWIFT Scan QRCode

    SWIFT中扫描QRCode代码如下,照着敲一次再看下API的注释应该就没问题了. import UIKit import Foundation import AVFoundation class V ...

  9. 无法访问 MemoryStream 的内部缓冲区

    无法访问 MemoryStream 的内部缓冲区 在处理剪贴板数据时, ms.GetBuffer() 语句出现异常,代码如下: //检索当前位于系统剪贴板中的数据 IDataObject ido = ...

  10. PAT 数列求和-加强版   (20分)(简单模拟)

    给定某数字A(1≤A≤9)以及非负整数N(0≤N≤100000),求数列之和S=A+AA+AAA+⋯+AA⋯A(N个A).例如A=1, N=3时,S=1+11+111=123 输入格式: 输入数字A与 ...