Servlet 3.0 之 安全

2016-03-02  本文已影响1127人  Lucky_Micky

Web应用被应用开发者创建,这些开发者给予、卖应用,或者为了安装到一个运行时环境,把应用转移应用到一个部署者。应用开发者会把安全要求传达给部署者和部署系统。这个信息可能会通过应用的部署描述符或者在应用代码里使用注解明确地表达。
本章为了表达应用的安全需求,描述了Servlet容器安全机制、接口、部署描述符和注解机制。

一、介绍

一个web应用包含能被很多用户访问的资源。这些资源通常在没有保护和开放网络上传输,如Internet。在这样的环境中,大量web应用将会有安全需求。
虽然质量保障和实现细节可能不同,servlet容器有机制和基础设施来满足共享下列特征的需求:

二、声明式安全

声明式安全指表达一个应用安全模型或者需求的方式,包括角色,访问控制以及在一个应用外部形势的授权要求。部署描述符在web应用中是主要的工具。
开发者把应用的逻辑安全需求映射到一个与具体运行时环境相关的安全策略代表上。在运行时中,servlet容器使用安全策略代表来强制认证和授权。
安全模型应用在web应用中的静态内容部分和被客户端请求的应用中的servlets和filters上。当一个servlet使用RequestDispatcher调用一个静态资源或者使用一个forward或者includeservlet时,安全模型并不会应用生效。

三、编程式安全

当声明式安全不足以表达应用的安全模型时,编程式安全被安全意识应用使用。编程式安全由HttpServletRequest接口的下列方法组成:
* authenticate
* login
* logout
* getRemoteUser
* isUserInRole
* getUserPrincipal

login方法允许一个应用执行用户名和密码集合(还有一种基于表单的登录)。authenticate方法允许一个应用从一个没有限制的请求上下文中激起请求调用者的认证。
logout方法允许一个应用重置一个请求的调用身份。
getRemote方法返回与请求关联的调用者名字。
isUserPrincipal方法决定与请求关联的调用者是否是一个指定的安全角色。
getUserPrincipal方法决定了远端调用者的主要名字,并且返回一个与远端调用者对应的java.security.Principal对象。在getUserPrincipal返回的Principal上调用getName方法会返回一个远端调用者的名字。这些APIs允许servlets基于获取的信息来决定业务逻辑。
如果没有用户已经被授权,getRemoteUser方法返回null,isUserInRole方法总会返回false,并且getUserPrincipal方法返回null。
isUserInRole方法期望一个表示用户角色名字的字符串参数。security-role-ref元素应该在部署描述符中被声明,并且有一个包含传递给方法的角色名字的role-name子元素。一个security-role-ref元素应该包含一个角色链接子元素,它的值是用户被映射到的安全角色的名字。当决定调用的返回值时,容器使用security-role-refsecurity-role的映射。
比如,把安全角色引用FOO映射到名字为manager的安全角色。语法如下
<security-role-ref>
<role-name>FOO</role-name>
<role-link>manager</role-link>
</security-role-ref>
这个例子中,如果被属于manager安全角色的用户调用的servlet调用API方法isUserInRole,结果将会是true。
如果没有匹配一个security-rolesecurity-role-ref元素已经被声明,容器必须默认对web应用的security-role元素列表检查role-name元素参数。isUserInRole方法参考这个列表来决定调用者是否被映射到一个安全角色。开发者必须意识到这个默认机制的使用在改变应用中角色名字的时候限制灵活性,改变角色名字不必重新编译servlet来调用。

四、编程式访问控制注解

本节定义了注解和apis,用来配置Servlet容器强制的安全限制。

  1. @ServletSecurity Annotation
    @ServletSecurity注解为定义访问控制限制提供了一种替代机制,这等价于通过可移植部署描述符中的security-constraint元素声明或者通过ServletRegistration接口的setServletSecurity方法编程式声明。Servlet容器必须支持在实现了javax.servlet.Servlet 接口的类上使用@ServletSecurity注解。
    package javax.servlet.annotation;
    @Inherited
    @Documented
    @Target(value=TYPE)
    @Retention(value=RUNTIME)
    public @interface ServletSecurity {
    HttpConstraint value();
    HttpMethodConstraint[] httpMethodConstraints();
    }
    Table 1-1 ServletSecurity Interface
Element 描述 默认
value HttpConstraint定义了应用于所有没出现在httpMethodConstraints返回数组中的HTTP方法的保护。 @HttpConstraint
httpMethodConstraints HTTP方法具体限制的数组 {}

@HttpConstraint
@HttpConstraint注解在@ServletSecurity注解中被使用来代表应用于所有HTTP协议方法的安全限制,一个对应的@HttpMethodConstraint没有为上述方法出现在@ServletSecurity注解中。

package javax.servlet.annotation
@Documented
@Retention(value=RUNTIME)
public @interface HttpConstraint {
  ServletSecurity.EmptyRoleSemantic value();
  java.lang.String[] rolesAllowed();
  ServletSecurity.TransportGuarantee transportGuarantee();
}

Table 1-2 HttpConstraint接口

Element 描述 默认
value 默认的授权语义,仅当rolesAllowed返回空数组时应用。 PERMIT
rolesAllowed 包含授权角色名字的一个数组 {}
transportGuarantee 数据保护要求,必须被请求到达的连接上被满足 NONE

@HttpMethodConstraint
@HttpMethodConstraint注解在具体HTTP协议消息上代表安全限制的@ServletSecurity注解中被使用。

package javax.servlet.annotation
@Documented
@Retention(value=RUNTIME)
public @interface HttpMethodContraint {
  ServletSecurity.EmptyRoleSemantic value();
  java.lang.String[] rolesAllowed();
  ServletSecurity.TransportGuarantee transportGuarantee();
}
Element 描述 默认
value HTTP协议方法名字
emptyRoleSemantic 默认授权机制,仅当rolesAllowed返回空字符串时应用 PERMIT
rolesAllowed 包含已授权角色名字的一个数组 {}
transportGuarantee 数据保护需求,必须被请求到达的连接满足 NONE

@ServletSecurity注解可以在一个Servlet实现类上被指明,并且它的值被为根据元注解@Inherited规则定义的子类继承。@ServletSecurity注解的一个实例可以出现在一个Servlet实现类上面,并且@ServletSecurity注解一定不能在一个Java方法上被指定。
当一个或者多个@HttpMethodConstraint注解在一个@ServletSecurity注解里面被定义,每个@HttpMethodConstraint定义了security-constraint,它应用在所有HTTP协议方法上,而不是那些一个对应HttpMethodConstraint@ServletSecurity注解中被定义的方法上。
定义在可移植部署描述符中的security-constraint元素对所有出现在限制里的url-patterns的有权限。
当在可移植部署描述符中的一个security-constraint包含一个映射到一个用@ServletSecurity注解的类的一个精确匹配的url-pattern,注解一定不能影响Servlet容器在pattern上的强行限制。
当一个类上没有使用@ServletSecurity,应用于从那个类映射而来的一个servlet的访问策略(如果有的话)在对应的部署描述符中被适用的security-constraint元素建立,或者除了任何这些元素,通过ServletRegistration接口的setServletSecurity方法编程式地为目标servlet建立。

  1. ServletRegistration.Dynamic的setServletSecurity
    setServletSecurity方法可以在ServletContextListener中被用来定义应用于为ServletRegistration定义的映射的安全约束。
    Collection<String> setServletSecurity(ServletSecurityElement arg)
    传递给serServletSecurityjavax.servlet.ServletSecurityElement参数类似@ServletSecurity注解的ServletSecurity接口的结构和模型。Mapping @ServletSecurity to security-constraint节中定义的映射关系类似于一个包含HttpConstraintElementHttpMethodConstraintElement值的ServletSecurityElement元素的映射,等价于security-constraint表示。
    setServletSecurity方法返回已经是部署描述符(因此不会被调用影响)中security-constraint元素的精确目标的URL模式的集合(可能为空)。
    如果ServletsContext(ServletRegistration从这里获取到)已经被初始化,那么这个方法将会抛出一个IllegalStateException
    当部署描述符中的一个security-constraint包含一个url-pattern,这个url-pattern是一个被ServletRegistration映射的模式的精确匹配,那么调用ServletRegistration之上的setServletSecurity一定不能影响Servlet容器在模式上的强制约束。
    有上述列出的异常以及包含何时Servlet类被@ServletSecurity注解,何时setServletSecurityServletRegistration上被调用,它会建立应用于registration的url-pattern的安全约束。

