设为首页 - 加入收藏 天门站长网 (http://www.0728zz.com)- 国内知名站长资讯网站,提供最新最全的站长资讯,创业经验,网站建设等!
热搜: 市场 全新 业务 数据
当前位置: 首页 > 曾道人上下策 > 外闻 > 正文

Tomcat 容器的安全认证和鉴权

发布时间:2019-05-21 21:17 所属栏目:[外闻] 来源:顿悟源码
导读:大量的 Web 应用都有安全相关的需求,正因如此,Servlet 规范建议容器要有满足这些需求的机制和基础设施,所以容器要对以下安全特性予以支持: 身份验证:验证授权用户的用户名和密码 资源访问控制:限制某些资源只允许部分用户访问 数据完整性:能够证明

大量的 Web 应用都有安全相关的需求,正因如此,Servlet 规范建议容器要有满足这些需求的机制和基础设施,所以容器要对以下安全特性予以支持:

Tomcat 容器的安全认证和鉴权

  • 身份验证:验证授权用户的用户名和密码
  • 资源访问控制:限制某些资源只允许部分用户访问
  • 数据完整性:能够证明数据在传输过程中未被第三方修改
  • 机密性或数据隐私:传输加密(SSL),确保信息只能被信任用户访问

本文就以上问题,对 Tomcat 容器提供的认证和鉴权的设计与实现,以及内部单点登录的原理进行分析。首发于微信公众号顿悟源码.

1. 授权

容器和 Web 应用采用的是基于角色的权限访问控制方式,其中容器需要实现认证和鉴权的功能,而 Web 应用则要实现授权的功能。

在 Servlet 规范中描述了两种授权方式:声明式安全和编程式安全。声明式安全就是在部署描述符中声明角色、资源访问权限和认证方式。以下代码片段摘自 Tomcat 自带的 Manager 应用的 web.xml:

  1. ??
  2. ????
  3. ????HTML?Manager?commands?
  4. ????/html/*?
  5. ???
  6. ???
  7. ?????manager-gui?
  8. ???
  9. ?
  10. ?
  11. ?
  12. ??BASIC?
  13. ??Tomcat?Manager?Application?
  14. ?
  15. ?
  16. ?
  17. ???
  18. ????The?role?that?is?required?to?access?the?HTML?Manager?pages?
  19. ???
  20. ??manager-gui?
  21. ?

这些安全相关的配置,都会在应用部署时,初始化和设置到 StandardContext 对象中。更多详细的内容可查看规范对部署描述文件的解释,接下来看 Tomcat 怎么设计和实现认证及鉴权。

2. 认证和鉴权的设计

Servlet 规范虽然描述了 Web 应用声明安全约束的机制,但没有定义容器与关联用户和角色信息之间的接口。因此,Tomcat 定义了一个 Realm 接口,用于适配身份验证的各种信息源。整体设计的类图如下:

Tomcat 容器的安全认证和鉴权

上图中,包含了各个类的核心方法,关键类或接口的作用如下:

  • Realm - 译为域,域有泛指某种范围的意思,在这个范围内存储着用户名、密码、角色和权限,并且提供身份和权限验证的功能,典型的这个范围可以是某个配置文件或数据库
  • CombinedRealm - 内部包含一个或多个 Realm,按配置顺序执行身份验证,任一 Realm 验证成功,则表示成功验证
  • LockOutRealm - 提供用户锁定机制,防止在一定时间段有过多身份验证失败的尝试
  • Authenticator - 不同身份验证方法的接口,主要有 BASIC、DIGEST、FORM、SSL 这几种标准实现
  • Principal - 对认证主体的抽象,它包含用户身份和权限信息
  • SingleSignOn - 用于支持容器内多应用的单点登录功能

2.1 初始化

Realm 是容器的一个可嵌套组件,可以嵌套在 Engine、Host 和 Context 中,并且子容器可以覆盖父容器配置的 Realm。默认的 server.xml 在 Engine 中配置了一个 LockOutRealm 组合域,内部包含一个 UserDatabaseRealm,它从配置的全局资源 conf/tomcat-users.xml 中提取用户信息。

web.xml 中声明的安全约束会初始化成对应的 SecurityConstraint、SecurityCollection 和 LoginConfig 对象,并关联到一个 StandardContext 对象。

在上图可以看到,AuthenticatorBase 还实现了 Valve 接口,StandardContext 对象在配置的过程中,如果发现声明了标准的验证方法,那么就会把它加入到自己的 Pipeline 中。

3. 一次请求认证和鉴权过程

Context 在 Tomcat 内部就代表着一个 Web 应用,假设配置使用 BASIC 验证方法,那么 Context 内部的 Pipeline 就有 BasicAuthenticator 和 StandardContextValve 两个阀门,当请求进入 Context 管道时,就首先进行认证和鉴权,方法调用如下:

Tomcat 容器的安全认证和鉴权

整个过程的核心代码就在 AuthenticatorBase 的 invoke 方法中:

  1. public?void?invoke(Request?request,?Response?response)?throws?IOException,?ServletException?{?
  2. ??LoginConfig?config?=?this.context.getLoginConfig();?
  3. ??//?0.?Session?对象中是否缓存着一个已经进行身份验证的?Principal?
  4. ??if?(cache)?{?
  5. ????Principal?principal?=?request.getUserPrincipal();?
  6. ????if?(principal?==?null)?{?
  7. ??????Session?session?=?request.getSessionInternal(false);?
  8. ??????if?(session?!=?null)?{?
  9. ????????principal?=?session.getPrincipal();?
  10. ????????if?(principal?!=?null)?{?
  11. ??????????request.setAuthType(session.getAuthType());?
  12. ??????????request.setUserPrincipal(principal);?
  13. ????????}?
  14. ??????}?
  15. ????}?
  16. ??}?
  17. ??//?对于基于表单登录,可能位于安全域之外的特殊情况进行处理?
  18. ??String?contextPath?=?this.context.getPath();?
  19. ??String?requestURI?=?request.getDecodedRequestURI();?
  20. ??if?(requestURI.startsWith(contextPath)?&&?requestURI.endsWith(Constants.FORM_ACTION))?{?
  21. ??????????return;?
  22. ??????}?
  23. ??}?
  24. ??//?获取安全域对象,默认配置是?LockOutRealm?
  25. ??Realm?realm?=?this.context.getRealm();?
  26. ??//?根据请求?URI?尝试获取配置的安全约束?
  27. ??SecurityConstraint?[]?constraints?=?realm.findSecurityConstraints(request,?this.context);?
  28. ??
  29. ??if?((constraints?==?null)?/*?&&?(!Constants.FORM_METHOD.equals(config.getAuthMethod()))?*/?)?{?
  30. ????//?为?null?表示访问的资源没有安全约束,直接访问下一个阀门?
  31. ????getNext().invoke(request,?response);?
  32. ????return;?
  33. ??}?
  34. ??//?确保受约束的资源不会被?Web?代理或浏览器缓存,因为缓存可能会造成安全漏洞?
  35. ??if?(disableProxyCaching?&&??
  36. ??????!"POST".equalsIgnoreCase(request.getMethod()))?{?
  37. ??????if?(securePagesWithPragma)?{?
  38. ??????????response.setHeader("Pragma",?"No-cache");?
  39. ??????????response.setHeader("Cache-Control",?"no-cache");?
  40. ??????}?else?{?
  41. ??????????response.setHeader("Cache-Control",?"private");?
  42. ??????}?
  43. ??????response.setHeader("Expires",?DATE_ONE);?
  44. ??}?
  45. ??int?i;?
  46. ??//?1.?检查用户数据的传输安全约束?
  47. ??if?(!realm.hasUserDataPermission(request,?response,?constraints))?{?
  48. ????//?验证失败?
  49. ????//?Authenticator已经设置了适当的HTTP状态代码,因此我们不必做任何特殊的事情?
  50. ????return;?
  51. ??}?
  52. ??//?2.?检查是否包含授权约束,也就是角色验证?
  53. ??boolean?authRequired?=?true;?
  54. ??for(i=0;?i?
  55. ????if(!constraints[i].getAuthConstraint())?{?
  56. ??????authRequired?=?false;?
  57. ????}?else?if(!constraints[i].getAllRoles())?{?
  58. ??????String?[]?roles?=?constraints[i].findAuthRoles();?
  59. ??????if(roles?==?null?||?roles.length?==?0)?{?
  60. ????????authRequired?=?false;?
  61. ??????}?
  62. ????}?
  63. ??}?
  64. ??//?3.?验证用户名和密码?
  65. ??if(authRequired)?{?
  66. ????//?authenticate?是一个抽象方法,由不同的验证方法实现?
  67. ????if?(!authenticate(request,?response,?config))?{?
  68. ??????return;?
  69. ????}??
  70. ??}?
  71. ??//?4.?验证用户是否包含授权的角色?
  72. ??if?(!realm.hasResourcePermission(request,?response,constraints,this.context))?{?
  73. ????return;?
  74. ??}?
  75. ??//?5.?已满足任何和所有指定的约束?
  76. ??getNext().invoke(request,?response);?
  77. }?

【免责声明】本站内容转载自互联网,其相关言论仅代表作者个人观点绝非权威,不代表本站立场。如您发现内容存在版权问题,请提交相关链接至邮箱:bqsm@foxmail.com,我们将及时予以处理。

网友评论
推荐文章