Como criar métodos personalizados para uso em anotações de linguagem de expressão de segurança Spring

92

Gostaria de criar uma classe que adiciona métodos personalizados para uso em linguagem de expressão de segurança Spring para autorização baseada em método por meio de anotações.

Por exemplo, eu gostaria de criar um método personalizado como 'customMethodReturningBoolean' para ser usado de alguma forma assim:

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

Minha pergunta é esta. Se for possível, qual classe devo criar como subclasse para criar meus métodos personalizados, como eu faria para configurá-lo nos arquivos de configuração xml do spring e vir alguém me dar um exemplo de um método personalizado usado dessa forma?

Paul D. Eden
fonte
1
Não tenho tempo para digitar uma resposta agora, mas segui este guia e funcionou perfeitamente : baeldung.com/… Estou usando o Spring Security 5.1.1.
Paul

Respostas:

35

Você precisará criar uma subclasse de duas classes.

Primeiro, defina um novo manipulador de expressão de método

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

myMethodSecurityExpressionHandlerserá uma subclasse da DefaultMethodSecurityExpressionHandlerqual se sobrepõe createEvaluationContext(), definindo uma subclasse de MethodSecurityExpressionRootno MethodSecurityEvaluationContext.

Por exemplo:

@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
fonte
Hmm, parece uma boa ideia, mas todas as propriedades de DefaultMethodSecurityExpressionHandler são privadas sem acessadores, então eu estava curioso para saber como você estendeu a classe sem nenhum reflexo feio. Obrigado.
Joseph Lust
1
Você quer dizer trustResolver, etc? Todos eles têm setters em DefaultMethodSecurityExpressionHandler (pelo menos no Spring Security 3.0) Veja: static.springsource.org/spring-security/site/apidocs/org/…
sourcedelica
3
@ericacm Como você evita MethodSecurityExpressionRootser um pacote privado ?
C. Ross
175

Nenhuma das técnicas mencionadas funcionará mais. Parece que o Spring fez um grande esforço para evitar que os usuários substituíssem o SecurityExpressionRoot.

EDITAR 19/11/2014 Configure o Spring para usar anotações de segurança:

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

Crie um bean como este:

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

Em seguida, faça algo assim em seu jsp:

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

Ou anote um método:

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

Além disso, você pode usar Spring Expression Language em suas @PreAuthorizeanotações para acessar a autenticação atual, bem como argumentos de método.

Por exemplo:

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

Em seguida, atualize seu @PreAuthorizepara corresponder à nova assinatura de método:

@PreAuthorize("@mySecurityService.hasPermission(authentication, #foo)")
public void doSpecialStuff(String foo) { ... }
James Watkins
fonte
6
@Bosh em seu método hasPermission, você pode usar Authentication auth = SecurityContextHolder.getContext().getAuthentication();para obter o token de autenticação atual.
James Watkins
2
Obrigado James pela sua resposta. Devo definir mySecurityService no arquivo de configuração do spring?
WowBow de
2
Você não precisa definir mySecurityService em nenhum arquivo XML se tiver uma configuração de varredura de componente para o pacote em que o serviço está. Se você não tiver uma varredura de componente correspondente, deverá usar uma definição de bean xml. @PreAuthorize vem de org.springframework.security
James Watkins
3
Você pode precisar especificar o nome do bean para a anotação como este: @Component ("mySecurityService") ou use a anotação @Named.
James Watkins
1
@VJS Por favor, veja a edição que fiz. Você precisará configurar o spring para usar essas anotações. Estou surpreso que ninguém mais tenha reclamado desse importante detalhe ausente :)
James Watkins
14

Obrigado ericacm , mas não funciona por alguns motivos:

  • As propriedades de DefaultMethodSecurityExpressionHandler são privadas (visibilidade do reflexo é indesejável)
  • Pelo menos em meu Eclipse, não consigo resolver um objeto MethodSecurityEvaluationContext

As diferenças são que chamamos o método createEvaluationContext existente e, em seguida, adicionamos nosso objeto raiz personalizado. Finalmente, acabei de retornar um tipo de objeto StandardEvaluationContext, já que MethodSecurityEvaluationContext não resolveria no compilador (ambos são da mesma interface). Este é o código que tenho agora em produção.

Faça MethodSecurityExpressionHandler usar nossa raiz personalizada:

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

Isso substitui a raiz padrão estendendo SecurityExpressionRoot . Aqui, renomeei hasRole para 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);
    }

}

Por fim, atualize o securityContext.xml (e certifique-se de que seja referenciado no seu 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" />

Observação: a anotação @Secured não aceitará essa substituição, pois ela é executada por um manipulador de validação diferente. Portanto, no xml acima, desativei-os para evitar confusão posterior.

Joseph Lust
fonte