简体   繁体   中英

How to add a custom security annotation to Spring MVC controller method

I am using Java 8, Spring MVC 4, Spring Boot, and Gradle for my REST application.

I would like to add security to my REST application via custom method annotations within certain Spring MVC 4 controllers.

Here is a basic example.

HomeController.java

package myapp;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;

@RestController
@RequestMapping("/")
public class HomeController {

    @RequestMapping("/")
    public String index() {
        return "<h1>Hello, World!</h1><p>Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum</p>";
    }

    @CustomSecurityAnnotation
    @RequestMapping("/secure")
    public String secure() {
        return "<h1>Secured!</h1><p>Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum</p>";
    }
}

I would like CustomSecurityAnnotation to run a custom method that will check the headers of the incoming REST request, look at HTTP header Authorization , pull the value provided (if one was provided), and run code against that value to validate the request before allowing the method to proceed. The annotation should have the ability to override the response and provide a HTTP 401 or 403 if the authorization is invalid, and allow the method under the annotation to run if I decide the annotation custom method passed successfully.

I realize there is something similar I could do with PreAuthorize and other MVC annotations but I'm looking at packaging up custom logic within a method inside a single annotation to be used on any method on any controller of my choosing.

Thanks!

We also created a custom annotation in our project. What you need to accomplish this, is a bit of Aspect Oriented Programming.

First you'll want to create your own annotation to tag your methods, as follows:

public @interface CustomSecurityAnnotation {
}

Then you have to write the logic which is triggered when your method is executed. You write an aspect for that.

@Aspect
@Component
public class CustomSecurityAspect {
    @Pointcut("@annotation(my.package.CustomSecurityAnnotation)")
    private void customSecurityAnnotation() {
    }

    @Around("my.package.CustomSecurityAspect.customSecurityAnnotation()")
    public Object doSomething(ProceedingJoinPoint pjp) throws Throwable {
        HttpServletRequest req = getRequest();
        // Check header values
        // Throw Spring's AccessDeniedException if needed
        return pjp.proceed();
    }

    private HttpServletRequest getRequest() {
        ServletRequestAttributes sra = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        return sra.getRequest();
    }
}

As you can see, I've already included a method to retrieve the current HTTP request so you can easily retrieve the header you want to check.

In case of an AccessDeniedException , Spring automatically sets the response status code to HTTP 403.

Don't forget to enable @EnableAspectJAutoProxy on your @Configuration class to enable aspects.

If you don't want to create your own Aspect (or you want to rely on plain Spring-Security), you can take the mechanisms of Spring Security and create your own custom SecurityExpressionRoot like shown here: https://www.baeldung.com/spring-security-create-new-custom-security-expression#2-custom-expression-handler

The wonderful part is you have the methodInvocation delivered by the framework, get your annotation via reflection and pass it to your CustomSecurityExpressionRoot . In Kotlin this looks like follows:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class MethodAuthConfig: GlobalMethodSecurityConfiguration() {
override fun createExpressionHandler(): MethodSecurityExpressionHandler = object: DefaultMethodSecurityExpressionHandler() {
    override fun createSecurityExpressionRoot(authentication: Authentication, invocation: MethodInvocation): MethodSecurityExpressionOperations =
        SecuredTargetExpressionRoot(
            authentication,
            // This is the important part to find your own annotation and pass it to your method security resolver
            invocation.method.annotations.mapNotNull { it as? SecuredTarget }.firstOrNull()
        ).apply {
            setThis(invocation.getThis())
            setPermissionEvaluator(permissionEvaluator)
            setTrustResolver(trustResolver)
            setRoleHierarchy(roleHierarchy)
        }
    }
}

The only thing missing is to add an SpringSecurity annotation to your own annotation like:

@Target(FUNCTION)
@Retention(RUNTIME)
// This is the important line to let Spring security kick in action
@PreAuthorize("canAccessTarget()")
annotation class SecuredTarget (
    // maybe any variables your want to specify at this annotation
)

And to have a look in your custom SpEl for Spring-Security:

class SecuredTargetExpressionRoot(
    authentication: Authentication,
    private val securityInformation: SecuredTarget?
): SecurityExpressionRoot(authentication), MethodSecurityExpressionOperations {
    fun canAccessTarget(): Boolean {
        return // Your code of verification goes here :)
    }
}

(I created this in my kotlin project. But I guess you have no problem converting these lines into Java-Code. Most of what you need to do is in the linked tutorial. I just point out where to get your custom Annotation information from ;) And on the plus side: You can use all of Spring-Securitys mechanism without having to write your own logic to catch exceptions etc.)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM