我正在Spring/Jersey设置中工作,并试图弄清楚如何授权用户仅访问属于他们的资源。我已经看到了许多关于如何使用授权或基于广泛角色的安全性来保护端点的示例,但是我还没有看到任何能够确保提交请求的用户是为了他/她自己的东西而提交请求的示例。
为了清楚起见,假设我有一个GET/account/1端点。我希望只有ID为1的用户能够访问该帐户信息,并且只有在他/她经过身份验证后才能访问。我已经使用OAuth处理了后一部分,但我不能弄清楚前一部分。
我该怎么做呢?
默认情况下,Spring Security的SecurityContext
是线程本地。因此,您可以使用SecurityContextHolder
从请求线程的任何地方访问它。从SecurityContext
中可以获得UserDetails
,它将具有用户名。
因此,由于Jersey请求处理发生在同一个请求线程中,因此您应该能够访问此信息。从Jersey组件中,您可以使用任何想要的服务来获取实际的用户域对象,并检查同一用户。例如,你可以输入
@Path("/accounts")
public class AccountsResource {
@Inject
private UserService userService;
@GET
@Path("/{id}")
@Produces("application/json")
public User get(@PathParam("id") Long id) {
User user = userService.getUser(id);
// if user == null throw 404
UserDetails userDetails
= (UserDetails) SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
if (!userDetails.getUsername().equals(user.getUsername())) {
throw new ForbiddenException();
}
return user;
}
}
如果你有很多这样的端点,并且你想保持它DRY,你可以使用Jersey过滤器来处理访问控制过程。
@Provider
@AccessFiltered
@Priority(Priorities.AUTHORIZATION)
public class UserAccessFilter implements ContainerRequestFilter {
@Inject
private UserService userService;
@Override
public void filter(ContainerRequestContext requestContext) {
List<String> params = requestContext.getUriInfo().getPathParameters().get("id");
Long id = Long.parseLong(params.get(0));
User user = userService.getUser(id);
// if user == null throw 404
UserDetails userDetails
= (UserDetails) SecurityContextHolder.getContext()
.getAuthentication().getPrincipal();
if (!userDetails.getUsername().equals(user.getUsername())) {
throw new ForbiddenException();
}
}
}
现在使用名称绑定注释@AccessFiltered
@NameBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface AccessFiltered {
}
我们可以过滤我们注释过的方法
@GET
@Path("/{id}")
@AccessFiltered
@Produces("application/json")
public User get(@PathParam("id") Long id) {
}
现在假设我们想要访问那个User
,并且我们不想在我们的资源方法中再次访问DB来检索它。可以将我们在过滤器中获得的User
放入请求上下文中(这里有一个完整的示例)
public class UserAccessFilter implements ContainerRequestFilter {
public static final String USER_FILTER_PROPERTY = "UserAccessFilter.User";
@Override
public void filter(ContainerRequestContext requestContext) {
...
User user = userService.getUser(id);
...
requestContext.setProperty(USER_FILTER_PROPERTY, user);
}
}
然后我们可以将User
注入到我们的资源方法
@GET
@Path("/{id}")
@AccessFiltered
@Produces("application/json")
public User get(@Context User user) {
return user;
}
还有一个步骤来配置它(即Factory
-参见前面的链接)。您也可以创建一个自定义注释来注入用户,但这会变得有点复杂。
我确信这种类型的访问控制可以使用Spring Security的ACL功能来实现,但对于我作为Jersey用户来说,这种方式对我来说更直观。