五、角色

一个安全角色是一个被应用开发者或者装配器定义的用户逻辑分组。当应用被部署,角色被一个开发者映射到运行时中的委托者或者组。
一个servlet容器为关联一个基于主体安全属性进来的请求的主体强制执行声明式或者编程式安全。
这可以在下述方式的任意一种中发生:

  1. 一个开发者已经在运行环境中把一个安全角色映射到一个用户组。调用主体所在的用户组从它们的安全属性中被检索。仅当主体属于安全角色已经被开发者映射到的用户组,主体才是在安全角色中。
  2. 一个开发者已经把一个安全角色映射到一个安全策略域中的一个主体名字。这种情况下,调用主体的主体名字从它们的安全属性中检索。仅当主体名与安全角色被映射到的主体名一样时,主体就在安全角色中。

六、认证

一个web客户端能够使用下列机制中的一种来给一个web服务器验证用户身份:

  1. HTTP Basic Authentication
    HTTP Basic Authentication,基于用户名和密码,是定义在HTTP/1.0规范中的认证机制。一个web服务器请求一个web客户端来验证这个用户。作为请求的一部分,web服务器会传递一个字符串realm,用户会在这个里面被认证。web客户端从用户获取用户名和密码,然后把它们传递给web服务器。web服务器会在具体的realm中验证用户。
    Basic Authentication并不是一个安全认证协议。用户密码以简单的base64编码发送,并且目标服务器没有被验证。额外的保护能够缓解一些担心:一个安全传输机制(HTTPS), 或者网络级别的安全在一些部署场景下被应用。
  2. HTTP Digest Authentication
    像HTTP Basic Authentication,HTTP Digest Authentication基于一个用户名和密码验证一个用户。然而,不像HTTP Basic Authentication,HTTP Digest Authentication不会在网络上发送用户密码。在HTTP Digest Authentication中,客户端发送一个密码的单向加密哈希(以及额外的数据)。尽管密码不会在电缆上发送,但是HTTP Digest Authentication要求明文密码等价物对验证容器可用,以便容器能够验证收到的通过计算预期数字的验证器。Servlet容器应该支持HTTP_DIGEST验证。
  3. Form Based Authentication
    login screen的外观和感觉在使用web浏览器的内置验证机制时不能不同。本规范介绍了一个必须的form based authentication机制,它允许一个开发者控制登录界面的外观和感觉。
    web应用部署描述符包含了登录表单的错误页面的入口。登录表单必须包含用来输入用户名和密码的域。这些域必须分别命名为j_usernamej_password
    当一个用户尝试获取受保护的web资源,容器会检查用户的身份认证。如果用户被认证,并且拥有访问资源的权限,请求的web资源被激活,并且执行它的一个引用被返回。
    如果用户没有被认证,所有下述步骤会出现:
  4. 与安全约束相关的登录表单被发送给客户端,并且触发认证的URL路径被容器存储。
  5. 用户被让填写表单,包括用户名和密码。
  6. 客户端把表单以post方式发回给服务器。
  7. 容器尝试使用表单中的信息验证用户。
  8. 如果验证失败,使用发送或者重定向的方式返回一个错误页面,并且response的状态码被设置为200。
  9. 如果验证成功,认证用户的主体被检查,看它是否在获取资源的授权角色中。
  10. 如果用户被认证,客户端使用存储的URL路径被重定向到资源。
    发送给为通过验证的用户的错误页面包含失败的信息。

