Authenticate all APIs using Spring Security - HMAC based Custom Authentication

2.1k views Asked by At

I am trying to implement HMAC Based authentication for all the REST API for my Microservice application (To know more about HMAC refer this). I have implemented below Spring security code but unable to get the app working with Spring security.

RESTSecurityConfig

@Configuration
@EnableWebSecurity
@Order(1)
public class RESTSecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
public RESTSecurityFilter authenticationFilter() throws Exception {
    RESTSecurityFilter authenticationFilter = new RESTSecurityFilter("/");
    authenticationFilter.setAuthenticationManager(authenticationManagerBean());
    return authenticationFilter;
}

@Bean
public RESTAuthenticationProvider authenticationProvider() {
    RESTAuthenticationProvider provider = new RESTAuthenticationProvider();     
    return provider;
}

@Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) {
    auth.authenticationProvider(authenticationProvider());
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().addFilterBefore(authenticationFilter(),
            UsernamePasswordAuthenticationFilter.class);
}
}

RESTSecurityFilter

public class RESTSecurityFilter extends AbstractAuthenticationProcessingFilter {

private static final Logger log = LoggerFactory.getLogger(RESTSecurityFilter.class);
private static final String ACCESS_KEY_PARAMETER_NAME = "x-access-key";
private static final String SIGNATURE_PARAMETER_NAME = "x-signature";
private static final String NONCE_PARAMETER_NAME = "x-nonce";
private static final String TIMESTAMP_PARAMETER_NAME = "x-timestamp";
private static final String SECRET_KEY = "xxxxxxxxxxxxxxxxx";

protected RESTSecurityFilter(String defaultFilterProcessesUrl) {
    super(defaultFilterProcessesUrl);
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
        throws AuthenticationException, IOException, ServletException {
    String accessKey = getHeaderValue(request, ACCESS_KEY_PARAMETER_NAME);
    String signature = getHeaderValue(request, SIGNATURE_PARAMETER_NAME);
    String nonce = getHeaderValue(request, NONCE_PARAMETER_NAME);
    String timestamp = getHeaderValue(request, TIMESTAMP_PARAMETER_NAME);       
    String message = accessKey + ":" + nonce + ":" + timestamp;
    String hashSignature = null;
    try {
        hashSignature = HMacUtility.calculateHmac(message, SECRET_KEY);
        log.info("hashSignature : {}", hashSignature);
    }
    catch (InvalidKeyException | SignatureException | NoSuchAlgorithmException e) {
        e.printStackTrace();
    }

    AbstractAuthenticationToken authRequest = createAuthenticationToken(accessKey,
            new RESTCredentials(signature, hashSignature));

    // Allow subclasses to set the "details" property
    setDetails(request, authRequest);

    return this.getAuthenticationManager().authenticate(authRequest);
}

@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
        Authentication authResult) throws IOException, ServletException {
    super.successfulAuthentication(request, response, chain, authResult);
    chain.doFilter(request, response);
}

private String getHeaderValue(HttpServletRequest request, String headerParameterName) {
    return (request.getHeader(headerParameterName) != null) ? request.getHeader(headerParameterName) : "";
}

private AbstractAuthenticationToken createAuthenticationToken(String apiKeyValue, RESTCredentials restCredentials) {
    return new RESTAuthenticationToken(apiKeyValue, restCredentials);
}

protected void setDetails(HttpServletRequest request, AbstractAuthenticationToken authRequest) {
    authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}

@Override
protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
    return true;
}
}

RESTAuthenticationProvider

public class RESTAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

private static final Logger log = LoggerFactory.getLogger(RESTAuthenticationProvider.class);

@Autowired
private UserSecurityService userSecurityService;

@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
        UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {

    RESTAuthenticationToken token = (RESTAuthenticationToken) authentication;
    if (token != null) {
        if (authentication.getCredentials() == null) {
            log.debug("Authentication failed: no credentials provided");
            throw new BadCredentialsException(messages.getMessage("badCredentials", "Bad credentials"));
        }
        RESTCredentials restCredentials = (RESTCredentials) authentication.getCredentials();

        log.info("==========userDetails.getPassword() = {}", userDetails.getPassword());
        log.info("=============restCredentials.getRequestSalt() = {}", restCredentials.getRequestSalt());
        log.info("=============restCredentials.getSecureHash() = {}", restCredentials.getSecureHash());

        // Check if signature and hashSignature matches and return API response
    } else {
        throw new AuthenticationCredentialsNotFoundException(
                MessageFormat.format("Expected Authentication Token object of type {0}, but instead received {1}",
                        RESTAuthenticationToken.class.getSimpleName(), authentication.getClass().getSimpleName()));
    }
}

@Override
protected UserDetails retrieveUser(String apiKey, UsernamePasswordAuthenticationToken authentication)
        throws AuthenticationException {
    log.info("Loading user by apikey = {}", apiKey);
    UserDetails loadedUser;
    try {
        loadedUser = userSecurityService.getUserByApiKey(apiKey);           
        log.info("########### Loaded user = {}", loadedUser);
    } catch (UsernameNotFoundException notFound) {
        throw notFound;
    }
    if (loadedUser == null) {
        throw new AuthenticationServiceException("UserSecurityServiceImpl returned null, which is an interface contract violation");
    }
    return loadedUser;
}
}

RESTAuthenticationToken

public class RESTAuthenticationToken extends UsernamePasswordAuthenticationToken {
private static final long serialVersionUID = 1L;

public RESTAuthenticationToken(Object principal, Object credentials) {
    super(principal, credentials);
}

public RESTAuthenticationToken(Object principal, Object credentials,
        Collection<? extends GrantedAuthority> authorities) {
    super(principal, credentials, authorities);
}
}

UserSecurityRepository

public interface UserSecurityRepository {
    UserDetails getUserByUsername(String username);
    UserDetails getUserByApiKey(String apiKey);
}

UserSecurityService

@Service
public interface UserSecurityService extends UserDetailsService {
    UserDetails getUserByApiKey(String apiKey);
}

Is there a simple and efficient way to Authenticate API in filter without the provider? I have user sent "signature" and "hashSignature" both in filter so just want to compare them and return the API json response if both of them match.

Any help is very much appreciated! Thanks

0

There are 0 answers