Spring MVC, сервисы RESTful и стратегия Apache Shiro

Я занят работой над проектом, которому нужен REST API, чтобы пользователи могли получать доступ к своей информации на сервере. У меня есть система, работающая с использованием Spring MVC и MappingJacksonJsonView для возврата JSON, где это необходимо.

Теперь я хочу включить безопасность в систему, чтобы, во-первых, убедиться, что пользователи аутентифицируют себя и имеют правильные разрешения для доступа к ресурсу, а во-вторых, я хочу иметь достаточно детальный контроль над данными, чтобы убедиться, что пользователь может получить доступ только к общедоступным части ресурса или, если у них есть правильные разрешения, весь ресурс.

Так, например:

Сценарий А. Пользователь А хочет получить доступ к своему списку сообщений, а также к своему имени и номеру телефона. Затем они будут аутентифицированы, и их разрешения позволят им получить доступ ко всей их собственной информации.

Сценарий Б. Пользователь А хочет получить доступ к номеру телефона пользователя Б, поэтому исключает список сообщений пользователя Б в ответе, поскольку у А нет разрешений на доступ к этой части информации.

  • Вопрос 1. Есть ли удобный способ сделать это с помощью Apache Shiro и Spring MVC?
  • Вопрос 2. Есть ли у кого-нибудь пример или ссылка на руководство о том, как это удалось сделать?
  • Вопрос 3. Какая схема разрешений с использованием Широ будет наиболее эффективной для такого детального контроля?

Я раньше не работал с Широ, но, судя по примерам и чтению документации, это возможно, и Широ лучше всего подходит для решения. Я открыт для других решений, хотя.

Редактировать: Одно решение, и я понятия не имею, возможно ли это с Широ и Джексоном, состоит в том, чтобы аннотировать свойства в моих POJO, которые я могу пометить как общедоступные или частные. Или, что еще лучше, отметьте их разрешением, необходимым для доступа к ним. Затем, когда Джексон распечатывает JSON-представление объекта, он может проверить разрешения для текущего свойства и решить, следует ли печатать свойство или нет, из его аннотации.


person Michael Gaylord    schedule 22.03.2011    source источник


Ответы (1)


Решение этой проблемы довольно простое. Я создал набор классов представления для Джексона:

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 {};
}

Затем я аннотировал все методы получения для каждой сущности, которую я хочу защитить, добавив следующее:

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

Вышеупомянутое правило означает, что только пользователи, прошедшие аутентификацию в системе, могут просматривать номер телефона пользователя.

Or

@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 для захвата objectMapper, когда Spring устанавливает ваш MappingJacksonJsonView .

Моя конфигурация 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, вызывается метод renderedMergedOutputModel. Если идентификатор пользователя указан в запросе, мы его сохраняем. Затем мы можем просмотреть карту модели и проверить, является ли каждое значение SecurePropertyInterface и получает ли оно максимальный уровень разрешений.

Эта стратегия, кажется, служит моим целям должным образом. Когда пользователи запрашивают список пользователей, они получают только аутентифицированные свойства, но когда пользователи запрашивают свои собственные данные, они получают только свои личные свойства.

Я подозреваю, что этот код можно улучшить, но я потратил слишком много времени, пытаясь сделать его менее сложным.

person Michael Gaylord    schedule 30.03.2011
comment
Вы можете сделать Class<?> и избавиться от предупреждений необработанного типа. - person Sanjay T. Sharma; 30.03.2011