How to authenticate users in Jersey

后端 未结 3 1875
被撕碎了的回忆
被撕碎了的回忆 2020-12-29 00:36

I am writing a RESTful application in Java using Jersey, and i need to authenticate users. I know i can specify the roles in the resource using the annotations @RolesAllowed

相关标签:
3条回答
  • 2020-12-29 01:14

    There are two things we need to address

    1. Authentication - Checking if the user is really the one it claims to be
    2. Authorization - If the authenticated user has the privilege to access the given method

    To do both authentication and authorization, we need a data store that store that stores the following mapping:

    1. Mapping between user and its password
    2. Mapping between roles and users
    3. Mapping between roles and permissions

    Here the first mapping is required for authentication and the other two mappings are used for authorization.

    Also, note that we need to do authentication and authorization for every API call. So we will be doing a lot of read operations.

    Hence, usually a directory server or Ldap server such as Apache DS is used to store these mappings because a directory server is a read optimised data store.

    In a RESTful application usually a filter is used to extract the username and password from the request header, and do the authentication with the Ldap server. IF the authenication is successful, the next step is to extract the permissions of the user from the Ldap server by consulting the user-role and role-permission mappings. If the user is authorized, only in that case the control flows to the actual API business logic.

    Refer to this answer for details.

    0 讨论(0)
  • 2020-12-29 01:17

    I know i can specify the roles in the resource using the annotations @RolesAllowed, but i can't understand how a user is associated to a specific role

    The role information is stored in the DB. Assuming say you have a User that models the USER and ROLES table in the DB

    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; }
    }
    

    You would get the User inside a Jersey filter. This is also where you would authenticate.

    @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(ContainerRequestFilter filter) {
            // 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 = filter.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 = filter.getSecurityContext();               // (4)
            filter.setSecurityContext(new BasicSecurityConext(user, oldContext.isSecure()));
        }
    }
    

    What you're doing here is:

    1. Parsing the Basic Auth Authorization header
    2. Getting the User with the username
    3. Doing your authentication
    4. Setting a new SecurityContext.

    The BasicSecurityContext is shown below. This is where you will associate roles with the user.

    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);
       }
    }
    

    If you look at the bottom at the isUserInRole. What will happen is that Jersey will grab the @RolesAllowed annotation from the resource method or class, grab the values, then pass them to the isUserInRole. If it returns true, then the user is authorized. In pseudo-code

    @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();
    

    This is just pseudo-code, but it shows how Jersey handles the authorizaiton, using the @RolesAllowed, the SecurityContext, and how you implement the isUserInRole.

    This authorization feature is not automatically turned on. You need to turn it on yourself. To do so, simply register the RolesAllowedDynamicFeature

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

    One thing to note here is that in all of the above, we are implementing our basic authentication and setting of the security context. There is nothing really wrong with this. But if you are using the servlet container authentication mechanism, Jersey will actually take the auth information from the HttpServletRequest. The HttpServletRequest has a getUserPrincipal() method and a isUserInRole method. Jersey will use these to delegate in the SecurityContext. So if you are user the container authentication, then you don't really need to implement anything. You just need to register the RolesAllowedDynamicFeature

    If you want to use your container's authentication mechanism, you should consult your server's documentation. After having set up a realm in with your server, you will then need to configure the web.xml with the security information. There's an example in the link below. You should also find this information in the Java EE docs under the web security section.

    See also:

    • Filter and Interceptors to learn more about working with filters.
    • Security for a little more info on working with security in Jersey.
    • A better implementation of the basic auth filter
    0 讨论(0)
  • 2020-12-29 01:26

    HttpAuthenticationFeature class provides HttpBasic and Digest client authentication capabilities. The feature work in one of 4 modes;

    BASIC: It’s preemptive authentication way i.e. information is send always with each HTTP request. This mode must be combined with usage of SSL/TLS as the password is send only BASE64 encoded.

    BASIC NON-PREEMPTIVE: It’s non-preemptive authentication way i.e. auth information is added only when server refuses the request with 401 status code and then the request is repeated with authentication information.

    DIGEST: Http digest authentication. Does not require usage of SSL/TLS.

    UNIVERSAL: Combination of basic and digest authentication in non-preemptive mode i.e. in case of 401 response, an appropriate authentication is used based on the authentication requested as defined in WWW-Authenticate HTTP header.

    To use HttpAuthenticationFeature, build an instance of it and register with client. For example;

    1) Basic authentication mode

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

    2) Basic authentication : non-prempitive mode

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

    3) Universal mode

    //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);
    
    0 讨论(0)
提交回复
热议问题