Как создать собственные методы для использования в аннотациях языка выражений безопасности Spring

92

Я хотел бы создать класс, который добавляет настраиваемые методы для использования в языке выражений безопасности Spring для авторизации на основе методов с помощью аннотаций.

Например, я хотел бы создать собственный метод, такой как customMethodReturningBoolean, чтобы использовать его как-то так:

  @PreAuthorize("customMethodReturningBoolean()")
  public void myMethodToSecure() { 
    // whatever
  }

У меня такой вопрос. Если это возможно, какой класс я должен создать подкласс для создания моих пользовательских методов, как я могу настроить его в файлах конфигурации spring xml и дать мне пример пользовательского метода, используемого таким образом?

Пол Д. Иден
источник
1
У меня нет времени набирать ответ прямо сейчас, но я следовал этому руководству, и оно отлично сработало: baeldung.com/… Я использую Spring Security 5.1.1.
Paul

Ответы:

35

Вам нужно будет создать подклассы двух классов.

Сначала установите новый обработчик выражения метода

<global-method-security>
  <expression-handler ref="myMethodSecurityExpressionHandler"/>
</global-method-security>

myMethodSecurityExpressionHandlerбудет подклассом, DefaultMethodSecurityExpressionHandlerкоторый переопределяет createEvaluationContext(), устанавливая подкласс MethodSecurityExpressionRootна MethodSecurityEvaluationContext.

Например:

@Override
public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
    MethodSecurityEvaluationContext ctx = new MethodSecurityEvaluationContext(auth, mi, parameterNameDiscoverer);
    MethodSecurityExpressionRoot root = new MyMethodSecurityExpressionRoot(auth);
    root.setTrustResolver(trustResolver);
    root.setPermissionEvaluator(permissionEvaluator);
    root.setRoleHierarchy(roleHierarchy);
    ctx.setRootObject(root);

    return ctx;
}
sourcedelica
источник
Хм, звучит неплохо, но все свойства DefaultMethodSecurityExpressionHandler являются частными без аксессоров, поэтому мне было любопытно, как вы расширили класс без какого-либо уродливого отражения. Спасибо.
Джозеф Ласт
1
Вы имеете в виду trustResolver и т. Д.? У всех них есть сеттеры в DefaultMethodSecurityExpressionHandler (по крайней мере, в Spring Security 3.0) См .: static.springsource.org/spring-security/site/apidocs/org/…
sourcedelica
3
@ericacm Как Вы обходите MethodSecurityExpressionRootбыть частным пакет ?
К. Росс
176

Ни один из упомянутых методов больше не будет работать. Похоже, Spring приложила немало усилий, чтобы помешать пользователям переопределять SecurityExpressionRoot.

РЕДАКТИРОВАТЬ 11/19/14 Настройте Spring для использования аннотаций безопасности:

<beans ... xmlns:sec="http://www.springframework.org/schema/security" ... >
...
<sec:global-method-security pre-post-annotations="enabled" />

Создайте такой bean-компонент:

@Component("mySecurityService")
public class MySecurityService {
    public boolean hasPermission(String key) {
        return true;
    }
}

Затем сделайте что-то вроде этого в своем jsp:

<sec:authorize access="@mySecurityService.hasPermission('special')">
    <input type="button" value="Special Button" />
</sec:authorize>

Или добавьте аннотацию к методу:

@PreAuthorize("@mySecurityService.hasPermission('special')")
public void doSpecialStuff() { ... }

Кроме того, вы можете использовать Spring Expression Language в своих @PreAuthorizeаннотациях для доступа к текущей аутентификации, а также к аргументам метода.

Например:

@Component("mySecurityService")
public class MySecurityService {
    public boolean hasPermission(Authentication authentication, String foo) { ... }
}

Затем обновите свой, @PreAuthorizeчтобы он соответствовал новой сигнатуре метода:

