Web容器安全管理(下)——容器基本身份验证

  为了更好地了解并实现Web容器的安全管理,笔者以两篇博客的篇幅来介绍,即:《Web容器安全管理(上)——Java EE的安全概念》 和 《Web容器安全管理(下)——容器基本身份验证》。上篇博客已经介绍了Java EE安全的基本概念,打下了基础。在本文,我们详述Web容器提供的基本身份验证方式。

1、容器声明式基本身份验证

  假设你已经开发好了应用程序,现在想针对几个页面进行保护,只有通过身份验证且具备足够权限的用户,才可以浏览这些页面。这个需求有几个部分必须实现:
  (1)身份验证的方式
  (2)授予访问页面的权限
  (3)定义用户

  这里采用Web容器提供的最简单的基本(Basic)验证,在访问藉此受保护的资源时,浏览器会弹出对话框要求输入用户名和密码。如下图所示,是chrome弹出的身份验证对话框。

Web容器安全管理(下)——容器基本身份验证_第1张图片

  使用Web容器提供的基本身份验证功能,需要在应用程序的web.xml中定义:


    method>BASICauth-method>
login-config>

  接着要授予指定角色访问页面的权限,所以要先定义角色,在授权之前,必须在应用程序中,定义角色名称。可以在web.xml中如下定义:

<security-role>
    <role-name>adminrole-name>
security-role>

<security-role>
    <role-name>managerrole-name>
