1

Is it possible to use a request header value in @PreAuthorize?

In my app, all requests have a custom header included which I need to use in conjunction with the user role to determine whether or not they should be allowed to access the controller.

It's ok if someone manually specifies a header as that won't be a security issue, as ultimately the role will control this. But I will need to use it to cut down on checking for that manually in each controller method.

Thank you, Matt

Matthew Holt
  • 149
  • 1
  • 11

3 Answers3

7

1 - This may be the fastest method if you will only use it in a few places.

@GetMapping(value = "/private-api-method")
@PreAuthorize("#request.getHeader('header-name') == 'localhost:8080'")
public ResponseEntity<String> privateApiMethod(HttpServletRequest request) {
    return ResponseEntity.ok("OK!");
}

OR

@GetMapping(value = "/private-api-method")
@PreAuthorize("#header == 'localhost:8080'")
public ResponseEntity<String> privateApiMethod(@RequestHeader("header-name") String header) {
    return ResponseEntity.ok("OK!");
}

2 - This may be the best method if you will use it in many places.

(In the SecurityService, you can add multiple different methods of checking.)

@GetMapping(value = "/private-api-method")
@PreAuthorize("@securityService.checkHeader(#request)")
public ResponseEntity<String> privateApiMethod(HttpServletRequest request) {
    return ResponseEntity.ok("OK!");
}

3 - You can choose a special method for yourself

A Custom Security Expression with Spring Security

İsmail Y.
  • 3,579
  • 5
  • 21
  • 29
1

Since you intend to check for a particular header/cookie/request-attribute for every controller methods, you should opt for a Filter as this would be a standard and you can have a guarantee for it be executed for each and every method and that too only once by extending from OncePerRequestFilter

Having said that, there would be 2 way you can achieve this:

  1. By extending AbstractAuthenticationProcessingFilter or OncePerRequestFilter

    For this you may refer the spring-security jwt token validation flow which all would advocate for:

    • Add method security at your desired controller method as @PreAuthorize("hasAuthority('USER_ROLE')")
    • Intercept the request before UsernamePasswordAuthenticationFilter, extract the Authentication header or cookies from the request and validate the token value for claims.
public class CustomHeaderAuthFilter extends AbstractAuthenticationProcessingFilter{

@Override
  public Authentication attemptAuthentication(
      HttpServletRequest request, HttpServletResponse response){

// Get all the headers from request, throw exception if your header not found    
Enumeration<String> reqHeaders =  request.getHeaderNames();

    Assert.notNull(reqHeaders, "No headers found. Abort operation!");

    Collections.list(reqHeaders)
        .stream()
        .filter(header_ -> header_.equals("TARGET_HEADER_NAME"))
.findAny().ifPresent(header_ -> {
          // header found, would go for success-andler
    });
    
    // Here it means request has no target header
    SecurityContextHolder.clearContext();
    failureHandler.onAuthenticationFailure(request, response, new CustomException(""));
  }

}

Going by this way, you need to register your filter with WebSecurityConfigurerAdapter and you may also provide your AuthenticationProvider if you extend from AbstractAuthenticationProcessingFilter.

  1. By accessing HTTP Headers in rest controllers using @RequestHeader as dm-tr has mentioned.
Kumar Ashutosh
  • 1,121
  • 10
  • 33
0

Maybe you can try this

@PreAuthorize("hasAuthority('ROLE_SOMETHING')")
@RequestMapping("PATH")
public void checkIt(@RequestHeader("header-name") String header) {
    if (null != header /* && header meets certain condition*/) {
        // stuff
    } else throw new ResponseStatusException(HttpStatus.FORBIDDEN); // PERMISSION NOT GRANTED, 403 ERROR
}
dm_tr
  • 4,265
  • 1
  • 6
  • 30
  • Thank you! Putting that approach together with doing a SEL check here, I'm curious if this may work: @PreAuthorize(@myEvaluationService.checkSecurity(#header, hasAuthority(ROLE_SOMETHING))) -- not this exactly as the syntax is definitely wrong, but looks like it may be possible to externalize to a service. I'll dig in and respond on the post with how it went. Thanks again for the help! – Matthew Holt Jan 02 '21 at 22:33
  • Replace my comment `/* && header..... */` with your condition `@myEvaluationService.checkSecurity(header)` – dm_tr Jan 02 '21 at 22:40