在泽西岛获得休息服务

时间:2015-01-29 05:05:46

标签: java rest jersey jax-rs jersey-2.0

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

我不知道该怎么做。我确实在网上搜索,但各种链接显示了各种实现,我无法决定如何继续进行。

我知道这是一个模糊的问题,但请在这方面提供帮助。

5 个答案:

答案 0 :(得分:12)

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

Authorization: Basic cGVlc2tpbGxldDpwYXNz

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

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

这是针对SSL支持的。建议用于基本身份验证。

如果您不想处理使用安全域和登录模块,领域等的麻烦,那么需要自定义servlet支持,或者如果您不在servlet环境中,在ContainerRequestFilter中实现Basic Auth实际上并不太难。

您可以在jersey/examples/https-clientserver-grizzly查看完整示例。您应该关注SecurityFilter

过滤器中的基本流程就像这样

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

  2. 一旦我们有了标题,我们解析它只是为了获得Base64编码的用户名:密码。然后我们解码它,然后拆分它,然后分开用户名和密码。如果此过程中的任何一个失败,请再次抛出映射到400 Bad Request的WebApplicationException

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

  4. 如果一切顺利,则会从User方法创建authenticate,并将其注入AuthorizerSecurityContext)。在JAX-RS中,SecurityContext通常用于授权。

  5. 对于授权,如果要保护某些资源的某些区域,可以对类或方法使用@RolesAllowed注释。泽西岛通过registering the RolesAllowedDynamicFeature支持此注释。

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

    要进行测试,您只需使用浏览器即可。如果一切设置正确,当您尝试访问资源时,您应该看到(在Firefox中)this post中显示的对话框。如果您使用cURL,则可以执行

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

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

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

答案 1 :(得分:1)

安全性主要有两种:

  • 基于容器
  • 基于申请

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

您可以使用基于容器的安全性,但我猜测您对spring的使用也排除了该选项。 由于选择Spring通常是为了避免使用完整的J2EE容器(编辑:虽然如下所述,但是大多数普通的servlet容器都允许你实现各种基于容器的安全方法)

这实际上只留下一个选项,即推出自己的安全性。

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

        
  • BASIC
  •     
  • 摘要
  •     
  • 表格
  •     
  • 证书

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

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

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

答案 2 :(得分:1)

正如之前的帖子所说,你可以选择不同的选项,实现的开销也各不相同。从实际的角度来看,如果您要从这开始并且正在寻找一种简单实现的舒适方式,我建议使用BASIC身份验证的基于容器的选项。

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

答案 3 :(得分:1)

我现在可以告诉你的是,你已经完成了大部分“肮脏”的事情。将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/

答案 4 :(得分:1)

昨天实际做了这件事。

所以这真的是你想要实现的目标。我的理由是使用移动设备和One-Page-App 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,但这很明显。