不同请求方法的Java httpServer基本身份验证

10

我正在使用Java中非常简单的httpServer来构建支持GET、POST、PUT和DELETE方法的API REST。我使用基本身份验证,并有一些类(Authentication.java和Authorisation.java)用于验证用户身份和检查权限。

现在,我想让所有已认证的用户都能够从我的API REST中获取信息,但只允许具有某些特权的用户进行POST、PUT和DELETE操作。那么,我该怎么做呢?

这就是我所拥有的内容。

public class Server {

  private static HttpServer server;

  public static void start() throws IOException {

    server = HttpServer.create(new InetSocketAddress(8000), 0);
    HttpContext ctx = server.createContext("/users", new UserHandler());
    ctx.setAuthenticator(new ApiRestBasicAuthentication("users"));

    server.start();
  }

}

这是我的ApiRestBasicAuthentication。

public class ApiRestBasicAuthentication extends BasicAuthenticator {

  private UserAuthentication authentication = new UserAuthentication();

  public ApiRestBasicAuthentication(String realm) {
    super(realm);
  }

  @Override
  public boolean checkCredentials(String user, String pwd) {
    int authCode = authentication.authenticate(user, pwd);
    return authCode == UserAuthentication.USER_AUTHENTICATED;
  }

}

目前,check credentials仅检查用户是否已通过身份验证。但是,如果请求方法为POST、DELETE或PUT的话,我也想检查特定的凭据。但我应该如何在我的ApiRestBasicAuthentication中获取方法?我是在我的处理程序类中进行操作。

public void handle(HttpExchange httpExchange) throws IOException {
    String method = httpExchange.getRequestMethod();
    if ("post".equalsIgnoreCase(method)) {
      createUser(httpExchange);
    } else if ("get".equalsIgnoreCase(method)) {
      readUsers(httpExchange);
    } else if ("put".equalsIgnoreCase(method)) {
      updateUser(httpExchange);
    } else if ("delete".equalsIgnoreCase(method)) {
      deleteUser(httpExchange);
    }
  }
也许这个应该用其他的方式完成。你有什么想法吗?
非常感谢。

我想你可能需要一些授权方面的功能(或类似过滤器或拦截器提供的功能),只有在用户经过授权后才会对非GET请求做出响应,但是我现在无法为您提供完整的答案。找出其中可用的那个(方面、过滤器、拦截器)并了解它的相关知识。 - Filip Malczak
这可能可行,因为我可以在过滤器中检查方法(POST、GET、PUT等),但是如何在过滤器内获取请求中发送的用户名呢? 我的ApiRestBasicAuthentication有一个checkCredentials()函数,它接收用户和密码,但在过滤器中,我只有httpExchange对象,而用户名/密码是加密的。 - David
我建议您使用 spring-security。请参考以下答案:https://dev59.com/kms05IYBdhLWcg3wQvuq#45965232 - Ortwin Angermeier
4个回答

10

一个简单的方法是更改你的ApiRestBasicAuthentication如下:

public class ApiRestBasicAuthentication extends BasicAuthenticator {

  private UserAuthentication authentication = new UserAuthentication();

  public ApiRestBasicAuthentication(String realm) {
    super(realm);
  }

  @Override
  public Authenticator.Result authenticate(HttpExchange exch) {
      Authenticator.Result result=super.authenticate(exch);
      if(result instanceof Authenticator.Success) {
          HttpPrincipal principal=((Authenticator.Success)result).getPrincipal();
          String requestMethod=exch.getRequestMethod();       
          if( ADD SOME LOGIC HERE FOR PRINCIPAL AND REQUEST METHOD) {
              return new return new Authenticator.Failure(401);
          }
          return result;

      }
  }

  @Override
  public boolean checkCredentials(String user, String pwd) {
    int authCode = authentication.authenticate(user, pwd);
    return authCode == UserAuthentication.USER_AUTHENTICATED;
  }

}