security-role>

  在这里定义了admin与manager两个角色名称。接着定义哪些URL可以被哪些角色以哪种HTTP方法访问。例如设置/admin下所有页面,无论使用哪个HTTP方法,都只能被admin角色访问:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Adminweb-resource-name>
        <url-pattern>/admin/*url-pattern>
    web-resource-collection>
    <auth-constraint>
        <role-name>adminrole-name>
    auth-constraint>
security-constraint>

  如果有多个角色可以访问某些页面,则标签可以设置多个标签。在这里看不到任何HTTP方法规范的定义,默认就是所有HTTP方法都受到限制。再来看另一个例子:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>Managerweb-resource-name>
        <url-pattern>/manager/*url-pattern>
        <http-method>GEThttp-method>
        <http-method>POSThttp-method>
    web-resource-collection>
    <auth-constraint>
        <role-name>adminrole-name>
        <role-name>managerrole-name>
    auth-constraint>
security-constraint>

  在这个设置中,对于/manager下的所有页面,根据&http-method>的设置,只有admin或manager才可以使用GET与POST方法进行访问。请留意这个语义“只有admin或manager才可以使用GET与POST方法进行访问”,这表示,其他HTTP方法,如PUT、TRACE、DELETE、HEAD和OPTIONS等,无论是否具备admin或manager角色,都可以访问!

  若没有设置,则所有HTTP方法都受到限制。设置了,则只有被设置HTTP受到限制,其他方法不受限制。如果没有设置标签,或标签中设置了*,表示任何角色都可以访问。如果直接编写了,那就没有任何角色可以访问了

  下面是一个完整的设置范例:


<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>SecurityBasicDemodisplay-name>

  <welcome-file-list>
    <welcome-file>index.jspwelcome-file>
  welcome-file-list>

  <session-config>
  
  
    <session-timeout>30session-timeout>
    
  session-config>

  <security-constraint>
    <web-resource-collection>
        <web-resource-name>Adminweb-resource-name>
        <url-pattern>/admin/*url-pattern>
    web-resource-collection>
    <auth-constraint>
        <role-name>adminrole-name>
    auth-constraint>
  security-constraint>

  <security-constraint>
    <web-resource-collection>
        <web-resource-name>Managerweb-resource-name>
        <url-pattern>/manager/*url-pattern>
        <http-method>GEThttp-method>
        <http-method>POSThttp-method>
    web-resource-collection>
    <auth-constraint>
        <role-name>adminrole-name>
        <role-name>managerrole-name>
    auth-constraint>
  security-constraint>

  <login-config>
    <auth-method>BASICauth-method>
  login-config>

  <security-role>
    <role-name>adminrole-name>
  security-role>

  <security-role>
    <role-name>managerrole-name>
  security-role>

web-app>

  就Web应用程序的设置部分,工作已经结束!但在将应用程序部署至服务器时,在服务器上设置角色与用户或组的对应,设置的方式并非Java EE的标准,而是各服务器都有所不同。例如在Tomcat中,可以在/conf/tomcat-users.xml中定义:

<tomcat-users version="1.0" xmlns="http://tomcat.apache.org/xml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd">
  <role rolename="manager"/>
  <role rolename="admin"/>
  <user username="caterpillar" password="123456" roles="admin,manger"/>
  <user username="momor" password="654321" roles="manager"/>
tomcat-users>

  要启用Tomcat的安全管理功能,还必须在Server Options中选取Enable security,才会读取tomcat-users.xml中的设置信息。

Web容器安全管理(下)——容器基本身份验证_第2张图片

  在这个设置中caterpillar同时具备admin与manager角色,而momor则具备manager角色。在启动应用程序之后,如果访问/admin或/manager,就会出现对话框要求输入名称、密码。如果输入错误,就会被一起要求输入直到正确为止。

Web容器安全管理(下)——容器基本身份验证_第3张图片

  如果访问/admin下的页面,只有输入了caterpillar名称及正确的密码,才可以正确浏览到页面。如果输入了momor名称及正确的密码,会提示权限不足,拒绝访问。

Web容器安全管理(下)——容器基本身份验证_第4张图片

  上面虽然输入了momor名称及正确的密码,通过了浏览器的身份验证,但授权失败,弹出403画面。

Web容器安全管理(下)——容器基本身份验证_第5张图片

  tomcat-user.xml是Tomcat预设的Realm(不知道什么是Realm,请参看上一篇博文),角色、用户名称、密码都存储在这个xml文件中。你也可以改用数据库表格,这需要额外配置。

2、容器声明式基本身份验证的原理

  在初次请求某个受保护的URL时,容器会检查请求中是否包括Authorization标头,如果没有的话,则容器会响应401 Unauthorized的状态码与信息,以及WWW-Authenticate标头给浏览器,浏览器收到WWW-Authenticate标头之后,就会出现对话框要求用户输入名称及密码,原理如下图所示:

Web容器安全管理(下)——容器基本身份验证_第6张图片

  如果用户在对话框中输入名称、密码后按下确定键,则浏览器会将名称密码以BASE64方式编码,然后放在Authorization标头中送出。容器会检查请求中是否包括Authorization标头,并验证名称、密码是否正确,如果正确,就将资源传送给浏览器。如下图所示:

Web容器安全管理(下)——容器基本身份验证_第7张图片

  BASE64是将二进制的字节编码为ASCII序列的编码方式,在HTTP中可用来传送内容较长的数据。编码并非加密,只要译码方式正确,就可以取得原本的信息

  接下来在关闭浏览器之前,只要是对服务器资源的请求,每次都包括Authorization标头,而服务器每次也都会检查是否有Authorization标头,所以登录期间会一起持续到关闭浏览器为止。如下图所示,为基本身份验证的流程图。

Web容器安全管理(下)——容器基本身份验证_第8张图片

  由于使用的是浏览器提供的对话框输入名称、密码,所以基本身份验证时无法自定义登录画面。由于传送名称、密码时使用的是Authenticate标头,无法设计注销机制,关闭浏览器是结束会话的唯一方式。

3、容器声明式窗体身份验证

  如果需要自定义登录页面,以及登录错误的页面,则可以改用容器所提供的窗体(Form)验证。要将之前的基本身份验证改为窗体验证的话,可以在web.xml中修改的设置:

//略...

    FORM
    
        /login.html
        /error.html
    

//略...

  在的设置从BASIC改为FORM。由于使用了窗体网页进行登录,所以必须告诉容器,登录页面是哪个,登录失败页面是哪个。这是由标签对来设置,设置时必须以斜杠开始,也就是从应用程序根目录开始的URL路径。

  接下来就可设置自己的窗体登录页面,但必须注意!窗体发送的URL必须是j_security_check,发送名称的请求参数必须是j_username,发送密码的请求参数必须是j_password。以下是login.html的简单示例:


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登录页面title>
head>
<body>
    <form action="j_security_check" method="post">
        名称:<input type="text" name="j_username"><br>
        密码:<input type="password" name="j_password"><br>
        <input type="submit" value="送出"/>
    form>
body>
html>

  error.html的简单示例:


<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>登录失败网页title>
head>
<body>
    <h1>用户名或者密码错误,登录失败h1>
    <a href='login.html'>返回登录页面a>
body>
html>

  登录时的页面如图所示:

Web容器安全管理(下)——容器基本身份验证_第9张图片

  登录失败时的页面如图所示:

Web容器安全管理(下)——容器基本身份验证_第10张图片

4、容器窗体身份验证的原理

  来了解一下容器利用窗体进行验证的原理。当使用窗体身份验证时,如果要访问受保护的资源,容器会检查用户有无登录,方式是查看HttpSession中有无”javax.security.auth.subject“属性,若没有这个属性,则表示没有经过容器的验证流程,则转发至登录页面,用户输入名称、密码并发送后,若验证成功,则容器会在HttpSession中设置属性名称”javax.security.auth.subject“的对应值javax.security.auth.subject实例。具体的流程如下图所示:

Web容器安全管理(下)——容器基本身份验证_第11张图片

  用户是否登录是通过HttpSession的”javax.security.auth.subject“属性来判断,所以要让此次登录失败,可以调用HttpSession的invalidate()方法,因此窗体验证时可以设计注销机制。

  除了基本身份验证与窗体验证之外,在中还可以设置DIGEST或CLIENT-CERT。

  DIGEST即所谓”摘要验证“,浏览器也会出现对话框输入名称、密码,而后通过Authorization标头传送,只不过并非使用BASE64来编码名称、密码。浏览器会直接传送名称,但对密码则先进行(MD5)摘要演算(非加密),得到理论上唯一且不可逆的字符串再传送,服务器根据名称从后端取得密码,以同样的方式作摘要演算,再比对浏览器送来的摘要字符串是否符合,如果符合就验证成功。由于网络上传送的并不是真正的密码,而不是不可逆的摘要,密码不会被得知,理论上比较安全。不过Java EE规范中并无要求一定得支持DIGEST的验证方式(看厂商的需要,Tomcat是支持的)。

  CLIENT-CERT也是用对话框的方式来输入名称与密码,因为使用PKC(Public Key Certificate)作加密,可保证数据传送时的机密性及完整性,但客户端需要安装证书(Certificate),在一般用户及应用程序之间并不常采用。

5、编程式安全管理

  Web容器的声明式安全管理,仅能针对URL来设置哪些资源必须受到保护,如果打算依据不同的角色在同一个页面中设置可访问的资源,例如只有站长或版面管理员可以看到删除整个讨论组的功能,普通用户不行,那么显然无法单纯使用声明式安全管理来实现。

  在Servlet3.0中,HttpServletRequest新增了三个与安全有关的方法:authenticate()、login()、logout()。

  首先来看authenticate()方法,搭配先前的声明式身份验证的web.xml的设置,你可以决定程序中哪一段逻辑,只有通过了容器身份验证的用户才可以看到。

package cc.openhome;

import java.io.IOException;
import java.io.PrintWriter;
import java.security.AccessControlException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(
        name="SecurityServlet",
        urlPatterns = { "/security" }
        )
public class SecurityServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public SecurityServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("任意其他用户就可以看到的数据一
"
); try { request.authenticate(response); out.println("

必须由容器验证通过的用户才可以看到的数据


"
); } catch (AccessControlException e) { e.printStackTrace(); } out.println("任意其他用户就可以看到的数据二
"
); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } }

  authenticate()方法会检查用户是否已经通过了容器验证,否则根据web.xml中的设置,要求进行身份验证,若通过验证,则可显示接下来的内容。

Web容器安全管理(下)——容器基本身份验证_第12张图片

  login()在调用时则可以提供用户名称、密码,利用容器设置的身份验证信息来进行验证。例如,以下Servlet只有在提供的username、password请求参数正确时,才可以看到相应的数据。

package cc.openhome;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.AccessControlException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet(name = "SecuityLoginServlet", urlPatterns = { "/securityLogin" })
public class SecuityLoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    public SecuityLoginServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
        response.setContentType("text/html;charset=UTF-8");
        PrintWriter out = response.getWriter();
        out.println("任意其他用户就可以看到的数据一
"
); try { String user = request.getParameter("user"); String passwd = request.getParameter("passwd"); request.login(user, passwd); out.println("

必须由容器验证通过的用户才可以看到的数据


"
); } catch (AccessControlException e) { e.printStackTrace(); } finally { request.logout(); } out.println("任意其他用户就可以看到的数据二
"
); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO Auto-generated method stub doGet(request, response); } }

  在浏览器的URL地址栏需要输入user与passwd参数,若参数通过验证,则可显示接下来的内容。

Web容器安全管理(下)——容器基本身份验证_第13张图片

  

你可能感兴趣的:(Java,Web学习笔记,容器与编程框架)