Cómo crear métodos personalizados para usar en anotaciones de lenguaje de expresión de seguridad de Spring

92

Me gustaría crear una clase que agregue métodos personalizados para usar en el lenguaje de expresión de seguridad de Spring para la autorización basada en métodos a través de anotaciones.

Por ejemplo, me gustaría crear un método personalizado como 'customMethodReturningBoolean' para usarlo de alguna manera así:

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

Mi pregunta es esta. Si es posible, ¿qué clase debería subclase para crear mis métodos personalizados, cómo haría para configurarlo en los archivos de configuración xml de primavera y si alguien me da un ejemplo de un método personalizado utilizado de esta manera?

Paul D. Eden
fuente
1
No tengo tiempo para escribir una respuesta en este momento, pero seguí esta guía y funcionó de manera brillante: baeldung.com/… Estoy usando Spring Security 5.1.1.
Paul

Respuestas:

35

Necesitará subclasificar dos clases.

Primero, establezca un nuevo controlador de expresión de método

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

myMethodSecurityExpressionHandlerserá una subclase de la DefaultMethodSecurityExpressionHandlerque anula createEvaluationContext(), estableciendo una subclase de MethodSecurityExpressionRooten el MethodSecurityEvaluationContext.

Por ejemplo:

@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;
}
fuentedelica
fuente
Hmm, suena como una buena idea, pero todas las propiedades de DefaultMethodSecurityExpressionHandler son privadas sin accesos , así que tenía curiosidad por saber cómo extendió la clase sin ningún reflejo desagradable. Gracias.
Joseph Lust
1
¿Te refieres a trustResolver, etc.? Todos tienen establecedores en DefaultMethodSecurityExpressionHandler (al menos en Spring Security 3.0) Ver: static.springsource.org/spring-security/site/apidocs/org/…
sourcedelica
3
@ericacm ¿Cómo puede evitar MethodSecurityExpressionRootser privado de paquetes ?
C. Ross
175

Ninguna de las técnicas mencionadas funcionará más. Parece que Spring ha hecho todo lo posible para evitar que los usuarios anulen SecurityExpressionRoot.

EDITAR 19/11/14 Configure Spring para usar anotaciones de seguridad:

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

Crea un frijol como este:

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

Luego haz algo como esto en tu jsp:

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

O anote un método:

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

Además, puede usar Spring Expression Language en sus @PreAuthorizeanotaciones para acceder a la autenticación actual, así como a los argumentos del método.

Por ejemplo:

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

Luego actualice su @PreAuthorizepara que coincida con la nueva firma del método:

@PreAuthorize("@mySecurityService.hasPermission(authentication, #foo)")
public void doSpecialStuff(String foo) { ... }
James Watkins
fuente
6
@Bosh en su método hasPermission, puede usarlo Authentication auth = SecurityContextHolder.getContext().getAuthentication();para obtener el token de autenticación actual.
James Watkins
2
Gracias James por tu respuesta. ¿Tengo que definir mySecurityService en el archivo de configuración de primavera?
WowBow
2
No necesita definir mySecurityService en ningún archivo XML si tiene una configuración de escaneo de componentes para el paquete en el que se encuentra el servicio. Si no tiene un escaneo de componentes coincidente, entonces debe usar una definición de bean xml. @PreAuthorize proviene de org.springframework.security
James Watkins
3
Es posible que deba especificar el nombre del bean en la anotación de esta manera: @Component ("mySecurityService") o utilizar la anotación @Named.
James Watkins
1
@VJS Por favor, vea la edición que hice. Deberá configurar Spring para usar estas anotaciones. Me sorprende que nadie más se haya quejado de este importante detalle que falta :)
James Watkins
14

Gracias ericacm , pero no funciona por algunas razones:

  • Las propiedades de DefaultMethodSecurityExpressionHandler son privadas (la visibilidad de la reflexión es indeseable)
  • Al menos en mi Eclipse, no puedo resolver un objeto MethodSecurityEvaluationContext

Las diferencias son que llamamos al método createEvaluationContext existente y luego agregamos nuestro objeto raíz personalizado. Finalmente, acabo de devolver un tipo de objeto StandardEvaluationContext ya que MethodSecurityEvaluationContext no se resolvería en el compilador (ambos son de la misma interfaz). Este es el código que ahora tengo en producción.

Haga que MethodSecurityExpressionHandler use nuestra raíz 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;
    }
}

Esto reemplaza la raíz predeterminada al extender SecurityExpressionRoot . Aquí he cambiado el nombre de hasRole a 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);
    }

}

Finalmente, actualice securityContext.xml (y asegúrese de que se haga referencia a él desde su 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" />

Nota: la anotación @Secured no aceptará esta anulación ya que se ejecuta a través de un controlador de validación diferente. Entonces, en el xml anterior, los desactivé para evitar confusiones posteriores.

Joseph Lust
fuente