Spring MVC、RESTful 服务和Apache Shiro 策略

发布于 2024-10-24 17:46:07 字数 894 浏览 0 评论 0原文

我正忙于一个项目,该项目需要 REST API 来允许用户访问服务器上的信息。我让系统使用 Spring MVC 和 MappingJacksonJsonView 来在需要时返回 JSON。

我现在希望在系统中包含安全性,首先确保用户进行身份验证并拥有访问资源的正确权限,其次我希望对数据进行足够细粒度的控制,以确保用户只能访问公开可用的资源资源的一部分,或者如果他们具有整个资源的正确权限。

例如:

场景 A:用户 A 希望访问他们的消息列表以及他们的姓名和电话号码。然后他们将获得身份验证,并且他们的权限将允许他们访问自己的所有信息。

场景 B:用户 A 希望访问用户 B 的电话号码 - 因此在响应中排除用户 B 的消息列表,因为 A 无权访问该部分信息。

  • Q1:有没有一种简洁的方法 关于使用 Apache Shiro 执行此操作 和Spring MVC?
  • Q2:有人吗 有示例或教程链接 关于别人如何取得成就 这?
  • Q3:什么样的权限 方案,使用Shiro,将是最 有效地允许这种类型的罚款 粒度控制?

我以前没有使用过 Shiro,但从使用示例和阅读文档来看,这似乎是可能的,而且 Shiro 最适合该解决方案。不过,我对其他解决方案持开放态度。

编辑:一个解决方案,我不知道 Shiro & 是否可以实现这一点。 Jackson 的任务是在我的 POJO 中注释属性,我可以将其标记为公共或私有。或者更好的是,用访问它们所需的权限来标记它们。然后,当 Jackson 打印出对象的 JSON 表示形式时,它可以检查当前属性的权限,并决定是否从其注释中打印该属性。

I am busy working on a project that needs a REST API to allow users to access their information on a server. I have the system working using Spring MVC and the MappingJacksonJsonView to return JSON where needed.

I now want to include security in the system to firstly, ensure users authenticate themselves and have the correct permissions to access a resource and secondly I want to have enough fine-grained control of the data to make sure that a user can only access publicly available portions of a resource or if they have the correct permissions the entire resource.

So for example:

Scenario A: User A wants to access their list of messages, as well as their name and phone number. They would then get authenticated and their permissions would allow them to access all of their own information.

Scenario B: User A wants to access User B's phone number - therefore excluding User B's list of messages in the response as A doesn't have permissions to access that portion of info.

  • Q1: Is there a neat way of going
    about doing this using Apache Shiro
    and Spring MVC?
  • Q2: Does anybody
    have an example or link to a tutorial
    on how somebody else has achieved
    this?
  • Q3: What sort of permissions
    scheme, using Shiro, would be most
    efficient to allow this type of fine
    grained control?

I haven't worked with Shiro before, but from playing with examples and reading the documentation it looks like this is possible and that Shiro would be the best fit for the solution. I am open to other solutions though.

Edit: One solution, and I have no idea if this is possible with Shiro & Jackson, is to annotate properties in my POJO's that I can mark as public or private. Or even better, mark them with the permission necessary to access them. Then when Jackson prints out the JSON representation of the object it can inspect the permissions for the current property and decide whether to print the property or not from its annotation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

杀手六號 2024-10-31 17:46:07

对于这个问题,解决方案非常简单。我为 Jackson 创建了一组视图类:

public class SecureViews {
    public static class Public{};
    public static class Authenticated extends Public{};
    public static class User extends Authenticated{};
    public static class Internal extends User {};
}

然后,我通过添加以下内容来注释我想要保护的每个实体的所有 getter 方法:

@JsonView(SecureViews.Authenticated.class)
public String getPhoneNumber() {
    return phoneNumber;
}

上述规则的含义是,只有在系统内经过身份验证的用户才能查看用户的电话数字。

或者,

@JsonView(SecureViews.User.class)
public List<Event> getEvents() {
    return Collections.unmodifiableList(events);
}

然后我创建了一个接口并在所有安全实体中实现了该接口:

public interface SecurePropertyInterface {
    Class<?> getMaxPermissionLevel(Long userID);
}

下面是该方法的实现:

public Class<?> getMaxPermissionLevel(Long userID) {
        if (userID != null && userID == uid) {
            return SecureViews.User.class;
        }
        if (SecurityUtils.getSubject().isAuthenticated()) {
            return SecureViews.Authenticated.class;
        }

        return SecureViews.Public.class;
    }

现在来看看魔术吧。扩展 MappingJacksonJsonView 并重写以下方法:

@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
            HttpServletResponse response) throws Exception {

        Long userID = CTConversionUtils.convertToLong(request.getParameter("id"));
        model = (Map<String, Object>) super.filterModel(model);

        Class<?> viewClass = SecureViews.Public.class;

        if(SecurityUtils.getSubject().isAuthenticated()) {
            viewClass = SecureViews.Authenticated.class;
        }

        for (Entry<String, Object> modelEntry : model.entrySet()) {
            if (modelEntry.getValue() instanceof SecurePropertyInterface) {
                viewClass = ((SecurePropertyInterface)modelEntry.getValue()).getMaxPermissionLevel(userID);
            }
        }
        objectMapper.viewWriter(viewClass).writeValue(getJsonGenerator(response), model);
    }

您还可以重写 setObjectMapper 方法,以在 Spring 设置您的 MappingJacksonJsonView 时捕获 objectMapper。

我的 Spring 配置如下所示:

<bean
        class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"
        p:order="1">
        <property name="mediaTypes">
            <map>
                <entry key="json" value="*/*" />
            </map>
        </property>
        <property name="defaultViews">
            <list>
                <bean
                    class="com.bytesizecreations.connecttext.json.SecureMappingJacksonJsonView">
                    <property name="objectMapper">
                        <bean
                            class="org.codehaus.jackson.map.ObjectMapper" />
                    </property>
                </bean>
            </list>
        </property>
    </bean>

那么我们现在有什么?每次需要将响应反序列化为 JSON 时,都会调用 renderMergedOutputModel 方法。如果用户的 ID 在请求中,我们将存储该 ID。然后我们可以循环遍历模型映射并检查每个值是否是 SecurePropertyInterface 以及是否获得其最大权限级别。

这个策略似乎很符合我的目的。当用户请求用户列表时,他们只能获得经过身份验证的属性,但是当用户请求自己的详细信息时,他们只能获得他们的私有属性。

我怀疑这段代码可以进一步改进,但我花了太多时间试图让它不那么复杂。

The solution is pretty simple for this problem. I created a set of view classes for Jackson:

public class SecureViews {
    public static class Public{};
    public static class Authenticated extends Public{};
    public static class User extends Authenticated{};
    public static class Internal extends User {};
}

I then annotated all the getter methods for each entity I want to be secured by adding the following:

@JsonView(SecureViews.Authenticated.class)
public String getPhoneNumber() {
    return phoneNumber;
}

What the above rule means is that only users who are authenticated within the system my view a user's phone number.

Or

@JsonView(SecureViews.User.class)
public List<Event> getEvents() {
    return Collections.unmodifiableList(events);
}

I then created an interface and implemented that interface in all my secure entities:

public interface SecurePropertyInterface {
    Class<?> getMaxPermissionLevel(Long userID);
}

And here is the implementation of that method:

public Class<?> getMaxPermissionLevel(Long userID) {
        if (userID != null && userID == uid) {
            return SecureViews.User.class;
        }
        if (SecurityUtils.getSubject().isAuthenticated()) {
            return SecureViews.Authenticated.class;
        }

        return SecureViews.Public.class;
    }

Now for the magic. Extend MappingJacksonJsonView and override the following method:

@Override
protected void renderMergedOutputModel(Map<String, Object> model, HttpServletRequest request,
            HttpServletResponse response) throws Exception {

        Long userID = CTConversionUtils.convertToLong(request.getParameter("id"));
        model = (Map<String, Object>) super.filterModel(model);

        Class<?> viewClass = SecureViews.Public.class;

        if(SecurityUtils.getSubject().isAuthenticated()) {
            viewClass = SecureViews.Authenticated.class;
        }

        for (Entry<String, Object> modelEntry : model.entrySet()) {
            if (modelEntry.getValue() instanceof SecurePropertyInterface) {
                viewClass = ((SecurePropertyInterface)modelEntry.getValue()).getMaxPermissionLevel(userID);
            }
        }
        objectMapper.viewWriter(viewClass).writeValue(getJsonGenerator(response), model);
    }

You can also override the setObjectMapper method to capture the objectMapper when Spring sets up your MappingJacksonJsonView .

My Spring config looks like this:

<bean
        class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver"
        p:order="1">
        <property name="mediaTypes">
            <map>
                <entry key="json" value="*/*" />
            </map>
        </property>
        <property name="defaultViews">
            <list>
                <bean
                    class="com.bytesizecreations.connecttext.json.SecureMappingJacksonJsonView">
                    <property name="objectMapper">
                        <bean
                            class="org.codehaus.jackson.map.ObjectMapper" />
                    </property>
                </bean>
            </list>
        </property>
    </bean>

So what do we have here now? Every time a response needs to be deserialized to JSON the renderedMergedOutputModel method is called. If the user's id is in the request we store that. We can then loop through the model map and check if each value is a SecurePropertyInterface and if it is get its max permission level.

This strategy seems to serve my purposes properly. When users request a list of users then they get only the authenticated properties, but when users request their own details they get only their private properties.

I suspect this code can be improved further though, but I have spent way too much time trying to make it less complicated.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文