@PreAuthorize("@mySecurityService.hasPermission(authentication, #foo)")
public void doSpecialStuff(String foo) { ... }
Джеймс Уоткинс
источник
6
@Bosh в вашем методе hasPermission, вы можете использовать его Authentication auth = SecurityContextHolder.getContext().getAuthentication();для получения текущего токена аутентификации.
Джеймс Уоткинс,
2
Спасибо, Джеймс, за ответ. Должен ли я определять mySecurityService в файле конфигурации spring?
WowBow
2
Вам не нужно определять mySecurityService в каком-либо XML-файле, если у вас есть настройка сканирования компонентов для пакета, в котором находится служба. Если у вас нет подходящего сканирования компонентов, вы должны использовать определение bean-компонента xml. @PreAuthorize исходит от org.springframework.security
Джеймс Уоткинс,
3
Возможно, вам потребуется указать имя bean-компонента для аннотации, например: @Component ("mySecurityService") или использовать аннотацию @Named.
Джеймс Уоткинс
1
@VJS Пожалуйста, посмотрите мои изменения. Для использования этих аннотаций вам нужно будет настроить spring. Я удивлен, что никто больше не жаловался на эту важную недостающую деталь :)
Джеймс Уоткинс
14

Спасибо ericacm , но он не работает по нескольким причинам:

  • Свойства DefaultMethodSecurityExpressionHandler являются частными (кладжи видимости отражения нежелательны)
  • По крайней мере, в моем Eclipse я не могу разрешить объект MethodSecurityEvaluationContext

Отличия в том, что мы вызываем существующий метод createEvaluationContext, а затем добавляем наш настраиваемый корневой объект. Наконец, я только что вернул объектный тип StandardEvaluationContext, так как MethodSecurityEvaluationContext не разрешается в компиляторе (они оба из одного интерфейса). Это код, который у меня сейчас в разработке.

Сделайте MethodSecurityExpressionHandler использовать наш пользовательский корень:

public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler  {

    // parent constructor
    public CustomMethodSecurityExpressionHandler() {
        super();
    }

    /**
     * Custom override to use {@link CustomSecurityExpressionRoot}
     * 
     * Uses a {@link MethodSecurityEvaluationContext} as the <tt>EvaluationContext</tt> implementation and
     * configures it with a {@link MethodSecurityExpressionRoot} instance as the expression root object.
     */
    @Override
    public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
        // due to private methods, call original method, then override it's root with ours
        StandardEvaluationContext ctx = (StandardEvaluationContext) super.createEvaluationContext(auth, mi);
        ctx.setRootObject( new CustomSecurityExpressionRoot(auth) );
        return ctx;
    }
}

Это заменяет корень по умолчанию, расширяя SecurityExpressionRoot . Здесь я переименовал hasRole в hasEntitlement:

public class CustomSecurityExpressionRoot extends SecurityExpressionRoot  {

    // parent constructor
    public CustomSecurityExpressionRoot(Authentication a) {
        super(a);
    }

    /**
     * Pass through to hasRole preserving Entitlement method naming convention
     * @param expression
     * @return boolean
     */
    public boolean hasEntitlement(String expression) {
        return hasRole(expression);
    }

}

Наконец, обновите securityContext.xml (и убедитесь, что на него есть ссылка из вашего файла applcationContext.xml):

<!-- setup method level security using annotations -->
<security:global-method-security
        jsr250-annotations="disabled"
        secured-annotations="disabled"
        pre-post-annotations="enabled">
    <security:expression-handler ref="expressionHandler"/>
</security:global-method-security>

<!--<bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">-->
<bean id="expressionHandler" class="com.yourSite.security.CustomMethodSecurityExpressionHandler" />

Примечание: аннотация @Secured не примет это переопределение, поскольку она проходит через другой обработчик проверки. Итак, в приведенном выше xml я отключил их, чтобы предотвратить дальнейшую путаницу.

Джозеф похоть
источник