To implement HMAC authentication I made my own filter, provider and token. RestSecurityFilter:
public class RestSecurityFilter extends AbstractAuthenticationProcessingFilter {
private final Logger LOG = LoggerFactory.getLogger(RestSecurityFilter.class);
private AuthenticationManager authenticationManager;
public RestSecurityFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
public RestSecurityFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
AuthenticationRequestWrapper request = new AuthenticationRequestWrapper(req);
// Get authorization headers
String signature = request.getHeader("Signature");
String principal = request.getHeader("API-Key");
String timestamp = request.getHeader("timestamp");
if ((signature == null) || (principal == null) || (timestamp == null))
unsuccessfulAuthentication(request, response, new BadHMACAuthRequestException("Authentication attempt failed! Request missing mandatory headers."));
// a rest credential is composed by request data to sign and the signature
RestCredentials credentials = new RestCredentials(HMACUtils.calculateContentToSign(request), signature);
// Create an authentication token
return new RestToken(principal, credentials, Long.parseLong(timestamp));
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
LOG.debug("Filter request: " + req.toString());
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
chain.doFilter(request, response);
Authentication authResult;
try {
authResult = attemptAuthentication(request, response);
if (authResult == null)
unsuccessfulAuthentication(request, response, new BadHMACAuthRequestException("Authentication attempt failed !"));
} catch (InternalAuthenticationServiceException failed) {
LOG.error("An internal error occurred while trying to authenticate the user.", failed);
unsuccessfulAuthentication(request, response, failed);
} catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
}
}
}
Authentication provider:
@Component
public class RestAuthenticationProvider implements AuthenticationProvider {
private final Logger LOG = LoggerFactory.getLogger(RestAuthenticationProvider.class);
private ApiKeysService apiKeysService;
@Autowired
public void setApiKeysService(ApiKeysService apiKeysService) {
this.apiKeysService = apiKeysService;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
RestToken restToken = (RestToken) authentication;
// api key (aka username)
String principal = restToken.getPrincipal();
LOG.info("Authenticating api key: '" + principal + "'");
// check request time, 60000 is one minute
long interval = Clock.systemUTC().millis() - restToken.getTimestamp();
if ((interval < 0) && (interval > 60000))
throw new BadHMACAuthRequestException("Auth Failed: old request.");
// hashed blob
RestCredentials credentials = restToken.getCredentials();
// get secret access key from api key
ApiKey apiKey = apiKeysService.getKeyByName(principal).orElseThrow(() -> new NotFoundException("Key not found for: '" + principal + "'"));
String secret = apiKey.getApiKey();
// calculate the hmac of content with secret key
String hmac = HMACUtils.calculateHMAC(secret, credentials.getRequestData());
LOG.debug("Api Key '{}', calculated hmac '{}'");
// check if signatures match
if (!credentials.getSignature().equals(hmac)) {
throw new BadHMACAuthRequestException("Auth Failed: invalid HMAC signature.");
}
return new RestToken(principal, credentials, restToken.getTimestamp(), apiKeysService.getPermissions(apiKey));
}
@Override
public boolean supports(Class<?> authentication) {
return RestToken.class.equals(authentication);
}
}
I don't know how to configure WebSecurityConfig to authenticate every request with my filter and Authentication Provider. I assume I need to create @Bean to initialize RestSecurityFilter
. Also JavaDoc for AbstractAuthenticationProcessingFilter
says I need to the authenticationManager
property. I would appreciate working solution with custom filter, provider and token.
Authentication Provider calls User Details service loads the User Details and returns the Authenticated Principal. Authentication Manager returns the Authenticated Object to Authentication Filter and Authentication Filter sets the Authentication object in Security Context .
I'm not familiar with Spring Boot, but I saw your comment on my question How To Inject AuthenticationManager using Java Configuration in a Custom Filter
In a traditional Spring Security XML configuration, you would specify your custom RestSecurityFilter like so
<http use-expressions="true" create-session="stateless" authentication-manager-ref="authenticationManager" entry-point-ref="restAuthenticationEntryPoint">
<custom-filter ref="restSecurityFilter" position="FORM_LOGIN_FILTER" />
</http>
More information http://docs.spring.io/spring-security/site/docs/4.0.1.RELEASE/reference/htmlsingle/#ns-custom-filters
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With