在 Spring/Jersey 中授权用户

Authorize User in Spring/Jersey

我正在 Spring/Jersey 设置中工作,并试图找出如何授权用户仅访问属于他们的资源。我见过很多关于如何使用授权或广泛的基于角色的安全性来保护端点的示例,但是我还没有看到任何可以确保提交请求的用户是为了 his/her 自己的东西而提交的。

为清楚起见,假设我有一个 GET /account/1 端点。我只希望 ID 为 1 的用户能够访问该帐户信息,并且只有一次 he/she 通过身份验证。我已经使用 OAuth 处理了后半部分,但我无法弄清楚前半部分。

我该怎么做?

默认情况下,Spring 安全 SecurityContext is a thread local. So you can access it anywhere from the request thread, using the 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;
    }
}

如果你有很多这样的端点,并且你想保持干燥,你可以使用 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();
        }
    }
}

现在 Name Binding Annotation @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,并且我们不想再次访问数据库以在我们的资源方法中检索它。可以将我们在过滤器中获得的 User 放入请求上下文 (a full example here)

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 - 请参阅前面的 link)。您也可以 来注入用户,但这会变得有点复杂。

我确信可以使用 Spring Security 的 ACL 功能来实现这种类型的访问控制,但对于我这个 Jersey 用户来说,这种方式对我来说更直观。