繁体   English   中英

保护泽西岛的 REST 服务

[英]Securing REST services in Jersey

我对网络服务非常陌生。 我已经使用 Jersey 2 与 Spring 集成公开了一些 REST 服务。 现在我需要使用用户名/密码进行身份验证来保护这些休息服务。 我被告知不要使用 Spring Security。

我不知道如何做到这一点。 我确实在网上搜索过,但各种链接显示了各种实现,我无法决定如何进行。

使用用户名和密码进行身份验证的常用方法是使用基本身份验证 基本上客户端需要发送一个请求头Authorization ,头值为Basic Base64Encoded(username:password) 所以我的用户名是peeskillet和我的密码是pass ,作为客户,我应该将标题设置为

Authorization: Basic cGVlc2tpbGxldDpwYXNz

在 servlet 环境中,容器应该支持基本身份验证。 您将在 web.xml 上配置此支持。 您可以在 Java EE 教程的48.2 保护 Web 应用程序中看到一个示例。 您还会在示例中注意到

<transport-guarantee>CONFIDENTIAL</transport-guarantee>

那是为了 SSL 支持。 推荐用于基本身份验证。

如果您不想处理使用安全域和登录模块、领域等的麻烦,则需要自定义 servlet 支持,或者如果您只是不在 servlet 环境中,则实现基本身份验证在ContainerRequestFilter中真的不是太难。

你可以在jersey/examples/https-clientserver-grizzly看到一个完整的例子,说明如何做到这一点。 您应该关注SecurityFilter

过滤器中的基本流程是这样的

  1. 获取Authorization标头。 如果它不存在,则抛出AuthenticationException 在这种情况下, AuthenticationExceptionMapper将发送标头"WWW-Authenticate", "Basic realm=\\"" + e.getRealm() + "\\" ,它是 Basic Auth 协议的一部分

  2. 获得标头后,我们对其进行解析以获取 Base64 编码的用户名:密码。 然后我们对其进行解码,然后拆分,然后将用户名和密码分开。 如果此过程中的任何一个失败,则再次抛出映射到 400 Bad Request 的WebApplicationException

  3. 检查用户名和密码。 示例源代码只检查用户名是否为user密码是否为password ,但您需要在过滤器中使用某些服务来验证此信息。 如果其中任何一个失败,则抛出AuthenticationException

  4. 如果一切顺利, User将从authenticate方法创建,并注入到Authorizer (这是一个SecurityContext )。 在 JAX-RS 中, SecurityContext通常用于授权。

对于授权,如果您想保护某些资源的某些区域,您可以为您的类或方法使用@RolesAllowed批注。 Jersey 通过注册RolesAllowedDynamicFeature支持此注释。

幕后发生的事情是将从请求中获取SecurityContext 通过我链接的示例,您可以看到Authorizer ,它有一个重写的方法isUserInRole 将调用此方法以检查@RolesAllowed({"ADMIN"}) 因此,当您创建SecurityContext ,您应该确保在重写的方法中包含用户的角色。

对于测试,您可以简单地使用浏览器。 如果一切设置正确,当你尝试访问该资源,你应该看到(在Firefox)对话框中看到这个帖子 如果你使用cURL ,你可以做

C:/>curl -v -u username:password http://localhost:8080/blah/resource

这将发出基本身份验证请求。 由于-v开关,您应该看到所有涉及的标头。 如果您只想使用客户端 API 进行测试,您可以在此处查看如何设置它。 上述三种情况中的任何一种,都会为您完成Base64编码,因此您不必担心。

至于 SSL,您应该查看容器的文档以获取有关如何设置它的信息。

所以这真的是你想要实现的问题。 我的情况是让这个东西用移动设备和单页应用程序 JavaScript 运行。

基本上,您需要做的就是生成某种标头,该值将在您的客户端发出的每个连续请求中都需要。

所以你做了一个端点,你在其中等待带有用户/密码的帖子:

@Path("/login")
public class AuthenticationResource {

@POST
@Consumes("application/json")
public Response authenticate(Credentials credential) {
    boolean canBeLoggedIn = (...check in your DB or anywher you need to)

    if (canBeLoggedIn) {
        UUID uuid = UUID.randomUUID();
        Token token = new Token();
        token.setToken(uuid.toString());
        //save your token with associated with user
        (...)

        return Response.ok(token).type(MediaType.APPLICATION_JSON_TYPE).build();
    } else {
        return Response.status(Response.Status.UNAUTHORIZED).build();
    }
}
}