Form Based Authentication如Basic Authentication一样缺乏安全,因为用户密码被作为普通文本被发送,并且目标服务器没有被验证。额外的保护能够缓解一些担忧:一个安全传输机制(HTTPS), 或者网络级别的安全在一些部署场景下被应用。

  1. 表单登录注意事项
    基于表单的登录和基于URL的会话跟踪可能是有问题的实现。仅当会话被cookies或者SSL session信息维护的时候,基于表单的登录才应该被使用。
    为了进行适当的验证,登录表单的动作必须总是j_security_check。做这个限制以便登录表单无论是哪个资源都能正常工作,并且避免要求服务器指定表单之外的动作域。
    这是一个展示表单如何被编码到HTML页面中的例子:
    <form method="POST" action="j_security_check">
    <input type="text" name="j_username">
    <input type="password" name="j_password">
    </form>
    如果基于表单的登录由于一个HTTP请求被调用,原始请求的参数必须被容器保留,如果成功通过验证,它会把调用重定向到一个请求的资源。
    如果用户使用表单登录通过验证,并且已经创建一个HTTP会话,会话的超时或者非法导致用户在后续请求必须引发用户被重新认证的场景下被登出。登出的范围与认证的一样:比如,如果容器支持单点登录,如兼容Java EE技术的web容器,用户将需要和位于web容器的任何web应用重新认证。

  2. HTTPS Client Authentication

使用HTTPS(HTTP over SSL)的终端用户认证是一个很强的认证机制。这种机制需要客户端拥有一个Public Key Certificate(PKC)。当前PKCs在电商应用中很有用,并且在浏览器中的单点登录中也很有用。

  1. 其它容器认证机制
    Servlet容器应该提供一个公共接口,可以通过部署的应用的容器被用来集成和配置额外的HTTP消息层认证机制。这些接口应该被提供给缔约方使用,而不是给容器提供商(包括应用开发者,系统管理员,和系统集成者)。
    为了促进便携实现和额外容器认证机制的集成,推荐所有Servlet容器实现给容器提供的Java 认证SPI的Servlet Container Profile。SPI可以在这里下载:http://www.jcp.org/en/jsr/detail?id=196

七、认证信息的服务器跟踪

在运行时环境中角色被映射到的当前安全身份(如用户和组)是具体环境,而不是具体应用,它期望是:

  1. 做web应用部署的环境中的登录机制和策略。
  2. 能够使用相同的认证信息来对所有部署在同一个容器的应用代表一个主体。
  3. 仅当跨越了一个安全策略域边界,才需要用户的重新认证。
    因此,一个servlet容器被要求在容器级别跟踪认证信息(而不是web应用级别)。这允许对一个web应用已经认证的用户访问被其它拥有同样安全身份的容器管理的资源。

八、指定安全约束

安全约束是定义web内容保护的一个声明方式。一个安全约束与认证关联,用户数据与web资源上的HTTP操作关联。一个安全约束,在部署描述中被security-constration表示,由下列元素组成:

  1. 联合约束
    为了联合约束的目的,当没有HTTP方法在集合中被命名,或者集合特地在一个被包含的http-method元素中特地命名了HTTP方法,或者集合包含一个或者更多http-method-omission元素,没有元素命名这个HTTP方法时,一个HTTP方法会在一个web-resource-collection中出现。
    当一个url-pattern和HTTP方法对在多个安全约束下出现在联合(比如在一个web-resource-collection中)中,这个约束(在pattern和方法上)通过联合单独的约束来定义。对相同pattern和方法出现的联合约束规则如下:
  1. 例子
    下列例子说明了约束的联合以及它们到一个应用约束表格的转换。假设一个部署描述符包含下列安全限制:
    <security-constraint>
    <web-resource-collection>
    <web-resource-name>preclued methods</web-resource-name>
    <url-pattern>/</url-pattern>
    <url-pattern>/acme/wholesale/
    </url-pattern>
    <url-pattern>/acme/retail/</url-pattern>
    <http-method-omission>GET</http-method-omission>
    <http-method-omission>POST</http-method-omission>
    </web-resource-collection>
    <auth-constraint/>
    </security-constraint>
    <security-constraint>
    <web-resource-collection>
    <web-resource-name>wholesale</web-resource-name>
    <url-pattern>/acme/wholesale/
    </url-pattern>
    <http-method>GET</http-method>
    <http-method>PUT</http-method>
    </web-resource-collection>
    <auth-constraint>
    <role-name>SALEACLERK</role-name>
    </auth-constraint>
    </security-constraint>
    <security-constraint>
    <web-resource-collection>
    <web-resource-name>wholesale 2</web-resource-name>
    <url-pattern>/acme/wholesale/</url-pattern>
    <http-method>GET</http-method>
    <http-method>POST</http-method>
    </web-resource-collection>
    <auth-constraint>
    <role-name>CONTRACTOR</role-name>
    </auth-constraint>
    <user-data-constraint>
    <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
    </security-constraint>
    <security-constraint>
    <web-resource-collection>
    <web-resource-name>retail</web-resource-name>
    <url-pattern>/acme/retail/
    </url-pattern>
    <http-method>GET</http-method>
    <http-method>POST</http-method>
    </web-resource-collection>
    <auth-constraint>
    <role-name>CONTRACTOR</role-name>
    <role-name>HOMEOWNER</role-name>
    </auth-constraint>
    </security-constraint>
    这个假想部署描述符的转换将会产生下表中定义的约束:
    TABLE 安全约束表
url-pattern http-method permitted roles 支持的连接类型
/* 除了GET、POST的所有方法 访问拒绝 没有约束
/acme/wholesale/* 除了GET、POST的所有方法 访问拒绝 没有约束
/acme/wholesale/* GET CONTRACTOR SALESCLERK 没有约束
/acme/wholesale/* POST CONTRACTOR CONFIDENTIAL
/acme/retail/* 除了GET、POST的所有方法 访问拒绝 没有约束
/acme/retail/* GET CONTRACTOR HOMEOWNER 没有约束
/acme/retail/* POST CONTRACTOR HOMEOWNER 没有约束
  1. 处理请求
    当servlet容器接收一个请求,它将会使用Use of URL Paths中描述的算法来选择是请求URI的最佳匹配的url-pattern上定义的约束(如果有)。如果没有约束被选择,容器将会接收请求。否则容器将会决定请求的HTTP方法是否在选择的模式上被约束。如果不是,请求将会被接收。否则,请求必须在url-pattern上满足应用于HTTP方法的约束。下列两条规则必须满足将被接收的请求并且分发给关联servlet。
  2. 请求被接收连接上的特点必须满足至少一个被约束定义的支持连接类型。如果这条规则不被满足,容器将会拒绝请求并且会重定向到HTTPS端口。
  3. 请求的认证特点必须满足任何认证和通过约束定义的角色要求。如果这条规则由于访问被拒绝(通过一个认证约束没有命名角色)而不被满足,请求将会按照禁止状态被拒绝并且403(SC_FORBIDDEN)状态码将会被返回给用户。如果访问被限制允许角色并且请求还没有被认证,请求将会因为未认证被拒绝,并且401(SC_UNAUTHORIZED)状态码将会返回给原因认证。如果访问被限制允许角色并且请求的认证身份不是这些角色中任何一个的成员,请求将会因为禁止被拒绝,并且一个403(SC_FORBIDDEN)状态码将会被返回给用户。

九、默认策略

默认地,访问资源不需要被认证。当包含是请求URI最佳匹配的url-pattern的安全约束(如果有)在请求的HTTP方法上结合实施一个auth-constraint(命名角色)时,认证是必要的。相似地,一个受保护的传输并不必要,除非应用于请求上的安全约束在请求的HTTP方法上结合实施一个user-data-constraint(带一个受保护的transport-guarantee)。

十、登录和登出

把一个请求分发给servlet引擎时,容器建立请求的调用者身份。调用者身份在整个请求处理过程中保持不变,或者直到应用在request上成功调用authenticatelogin或者logout
请求处理期间被记录到一个应用中,由于可以通过调用request上的getRemoteUser或者getUserPrincipal,精确对应一个与请求关联的合法非空调用者身份。一个来自任意这些方法的null返回值表明调用者在请求处理时没有被记录进应用中。
容器可以穿件一个HTTP会话对象来跟踪登录状态。如果一个开发者创建了一个会话,同时一个用户没有被认证,那么容器会认证这个用户,登录之后对开发者代码的会话可见性必须是同一个会话对象,这个对象在登录出现之前被创建,以便没有会话信息的丢失。

翻译自 Java Servlet Specification
Version 3.0 Rev a
Author:Rajiv Mordani
Date: December 2010

上一篇 下一篇

猜你喜欢

热点阅读