对于你想要在请求/用户中使认证器失败的内容,需要在那里添加一些逻辑。我在这里展示了如何在认证方法中获取方法,但您需要指定凭据类型。


另一个解决方案是,如果您检查BasicAuthenticator的源代码,您可以看到它如何实现authenticate方法,您可以以类似的方式创建自己的实现,而不是扩展BasicAuthenticator并使用get方法而不仅仅是用户名和密码。您可以在这里查看源代码,我相信您能找到正确的方法 ;)

通常,在企业应用程序中,您可以使用一些外部安全管理系统 - 例如,如果您使用Spring(当前Java Web应用程序中的事实标准),则可以使用Spring Security以更声明性的方式执行此类安全模式和过滤器


1
尽管上述答案可能对您有效,但我认为您还应考虑使用在web.xml中定义的角色和安全限制,并在REST资源中使用@RolesAllowed注释。这样可以让您特别允许单个方法或在REST资源/类级别上授予权限。在web.xml中,这看起来像这样:
<security-role>
        <role-name>SERVERTOSERVER</role-name>
    </security-role>
    <security-constraint>
        <web-resource-collection>
            <web-resource-name>REST API description</web-resource-name>
            <url-pattern>/<path name>/*</url-pattern>
            <http-method>GET</http-method>
        </web-resource-collection>
        <auth-constraint>
            <description>Only allow users 
        from following roles</description>
            <role-name>SERVERTOSERVER</role-name>
        </auth-constraint>
    </security-constraint>

以下链接提供了一些示例: 在tomcat-users.xml中使用角色标签的目的是什么?, https://www.thecoderscorner.com/team-blog/hosting-servers/17-setting-up-role-based-security-in-tomcat/ 如果有帮助的话,这里还有一个适用于基于Jersey的应用程序的解决方案: https://howtodoinjava.com/jersey/jersey-rest-security/

你指的是哪个 web.xml 文件?@RolesAllowed 注解来自哪里?没有进行交叉检查,我假设你在谈论一个特定于 Tomcat 服务器的解决方案。因此,这个答案不适用于上面的问题。 - user2690527
javax.annotation.security.RolesAllowed,我已经给出了基于角色的访问控制的一些示例(基于Tomcat / Jersey的REST API)。 - Ankur
正如你自己所说,你的解决方案是基于tomcat/jersey的。因此,它无法与com.sun.net.httpserver包中的HttpServer(或HttpsServer)一起使用。 - user2690527
@RolesAllowed是一个与安全角色一起使用的Java注释。我怀疑你所说的并不完全正确。 - Ankur

0

解决这个问题可能有很多方法,以下是我提出的其中一个建议:

  1. 创建一个用户对象,包含你需要的字段和一个名为“角色”的字段。假设只有“管理员”可以进行除“GET”之外的Http请求,而“普通”用户仅能进行“GET”。实现方式有很多种,但可以通过将“角色”字段设置为字符串并使用枚举类型分配值来轻松更改,仅使用特定术语。但这并非必须。为你创建的字段编写get和set方法,以备将来可能需要的情况,当然也要为角色编写相应的方法。

  2. 确保包含handle(HttpExchange httpExchange)的类能够查看当前登录的用户,并引用与其关联的User对象。然后修改该方法,以使其

 

if(loggedInUser.getRole().equals("admin")){
  //allow whatever method 
} else {
  // allow "GET" or give some denied permission error
}

由于没有提供其他实现,我无法给出更详细的答案或确保这将对您起作用。


0
我认为你应该创建一个 AuthenticationInterceptor,并绕过 GET 请求,相应地对 REST 的 non-GET 请求应用身份验证机制。
public class AuthenticationInterceptor extends HandlerInterceptorAdapter {

  @Autowired
  private ApiRestBasicAuthentication apiRestBasicAuthentication;

  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    switch (request.getMethod()) {
      case "GET" :
        // by-passing all GET requests
        return true;

      default :
        return apiRestBasicAuthentication.checkCredentials(username, password);
    }
  }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接