如何访问由@RolesAllowed 保护的泽西岛资源
我们正在通过邮递员休息客户端测试在泽西岛开发的 REST Web 服务.它是一个 POST 方法,并带有 @RolesAllowed
注释.该方法的完整注释如下:
We were testing a REST webservice developed in jersey through postman rest client. It is a POST method and is annotated with @RolesAllowed
. The full annotation the method is as follows:
@POST
@Path("/configuration")
@RolesAllowed("admin")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
当我使用预期的 HTTP 正文内容请求此 http://baseurl/configuration
时,我得到了 403
响应(这是预期的,因为它只允许管理员使用看起来).
When I requested this http://baseurl/configuration
with the expected HTTP body content, I got 403
response(it is expected since it is allowed only for admin as it seems).
我的疑问是如何通过rest客户端以指定角色访问此服务.
My doubt is how to access this service with the specified role via rest client.
推荐答案
看起来您设置了 RolesAllowedDynamicFeature
,但您没有进行身份验证来设置用户和角色.RolesAllowedDynamicFeature
所做的是查找 SecurityContext
,并调用 SecurityContext.isUserInRole(<"admin">)
来查看是否SecurityContext
中的用户具有角色.
So it seems like you set up the RolesAllowedDynamicFeature
, but you have no authentication happening to set up the user and roles. What the RolesAllowedDynamicFeature
does is lookup the SecurityContext
, and calls the SecurityContext.isUserInRole(<"admin">)
to see if the user in the SecurityContext
has the role.
我想你不知道 SecurityContext
是如何设置的.有几种方法.第一种是通过servlet 身份验证机制.您可以在 Securing Web Applications 中从 Java 中查看更多信息EE 教程.
I imagine you don't know how the SecurityContext
is set. There are a couple of ways. The first is through the servlet authentication mechanism. You can see more at Securing Web Applications from the Java EE tutorial.
基本上你需要在服务器上设置一个安全领域或安全域.每个服务器都有自己特定的设置方式.您可以查看此处的示例或如何使用 Tomcat 来完成.
Basically you need to set up a security realm or security domain on the server. Every server has it's own specific way of setting it up. You can see an example here or how it would be done with Tomcat.
基本上,领域/域包含允许访问网络应用程序的用户.这些用户具有关联的角色.当 servlet 容器进行身份验证时,无论是 Basic 身份验证还是 Form 身份验证,它都会从凭据中查找用户,如果用户通过身份验证,则将用户及其角色与请求相关联.Jersey 收集这些信息并将其放入请求的 SecurityContext
中.
Basically the realm/domain contains the users allowed to access the web app. Those users have associated roles. When the servlet container does the authentication, whether it be Basic authentication or Form authentication, it looks up the user from the credentials, and if the user is authenticated, the user and its roles are associated with the request. Jersey gathers this information and puts it into the SecurityContext
for the request.
如果这看起来有点复杂,一个更简单的方法是忘记 servlet 容器身份验证并创建一个 Jersey 过滤器,您可以在其中自己设置 SecurityContext
.您可以在此处查看示例.您可以使用任何您想要的身份验证方案.重要的部分是使用用户信息设置 SecurityContext
,无论您从何处获取它,可能是访问数据存储的服务.
If this seems a bit complicated, an easier way to just forget the servlet container authentication and just create a Jersey filter, where you set the SecurityContext
yourself. You can see an example here. You can use whatever authentication scheme you want. The important part is setting the SecurityContext
with the user information, wherever you get it from, maybe a service that accesses a data store.
另请参阅:
- 保障泽西岛的休息服务
这是使用过滤器的第二个选项的完整示例.该测试由 Jersey 测试框架 运行.您可以按原样运行测试
Here is a complete example of the second option using the filter. The test is run by Jersey Test Framework. You can run the test as is
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));
}
}
这是运行测试所需的唯一依赖项
Here is the only dependency needed to run the test
<dependency>
<groupId>org.glassfish.jersey.test-framework.providers</groupId>
<artifactId>jersey-test-framework-provider-grizzly2</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
相关文章