Custom HTTP Security Mechanisms with Java/Spring

Baruch Speiser
Javarevisited
Published in
5 min readJul 27, 2021

--

Typical HTTP security today is handled either by passing a Bearer token that can be validated using OAUTH2 or JWT, or sometimes even just keeping it simple with HTTP Basic Authentication.

In the event you need to use an alternate authentication mechanism — because your customer has some wacky legacy need or because typical token-based credentials don’t fit the integration paradigm — then you may need to implement a custom handler.

With Spring Security, doing so in Java is fairly straightforward. Our example will be built using Java 11, Maven, and Spring Boot.

Use Spring Initializr to create a new Maven Project with Spring Web and Spring Security — this will give you everything you need to get started:

Use whatever names you want for your artifact and packaging — but use Jar packaging to keep it simple.

Download the project and load it into your IDE of choice. You’ll have a working REST server out-of-the-box, so now we just need to add some simple API that we can protect using our custom security. Here’s a simple example of an API that returns the current time:

package net.cambium.examples.rest;import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* TimeController.
*
* Simple example REST service that we plan on securing
* with a custom security mechanism.
*
* @author Baruch Speiser, Cambium.
*/
@RestController
@RequestMapping("/api/time")
public class TimeController {

@GetMapping
public String now() {
Date now = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("hh:mm:ss");
String result = formatter.format(now);
return String.format("{ \"time\" : \"%s\" }", result);
}

}

Run your application and use an HTTP Client (such as Postman) to see that your API is accessible:

Now we’re ready to introduce our custom security method. In our case, we’re going to use a simple checksum verification as an example — it’s obviously not a good way to secure an API, but we’re more interested in how to fit a custom example into your application, so it’s good enough for now.

We’ll start by creating a custom HTTP Request Filter that will allow us to reject any HTTP requests that we don’t want to approve. For the sake of illustration, we will code it to look at the standard Authorization header, but with a nonstandard header value:

package net.cambium.examples.rest.security;import java.io.IOException;import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
/**
* CustomAuthorizationFilter.
*
* Introduce our own custom security mechanism to determine access.
* We use a simple checksum verification as an oversimplified
* example.
*
* @author Baruch Speiser, Cambium.
*
*/
public class CustomAuthorizationFilter implements Filter {
private static final String AUTHORIZATION_TYPE_PREFIX =
"X-Custom-Checksum ";
private static final Logger log LoggerFactory.getLogger(
CustomAuthorizationFilter.class);

/** Perform basic HTTP request filter handling. */
@Override
public void doFilter(
ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException
{
if(request instanceof HttpServletRequest) {
//We only care about HTTP requests,
// there isn't anything else here anyway
HttpServletRequest req = (HttpServletRequest)request;
if(!isAuthenticated(req)) {
//Not allowed in, reject the request:
HttpServletResponse res = (HttpServletResponse)response;
res.sendError(HttpStatus.UNAUTHORIZED.value());
return;
}
//otherwise, fall through and continue handling the request
}
chain.doFilter(request, response);
}
/**
* Check to see if the request includes our
* custom authorization header.
*/
private boolean isAuthenticated(HttpServletRequest request) {
try {
String authorization = request.getHeader(
HttpHeaders.AUTHORIZATION);
//our custom authorization mechanism is here:
if(null != authorization
&& authorization.startsWith(AUTHORIZATION_TYPE_PREFIX))
{
String numerical = authorization.substring(
AUTHORIZATION_TYPE_PREFIX.length());
long number = Long.parseLong(numerical);
return isValidChecksum(number);
}
//otherwise:
return false;
} catch(Exception e) {
log.warn("Failure while parsing authorization header, " +
"could have been sent in an incorrect format",
e);
return false;
}
}
/**
* Validate checksum, i.e. verify the
* supplied "credentials" to see if they are valid
*/
private boolean isValidChecksum(long number) {
//Extract the last digit:
long last = number % 10;
//Add up all the other digits:
long sum = 0;
for(long num = (number /= 10); num > 0; num /= 10) {
sum += num % 10;
}
//Now check if the last digit of the sum
// matches the last digit of the original number:
return (sum % 10 == last);
}

}

In the above algorithm, passing a header value of “X-Custom-Checksum 123” would be let through (because 1+2=3), but “X-Custom-Checksum 1234” would fail (because 1+2+3 != 4).

The last step is to configure Spring Security to use our filter as part of the security filter chain. Because we’re not using usernames and passwords, we’ll replace the standard Spring Security username/password authentication option with our checksum verification:

package net.cambium.examples.rest.security;import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation
.web.builders.HttpSecurity;
import org.springframework.security.config.annotation
.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation
.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security
.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* SecurityConfiguration.
*
* Spring Security configuration for Spring Boot that
* will allow us to use a custom authorization mechanism.
*
* @author Baruch Speiser, Cambium.
*/
@Configuration
@EnableWebSecurity
public class SecurityConfiguration
extends WebSecurityConfigurerAdapter
{
@Override
protected void configure(HttpSecurity http) throws Exception {
//Use our custom security filter instead
// of username/password authentication:
http.addFilterAt(
new CustomAuthorizationFilter(),
UsernamePasswordAuthenticationFilter.class);
}

}

And then we should be able to see our custom checksum verification in action:

A valid checksum gives you a successful response.
If the checksum doesn’t validate, then the request is rejected with a 401 Unauthorized.

There’s no reason that we couldn’t pass our number using the same convention we pass for regular tokens, with a header value of “Bearer 123” and then extract the numerical value the same way.

As a rule, you should try to stick to conventions whenever you can — but if needed, Spring Security provides the flexibility to introduce whatever mechanism you need to fit your project.

--

--

Baruch Speiser
Javarevisited

CTO of Cambium, Applicable Innovation (https://cambium.co.il). Long-time Java Architect and hobbyist woodworker. https://github.com/blspeiser