当前位置: 首页 > 科技观察

Tomcat容器的安全认证与鉴权

时间:2023-03-13 06:11:56 科技观察

大量的Web应用都有安全相关的需求。出于这个原因,Servlet规范建议容器具有满足这些要求的机制和基础设施,因此容器必须支持以下安全特性:身份验证:验证授权用户的用户名和密码资源访问控制:将某些资源的访问限制为仅某些用户数据完整性:能够证明数据在传输过程中未被第三方修改机密性或数据隐私:传输加密(SSL)以确保信息只能由受信任的用户访问。本文分析了Tomcat容器提供的鉴权认证的设计与实现,以及内部单点登录的原理。首发于微信公众号顿悟源码。1.授权容器和Web应用采用基于角色的权限访问控制方式,其中容器需要实现鉴权和鉴权功能,而Web应用需要实现授权功能。Servlet规范中描述了两种类型的授权:声明性安全性和程序性安全性。声明式安全是在部署描述符中声明角色、资源访问权限和认证方式。以下代码片段摘自Tomcat自带的Manager应用的web.xml:HTMLManagercommands/html/*manager-guiBASICTomcatManagerApplicationdescription>TherolethatisrequiredtoaccesstheHTMLManagerpagesmanager-gui这些与安全相关的配置将在部署应用程序时初始化并设置为StandardContext对象。更详细的内容请参考规范对部署描述文件的解释,然后看看Tomcat是如何设计和实现鉴权认证的。2.认证和授权的设计虽然Servlet规范描述了Web应用声明安全约束的机制,但是并没有定义容器与关联的用户和角色信息之间的接口。因此,Tomcat定义了一个Realm接口,用于适配各种信息源进行认证。整体设计的类图如下:上图中包含了各个类的核心方法,关键类或接口的作用如下:Realm——翻译成领域,领域一般指一定的范围,用户存储在这个范围内的名称、密码、角色和权限,并提供身份和权限验证的功能。通常,这个作用域可以是一个配置文件或数据库CombinedRealm——里面包含一个或多个Realms,并按照配置的顺序进行认证。AnyRealm认证如果成功,则表示认证成功。LockOutRealm-提供用户锁定机制以防止在特定时间段内进行过多的失败身份验证尝试。Authenticator-不同认证方式的接口,主要有BASIC、DIGEST、FORM、SSL等。Principal-认证主体的抽象,包含用户身份和权限信息SingleSignOn-用于支持容器内多个应用的??单点登录功能2.1InitializationRealm是容器的可嵌套组件,可以嵌套在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有两个valve,BasicAuthenticator和StandardContextValve。当请求进入Context管道时,首先进行鉴权和授权,方法调用如下:整个过程的核心代码在AuthenticatorBase的invoke方法中:publicvoidinvoke(Requestrequest,Responseresponse)throwsIOException,ServletException{LoginConfigconfig=this.context.getLoginConfig();//0.Session对象是否缓存一个认证过的Principalif(cache){Principalprincipal=request.getUserPrincipal();if(principal==null){Sessionsession=request.getSessionInternal(false);if(session!=null){principal=session.getPrincipal();if(principal!=null){request.setAuthType(session.getAuthType());request.setUserPrincipal(principal);}}}}//对于基于表单的登录,它可能位于特殊的安全域之外{return;}}//获取安全域对象,默认配置onisLockOutRealmRealmrealm=this.context.getRealm();//根据请求URI尝试尝试获取配置的安全约束/){//为null表示访问的资源没有安全约束,直接访问下一个valvegetNext().invoke(request,response);return;}//确保受限资源不会被web代理或浏览器缓存,因为缓存可能会造成安全漏洞response.setHeader("Pragma","No-cache");response.setHeader("Cache-Control","no-cache");}else{response.setHeader("Cache-Control","private");}response.setHeader("Expires",DATE_ONE);}inti;//1.CheckTransportsecurityconstraintsforuserdataif(!realm.hasUserDataPermission(request,response,constraints)){//Authenticationfailed//Authenticatorhasset相应的HTTP状态码,所以我们不用做任何特殊的return;}//2.检查是否包含授权约束,即角色验证booleanauthRequired=true;for(i=0;i...