【问题标题】:How to access Jersey resource secured by @RolesAllowed如何访问由@RolesAllowed 保护的泽西岛资源
【发布时间】:2015-12-25 08:29:39
【问题描述】:

我们正在通过邮递员休息客户端测试在泽西岛开发的 REST Web 服务。它是一个 POST 方法,并带有 @RolesAllowed 注释。该方法的完整注释如下:

@POST
@Path("/configuration")
@RolesAllowed("admin")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)

当我使用预期的 HTTP 正文内容请求此 http://baseurl/configuration 时,我得到了 403 响应(这是预期的,因为它似乎只允许管理员使用)。

我的疑问是如何通过rest客户端以指定角色访问此服务。

【问题讨论】:

    标签: java jersey jersey-2.0 postman


    【解决方案1】:

    看来您设置了RolesAllowedDynamicFeature,但您没有进行身份验证来设置用户和角色。 RolesAllowedDynamicFeature 所做的是查找SecurityContext,并调用SecurityContext.isUserInRole(<"admin">) 以查看SecurityContext 中的用户是否具有该角色。

    我想你不知道SecurityContext 是如何设置的。有几种方法。第一种是通过servlet 身份验证机制。您可以在 Java EE 教程中的 Securing Web Applications 上看到更多信息。

    基本上你需要在服务器上设置一个安全领域或安全域。每个服务器都有自己特定的设置方式。您可以查看example here 或如何使用 Tomcat。

    基本上,领域/域包含允许访问网络应用程序的用户。这些用户具有关联的角色。当 servlet 容器进行身份验证时,无论是 Basic 身份验证还是 Form 身份验证,它都会从凭据中查找用户,如果用户通过身份验证,则将用户及其角色与请求相关联。 Jersey 收集这些信息并将其放入SecurityContext 以供请求。

    如果这看起来有点复杂,那么一种更简单的方法是忘记 servlet 容器身份验证并创建一个 Jersey 过滤器,您自己在其中设置 SecurityContext。你可以看到一个例子here。您可以使用任何您想要的身份验证方案。重要的部分是使用用户信息设置SecurityContext,无论您从哪里获得它,也许是访问数据存储的服务。

    另请参阅:

    更新

    这是使用过滤器的第二个选项的完整示例。测试由Jersey Test Framework 运行。您可以按原样运行测试

    import java.io.IOException;
    import java.nio.charset.Charset;
    import java.security.Principal;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.annotation.Priority;
    import javax.annotation.security.RolesAllowed;
    import javax.inject.Inject;
    import javax.ws.rs.GET;
    import javax.ws.rs.Path;
    import javax.ws.rs.Priorities;
    import javax.ws.rs.WebApplicationException;
    import javax.ws.rs.container.ContainerRequestContext;
    import javax.ws.rs.container.ContainerRequestFilter;
    import javax.ws.rs.core.HttpHeaders;
    import javax.ws.rs.core.Response;
    import javax.ws.rs.core.Response.Status;
    import javax.ws.rs.core.SecurityContext;
    import javax.ws.rs.ext.Provider;
    import javax.xml.bind.DatatypeConverter;
    
    import org.glassfish.hk2.utilities.binding.AbstractBinder;
    import org.glassfish.jersey.internal.util.Base64;
    import org.glassfish.jersey.server.ResourceConfig;
    import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
    import org.glassfish.jersey.test.JerseyTest;
    
    import static junit.framework.Assert.*;
    import org.junit.Test;
    
    public class BasicAuthenticationTest extends JerseyTest {
    
        @Provider
        @Priority(Priorities.AUTHENTICATION)
        public static class BasicAuthFilter implements ContainerRequestFilter {
            
            private static final Logger LOGGER = Logger.getLogger(BasicAuthFilter.class.getName());
    
            @Inject
            private UserStore userStore;
    
            @Override
            public void filter(ContainerRequestContext requestContext) throws IOException {
                String authentication = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
                if (authentication == null) {
                    throw new AuthenticationException("Authentication credentials are required");
                }
    
                if (!authentication.startsWith("Basic ")) {
                    return;
                }
                
                authentication = authentication.substring("Basic ".length());
                String[] values = new String(DatatypeConverter.parseBase64Binary(authentication), 
                                             Charset.forName("ASCII")).split(":");
                if (values.length < 2) {
                    throw new WebApplicationException(400);
                }
                
                String username = values[0];
                String password = values[1];
                
                LOGGER.log(Level.INFO, "{0} - {1}", new Object[]{username, password});
                
                User user = userStore.getUser(username);
                if (user == null) {
                    throw new AuthenticationException("Authentication credentials are required"); 
                }
                
                if (!user.password.equals(password)) {
                    throw new AuthenticationException("Authentication credentials are required");
                }
                
                requestContext.setSecurityContext(new MySecurityContext(user));
            }
        }
        
        static class MySecurityContext implements SecurityContext {
            
            private final User user;
            
            public MySecurityContext(User user) {
                this.user = user;
            }
    
            @Override
            public Principal getUserPrincipal() {
                return new Principal() {
                    @Override
                    public String getName() {
                        return user.username;
                    }
                };
            }
    
            @Override
            public boolean isUserInRole(String role) {
                return role.equals(user.role);
            }
    
            @Override
            public boolean isSecure() { return true; }
    
            @Override
            public String getAuthenticationScheme() {
                return "Basic";
            }
            
        }
    
        static class AuthenticationException extends WebApplicationException {
    
            public AuthenticationException(String message) {
                super(Response
                        .status(Status.UNAUTHORIZED)
                        .header("WWW-Authenticate", "Basic realm=\"" + "Dummy Realm" + "\"")
                        .type("text/plain")
                        .entity(message)
                        .build());
            }
        }
    
        class User {
    
            public final String username;
            public final String role;
            public final String password;
    
            public User(String username, String password, String role) {
                this.username = username;
                this.password = password;
                this.role = role;
            }
        }
    
        class UserStore {
    
            public final Map<String, User> users = new ConcurrentHashMap<>();
    
            public UserStore() {
                users.put("peeskillet", new User("peeskillet", "secret", "USER"));
                users.put("stackoverflow", new User("stackoverflow", "superSecret", "ADMIN"));
            }
    
            public User getUser(String username) {
                return users.get(username);
            }
        }
        
        private static final String USER_RESPONSE = "Secured User Stuff";
        private static final String ADMIN_RESPONSE = "Secured Admin Stuff";
        private static final String USER_ADMIN_STUFF = "Secured User Admin Stuff";
        
        @Path("secured")
        public static class SecuredResource {
            
            @GET
            @Path("userSecured")
            @RolesAllowed("USER")
            public String getUser() {
                return USER_RESPONSE;
            }
            
            @GET
            @Path("adminSecured")
            @RolesAllowed("ADMIN")
            public String getAdmin() {
                return ADMIN_RESPONSE;
            }
            
            @GET
            @Path("userAdminSecured")
            @RolesAllowed({"USER", "ADMIN"})
            public String getUserAdmin() {
                return USER_ADMIN_STUFF;
            }
        }
    
        @Override
        public ResourceConfig configure() {
            return new ResourceConfig(SecuredResource.class)
                    .register(BasicAuthFilter.class)
                    .register(RolesAllowedDynamicFeature.class)
                    .register(new AbstractBinder(){
                @Override
                protected void configure() {
                    bind(new UserStore()).to(UserStore.class);
                }
            });
        }
        
        static String getBasicAuthHeader(String username, String password) {
            return "Basic " + Base64.encodeAsString(username + ":" + password);
        }
        
        @Test
        public void should_return_403_with_unauthorized_user() {
            Response response = target("secured/userSecured")
                    .request()
                    .header(HttpHeaders.AUTHORIZATION, 
                            getBasicAuthHeader("stackoverflow", "superSecret"))
                    .get();
            assertEquals(403, response.getStatus());
        }
        
        @Test
        public void should_return_200_response_with_authorized_user() {
            Response response = target("secured/userSecured")
                    .request()
                    .header(HttpHeaders.AUTHORIZATION, 
                            getBasicAuthHeader("peeskillet", "secret"))
                    .get();
            assertEquals(200, response.getStatus());
            assertEquals(USER_RESPONSE, response.readEntity(String.class));
        }
        
        @Test
        public void should_return_403_with_unauthorized_admin() {
            Response response = target("secured/adminSecured")
                    .request()
                    .header(HttpHeaders.AUTHORIZATION, 
                            getBasicAuthHeader("peeskillet", "secret"))
                    .get();
            assertEquals(403, response.getStatus());
        }
        
        @Test
        public void should_return_200_response_with_authorized_admin() {
            Response response = target("secured/adminSecured")
                    .request()
                    .header(HttpHeaders.AUTHORIZATION, 
                            getBasicAuthHeader("stackoverflow", "superSecret"))
                    .get();
            assertEquals(200, response.getStatus());
            assertEquals(ADMIN_RESPONSE, response.readEntity(String.class));
        }
    }
    

    这是运行测试所需的唯一依赖项

    <dependency>
        <groupId>org.glassfish.jersey.test-framework.providers</groupId>
        <artifactId>jersey-test-framework-provider-grizzly2</artifactId>
        <version>${jersey2.version}</version>
        <scope>test</scope>
    </dependency>
    

    【讨论】:

    • 感谢您提供的信息。在我的情况下,Web 服务是由另一个团队开发和部署的,我对 src 具有只读访问权限,我从那里获得了所指定的注释。我的疑问是在通过 restclient 访问此资源时,我们将如何在请求中提供角色信息。正如你提到的,它会提示输入角色吗?
    • 如果其余的服务是其他团队开发的,那么你需要问他们认证方案是什么。没有一种方法可以进行身份​​验证。如果他们使用基本身份验证,那么您应该将Authorization 标头设置为Basic &lt;base64(username:password)&gt;。如果是别的东西,那么你需要问他们如何发送它。我们无法知道。
    • 一般客户端不应该发送角色。客户端发送凭据,服务器端查找客户端凭据并进行身份验证,然后设置 SecurityContext,从用户的数据存储查找中获取角色。该角色应与数据存储中的用户相关联
    • 如果你看看我在底部的测试,你会看到所有的请求只发送用户名和密码。请求通过过滤器,并在UserStore 中查找用户,其中User 与角色相关联。
    • ok @peeskillet。你之前的评论是对的,客户端不需要发送角色,只需要传递凭据。我需要了解有关 REST 安全性的更多信息。感谢您的意见。最好我会问开发人员
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-08
    • 1970-01-01
    • 2015-03-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多