【问题标题】:Java HTTPServer Authentication - Client SideJava HTTPServer 身份验证 - 客户端
【发布时间】:2015-03-01 05:50:41
【问题描述】:

我实现了一个非常简单的 HTTP 服务器,如 here 所述。我设法通过使用 Authentication 标头使身份验证工作,但我无法弄清楚如何从表单中获取凭据并使用它们与服务器进行身份验证。这通常是怎么做的?

代码:

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

import com.sun.net.httpserver.BasicAuthenticator;
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

public class SimpleHttpServer3 {

  public static void main(String[] args) throws Exception {
    HttpServer server = HttpServer.create(new InetSocketAddress(8000), 0);
    server.createContext("/info", new InfoHandler());
    HttpContext hc1 = server.createContext("/get", new GetHandler());
    hc1.setAuthenticator(new BasicAuthenticator("get") {
        @Override
        public boolean checkCredentials(String user, String pwd) {
            return user.equals("admin") && pwd.equals("password");
        }
    });
    server.setExecutor(null); // creates a default executor
    server.start();
    System.out.println("The server is running");
  }

  // http://localhost:8000/info
  static class InfoHandler implements HttpHandler {
    public void handle(HttpExchange httpExchange) throws IOException {
      String response = "Use /get to authenticate (user:admin pwd:password)";
      SimpleHttpServer3.writeResponse(httpExchange, response.toString());
    }
  }

  static class GetHandler implements HttpHandler {
    public void handle(HttpExchange httpExchange) throws IOException {
      StringBuilder response = new StringBuilder();
      response.append("<html><body>");
      response.append("hello " + httpExchange.getPrincipal().getUsername());
      response.append("</body></html>");
      SimpleHttpServer3.writeResponse(httpExchange, response.toString());
    }
  }

  public static void writeResponse(HttpExchange httpExchange, String response) throws IOException {
    httpExchange.sendResponseHeaders(200, response.length());
    OutputStream os = httpExchange.getResponseBody();
    os.write(response.getBytes());
    os.close();
  }

}

【问题讨论】:

    标签: java rest http client-side basic-authentication


    【解决方案1】:

    通常大多数人会使用 Shiro 或 Spring Security 等身份验证框架。

    这些框架在模式上注册一个 Servlet 过滤器,例如 /* 以对所有请求强制执行身份验证。它们将身份验证数据存储在 Servlet 会话中,以使用户在请求之间保持登录状态,这通常由 HTTP 服务器通过 Cookie 隐式完成。他们还注册了一个特殊的上下文来接受基于表单的身份验证请求,例如对/login 的 POST 请求。

    这些基于表单的端点将读取(通常是application/x-www-form-urlencoded),请求并提取提交的用户名和密码,以与服务器存储密码相同的方式对密码进行哈希处理,并比较它们以验证身份验证主体。

    【讨论】:

    • 我试图避免使用 Spring,因为它对于我非常简单的项目来说似乎有点过头了,但最后它似乎是我最好的选择
    • @SamanthaCatania,我同意,如果您想保持简单,请使用 Shiro,它是一个出色的框架,不需要引入任何额外的外部依赖项
    • Spring 似乎有更多的客户端浏览器支持,还是我错过了什么?
    【解决方案2】:

    Basic Authentication 协议规定客户端请求应具有格式为

    的标头
    Authorization: Basic Base64Encoded(username:password)
    

    其中Base64Encoded(username:password)username:password 的实际Base64 编码字符串。例如,如果我的用户名和密码是peeskillet:pass,则标头应发送为

    Authorization: Basic cGVlc2tpbGxldDpwYXNz
    

    the example from your link 中,它使用基本身份验证。受保护资源的 URL 是

    http://localhost:8000/get
    

    您可以通过浏览器访问该 URL,您将看到一个对话框(在 Firefox 中),如 this answer 所示。输入admin(用户名)和password(密码)。您将收到hello admin 返回消息。

    您不需要自己进行任何身份验证(或至少解析/解码),因为在内部(我没有检查源代码,但似乎是这样)BasicAuthenticator 解码并解析Authorization 标头并将用户名和密码传递给您实现的 checkCredentials 方法。因为它简单只允许admin/password,所以这是唯一可以进行身份​​验证的组合。

    为了完整起见,解码/解析(在伪代码中)可能看起来像

    String authHeader = request.getHeader("Authorization");
    String[] split = authHeader.split("\\s");
    String credentials = split[1];
    String decoded = Base64.decode(credentials);
    String[] userPass = decoded.split(":");
    
    String username = userPass[0];
    String password = userPass[1];
    
    boolean authenticated = checkCredentials(username, password);
    

    如果您想尝试使用代码发出请求,则需要在请求中设置Authorization 标头。该值将是admin:password 的 Base64 编码字符串,即YWRtaW46cGFzc3dvcmQ=。所以标题应该被发送出去

    Authorization: Basic YWRtaW46cGFzc3dvcmQ=
    

    对于其他组合,您只需 go here 输入组合,或者 Java 8 具有可用于对凭据进行编码的 java.util.Base64

    String cred = "admin:password";
    String encoded = Base64.getEncoder().encodeToString(cred.getBytes());
    System.out.println(encoded);
    // YWRtaW46cGFzc3dvcmQ=
    

    【讨论】:

    • 最初的问题表明他有基本的身份验证工作并且正在寻找有关基于表单的登录如何工作的信息,这意味着会话使用等。这属于“不要重新发明*”类别,因为像 Shiro 或 Spring Security 这样的框架在维护身份验证状态方面非常成熟
    • @Alex 是的,我可能误读或误解了它,但 OP 也没有说明客户。就我们所知,它是一个 OP 正在玩的 Swing 应用程序。那里有太多的可能性。但是在不知道 OP 打算使用的客户端的情况下,我认为任何答案都不是靠墙扔掉的任何选项,希望它能坚持下去:-) 但你似乎对我可能对这个问题的误解是正确的。我们可以等待 OP 的澄清