现在您需要保护需要该令牌的资源:

   @Path("/payment")
   @AuthorizedWithToken
   public class Payments {

    @GET
    @Produces("application/json")
    public Response sync() {
     (...)
    }

    }

注意@AuthorizedWithToken注释。 您可以使用特殊元注释@NameBinding自行创建此注释

@NameBinding
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthorizedWithToken {}

现在对于实现标头检查的过滤器:

@AuthorizedWithToken
@Provider
public class XAuthTokenFilter implements ContainerRequestFilter {

    private static String X_Auth_Token = "X-Auth-Token";

    @Override
    public void filter(ContainerRequestContext crc) throws IOException {
        String headerValue = crc.getHeaderString(X_Auth_Token);
        if (headerValue == null) {
            crc.abortWith(Response.status(Response.Status.FORBIDDEN).entity("Missing " + X_Auth_Token + " value").build());
            return;
        }

        if(! TOKEN_FOUND_IN_DB) {
            crc.abortWith(Response.status(Response.Status.UNAUTHORIZED).entity("Wrong " + X_Auth_Token + " value").build());
            return;
        }
    }
}

您可以创建任意数量的自己的注释来检查 http 请求中的各种内容并将它们混合。 但是,您需要注意优先级,但这实际上很容易找到。 此方法需要使用https但这很明显。

安全性有两种主要形式:

  • 基于容器
  • 基于应用

保护 Spring 应用程序的标准方法是使用 Spring Security(以前称为 Acegi)。 知道为什么不允许您使用它会很有趣。

您可以使用基于容器的安全性,但我猜您对 spring 的使用也排除了该选项。 由于选择 Spring 通常是为了避免使用完整的 J2EE 容器(编辑:尽管正如下面其他人指出的,大多数普通 servlet 容器确实允许您实现各种基于容器的安全方法)

这实际上只为您提供了一种选择,那就是推出您自己的安全性。

您对 Jersey 的使用表明这可能是一个 REST 应用程序。 在这种情况下,您真的应该坚持使用标准的 HTTP 身份验证方法,这些方法以强度相反的顺序具有以下风格:

  • 基本的
  • 消化
  • 形式
  • 证书

REST 应用程序通常应该是“无状态的”,这基本上排除了基于表单的身份验证(因为您需要使用 Session),而留给您的是 BASIC、Digest 和 Certificate。

你的下一个问题是,我在验证谁。 如果您可以期望根据他们请求的 URL 知道用户的用户名和密码(假设它是所有用户的一组凭据),那么 Digest 是最好的选择,因为从不发送密码,只有一个散列。 如果您不知道密码(因为您要求第三方系统对其进行验证等),那么您就无法使用 BASIC。 但是您可以通过使用 SSL 来增强 BASIC 的安全性,或者更好的是,将 BASIC 与客户端证书身份验证相结合。 事实上,基于 HTTPS 的 BASIC 身份验证是保护大多数 REST 应用程序的标准技术。

您可以轻松实现一个 Servlet 过滤器,它会查找身份验证标头并自行验证凭据。 有很多此类过滤器的示例,它是一个独立的类文件。 如果未找到凭据,过滤器将返回 401,并在响应标头中传递基本身份验证提示。 如果凭据无效,您将返回 403。应用程序安全本身几乎是整个职业,但我希望这会有所帮助。

正如前几篇文章所说,您可以采用不同的选项,实现不同的开销。 从实用的角度来看,如果您打算从这里开始并正在寻找一种简单实现的舒适方式,我建议使用 BASIC 身份验证的基于容器的选项。

如果使用tomcat,可以设置一个realm ,实现起来比较简单。 您可以使用 JDBCRealm,它从数据库中的指定列中获取用户和密码,并通过 server.xml 和 web.xml 对其进行配置。 每次您尝试访问您的应用程序时,这都会自动提示您输入凭据。 您没有任何应用程序端实现要做。

我现在可以告诉您的是,您已经完成了将 Jersey 与 Spring 集成的大部分“脏”工作。 我建议您使用基于应用程序的解决方案,它不会将您绑定到特定容器。 Spring Security 起初可能令人生畏,但是当您驯服野兽时,您会发现它实际上是一只友好的小狗。

事实是 Spring Security 是非常可定制的,只需实现它们的接口即可。 并且有很多文档和支持。 另外,您已经有一个基于 Spring 的应用程序。

由于您寻求的只是指导,我可以为您提供一些教程。 你可以利用这个博客。

http://www.baeldung.com/rest-with-spring-series/ http://www.baeldung.com/2011/10/31/securing-a-restful-web-service-with-spring-security- 3-1-part-3/

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM