【问题标题】:How to authenticate users in Jersey如何在泽西岛对用户进行身份验证
【发布时间】:2017-02-21 17:06:20
【问题描述】:

我正在使用 Jersey 用 Ja​​va 编写一个 RESTful 应用程序,我需要对用户进行身份验证。我知道我可以使用注释@RolesAllowed 指定资源中的角色,但我无法理解用户如何与特定角色相关联。客户端这样发送用户名和密码

    HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic(user, password);
    Client client = ClientBuilder.newClient();
    client.register(feature);
    WebTarget target = client.target(baseUrl).path(urlString);
    Invocation.Builder invocationBuilder = target.request(MediaType.APPLICATION_JSON);
    Response response = invocationBuilder.get();

假设有些方法只能由超级用户使用,有些方法可以由任何用户使用,那么当客户端发送用户名和密码时如何区分它们?

【问题讨论】:

    标签: java rest jersey


    【解决方案1】:

    我知道我可以使用注释 @RolesAllowed 指定资源中的角色,但我无法理解用户是如何与特定角色相关联的

    角色信息存储在数据库中。假设你有一个 User 来模拟数据库中的 USER 和 ROLES 表

    class User {
      String username;
      List<String> roles;
    
      public String getUsername() { return username; }
      public void setUsername(String username) { this.username = username; }
      public List<String> getRoles() { return roles; }
      public void setRoles(List<String> roles) { this.roles = roles; }
    }
    

    您会在 Jersey 过滤器中获得 User。这也是您进行身份验证的地方。

    @Provider
    @Priority(Priorities.AUTHENTICATION)  // needs to happen before authorization
    class AuthenticationFilter implements ContainerRequestFilter {
        @Inject
        private UserService userService;  // this is your own service
    
        @Override
        public void filter(ContainerRequestContex context) {
            // note, this is a lazy implementation of Basic auth.
            // it doesn't do ant error checking. Please see
            // link at bottom for better imlementation
            String authzHeader = context.getHeaderString(HttpHeaders.AUTHORIZATION); // (1)
            String decoded = Base64.decodeAsString(authzHeader);
            String[] split = decoded.split(":");
            User user = userService.getUser(split[0]);                              // (2)
            if (user == null || !user.getPassword().equals(someHash(split[1])) {    // (3)
                throw new UnauthorizedException();
            }
            SecurityContext oldContext = context.getSecurityContext();               // (4)
            context.setSecurityContext(new BasicSecurityConext(user, oldContext.isSecure()));
        }
    }
    

    你在这里做的是:

    1. 解析基本认证授权标头
    2. 使用用户名获取User
    3. 进行身份验证
    4. 设置一个新的SecurityContext

    BasicSecurityContext 如下所示。这是您将角色与用户相关联的地方。

    static class BasicSecurityContext implements SecurityContext {
       private final User user;
       private final boolean secure;
       
       public BasicSecurityContext(User user, boolean secure) {
           this.user = user;
           this.secure = secure;
       }
    
       @Override
       public Principal getUserPrincipal() {
           return new Principal() {
               @Override
               public String getName() {
                    return user.getUsername();
               }
           };
       }
    
       @Override
       public String getAuthenticationScheme() {
           return SecurityContext.BASIC_AUTH;
       }
    
       @Override
       public boolean isSecure() { return secure; }
    
       @Override
       public boolean isUserInRole(String role) {
           return user.getRoles().contains(role);
       }
    }
    

    如果您查看isUserInRole 的底部。将会发生的是,Jersey 将从资源方法或类中获取 @RolesAllowed 注释,获取值,然后将它们传递给 isUserInRole。如果它返回true,则用户被授权。在伪代码中

    @GET
    @Path("/somepath")
    @RolesAllowed({"USER", "SUPER_USER"})
    public Response get() {}
    ...
    
    RolesAllowed annotation = resourceMethod.getAnnotation(RolesAllowed.class);
    String roles = annotation.value();
    SecurityContext context = getSecurityContext();
    for (String role: roles) {
        if (context.isUserInRole(role)) {
            return;
        }
    }
    throw new ForbiddenException();
    

    这只是伪代码,但它显示了 Jersey 如何使用@RolesAllowedSecurityContext 处理授权,以及如何实现isUserInRole

    此授权功能不会自动开启。你需要自己打开它。为此,只需注册RolesAllowedDynamicFeature

    public JerseyConfig extends ResourceConfig {
        public JerseyConfig() {
            register(RolesAllowedDynamicFeature.class);
        }
    }
    

    这里要注意的一点是,在上述所有内容中,我们正在实现我们的基本身份验证和安全上下文的设置。这并没有什么问题。但是如果你使用 servlet 容器身份验证机制,Jersey 实际上会从HttpServletRequest 获取身份验证信息。 HttpServletRequest 有一个 getUserPrincipal() 方法和一个 isUserInRole 方法。泽西岛将使用这些来委托SecurityContext。因此,如果您用户容器身份验证,那么您实际上不需要实现任何东西。您只需注册RolesAllowedDynamicFeature

    如果您想使用容器的身份验证机制,您应该查阅服务器的文档。在您的服务器中设置领域后,您需要使用安全信息配置web.xml。下面的链接中有一个示例。您还应该在 Web 安全部分下的 Java EE 文档中找到此信息。

    另请参阅:

    【讨论】:

    • 谢谢!有两件事我不明白: 1 - 我在哪里可以找到 UserService 类?还是我必须实施它? 2 - 我必须在哪里实现 JerseyConfig 类?谁必须实例化它?
    • 1) UserService 是您自己的类,用于从数据库中获取数据。这只是一个例子。 2)那是一个球衣配置类。您可以使用它来代替或与 web.xml 一起使用。你不创造它。泽西岛将创造它。您要么需要在 web.xml 中引用它
    【解决方案2】:

    我们需要解决两件事

    1. 身份验证 - 检查用户是否真的是它声称的那个人
    2. 授权 - 如果经过身份验证的用户有权访问给定的方法

    要同时进行身份验证和授权,我们需要一个存储以下映射的数据存储:

    1. 用户与其密码的映射
    2. 角色和用户之间的映射
    3. 角色和权限之间的映射

    这里需要第一个映射进行身份验证,其他两个映射用于授权。

    另外,请注意,我们需要对每个 API 调用进行身份验证和授权。所以我们会做很多读操作。

    因此,通常使用目录服务器或 Apache DS 等 Ldap 服务器来存储这些映射,因为目录服务器是读取优化的数据存储。

    在 RESTful 应用程序中,通常使用过滤器从请求标头中提取用户名和密码,并使用 Ldap 服务器进行身份验证。如果认证成功,下一步是通过查询用户角色和角色权限映射从 Ldap 服务器中提取用户的权限。如果用户被授权,那么只有在这种情况下,控制才会流向实际的 API 业务逻辑。

    详情请参考this答案。

    【讨论】:

      【解决方案3】:

      HttpAuthenticationFeature 类提供 HttpBasic 和 Digest 客户端身份验证功能。该功能可在 4 种模式之一中工作;

      BASIC:这是一种抢先式身份验证方式,即信息始终随每个 HTTP 请求一起发送。此模式必须与 SSL/TLS 结合使用,因为密码仅发送 BASE64 编码。

      BASIC NON-PREEMPTIVE:这是一种非抢占式认证方式,即只有当服务器以401状态码拒绝请求时才添加认证信息,然后再用认证信息重复请求。

      DIGEST: Http 摘要认证。不需要使用 SSL/TLS。

      UNIVERSAL:在非抢占模式下结合基本和摘要身份验证,即在 401 响应的情况下,根据 WWW-Authenticate HTTP 标头中定义的身份验证请求使用适当的身份验证。

      要使用 HttpAuthenticationFeature,请构建它的实例并向客户端注册。例如;

      1) 基本认证方式

      HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic("username", "password");
      
      final Client client = ClientBuilder.newClient();
      client.register(feature);
      

      2) 基本身份验证:非抢占模式

      HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder()
                                          .nonPreemptive()
                                          .credentials("username", "password")
                                          .build();
      
      final Client client = ClientBuilder.newClient();
      client.register(feature);
      

      3) 通用模式

      //Universal builder having different credentials for different schemes
      HttpAuthenticationFeature feature = HttpAuthenticationFeature.universalBuilder()
      .credentialsForBasic("username1", "password1")
      .credentials("username2", "password2").build();
      
      final Client client = ClientBuilder.newClient();
      client.register(feature);
      

      【讨论】:

      • 谢谢,但我需要知道如何修改资源以使其了解请求来自超级用户还是普通用户。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-04-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-25
      • 1970-01-01
      • 2014-10-06
      相关资源
      最近更新 更多