Skip to content

Instantly share code, notes, and snippets.

@ccashwell
Created December 12, 2014 20:46
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ccashwell/dfc05dd8bd1a75d189d1 to your computer and use it in GitHub Desktop.
Save ccashwell/dfc05dd8bd1a75d189d1 to your computer and use it in GitHub Desktop.
package com.some.api.support;
import com.some.domain.User;
public class AuthenticationResult {
private String token;
private User user;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
package com.some.api.support.spring;
import com.some.api.support.TokenProvider;
import com.some.domain.User;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.GenericFilterBean;
public class AuthenticationTokenProcessingFilter extends GenericFilterBean {
private static Log log = LogFactory.getLog(AuthenticationTokenProcessingFilter.class);
@Autowired TokenProvider tokenProvider;
AuthenticationManager authManager;
SecurityContextProvider securityContextProvider;
WebAuthenticationDetailsSource webAuthenticationDetailsSource = new WebAuthenticationDetailsSource();
public AuthenticationTokenProcessingFilter(AuthenticationManager authManager) {
this.authManager = authManager;
this.securityContextProvider = new SecurityContextProvider();
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
log.debug("Checking headers and parameters for authentication token...");
String token = null;
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (httpServletRequest.getParameter("token") != null) {
token = httpServletRequest.getParameter("token");
log.debug("Found token '" + token + "' in request parameters");
} else if (httpServletRequest.getHeader("Authentication-token") != null) {
token = httpServletRequest.getHeader("Authentication-token");
log.debug("Found token '" + token + "' in request headers");
}
if (token != null) {
if (tokenProvider.isTokenValid(token)) {
User user = tokenProvider.getUserFromToken(token);
authenticateUser(httpServletRequest, user);
}
}
chain.doFilter(request, response);
}
private void authenticateUser(HttpServletRequest request, User user) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user.getEmail(), user.getHashedPassword());
authentication.setDetails(webAuthenticationDetailsSource.buildDetails(request));
SecurityContext sc = securityContextProvider.getSecurityContext();
sc.setAuthentication(authManager.authenticate(authentication));
}
}
package com.some.api.support.spring;
import com.some.domain.User;
import com.some.services.UserService;
import java.util.Collections;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
public class CustomApiAuthProvider extends AbstractUserDetailsAuthenticationProvider {
@Autowired UserService userService;
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
}
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
User user = userService.findUserByHashedCredentials(authentication.getName(), (String)authentication.getCredentials());
if(null == user) {
throw new BadCredentialsException("Username not found");
}
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getHashedPassword(), Collections.<GrantedAuthority>emptySet());
}
}
package com.some.api.support.spring;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized" );
}
}
package com.some.api.support.spring;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
public class SecurityContextProvider {
public SecurityContext getSecurityContext() {
return SecurityContextHolder.getContext();
}
}
package com.some.api.support;
import com.some.domain.User;
import com.some.services.UserService;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
public class TokenProvider {
@Autowired UserService userService;
MessageDigest md5er;
private final String secretKey;
static String FENCE_POST = "!!!";
private static final String NEWLINE = "\r\n";
public TokenProvider(String secretKey) {
try {
md5er = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Cannot find MD5 algorithm",e);
}
if(StringUtils.isEmpty(secretKey)){
throw new IllegalArgumentException("Secret key must be set");
}
this.secretKey = secretKey;
}
public String getToken(User user) {
return getToken(user, DateTime.now().plusDays(1).getMillis());
}
public String getToken(User user, long expirationDateInMillis) {
StringBuilder tokenBuilder = new StringBuilder();
byte[] token = tokenBuilder
.append(user.getEmail())
.append(FENCE_POST)
.append(expirationDateInMillis)
.append(FENCE_POST)
.append(new String(buildTokenKey(expirationDateInMillis, user)))
.toString().getBytes();
// returns a value ending in a newline, remove it
return Base64.encodeBase64String(token).replace(NEWLINE, "");
}
public boolean isTokenValid(String encodedToken) {
String[] components = decodeAndDissectToken(encodedToken);
if (components == null || components.length != 3) {
return false;
}
String externalUser = components[0];
Long externalDate = Long.parseLong(components[1]);
String externalKey = components[2];
User user = userService.findUserByEmail(externalUser);
String expectedKey = new String(buildTokenKey(externalDate, user));
byte[] expectedKeyBytes = expectedKey.getBytes();
byte[] externalKeyBytes = externalKey.getBytes();
if (!MessageDigest.isEqual(expectedKeyBytes, externalKeyBytes)) {
return false;
}
if (new DateTime(externalDate).isBeforeNow()) {
return false;
}
return true;
}
private byte[] buildTokenKey(long expirationDateInMillis, User user) {
StringBuilder keyBuilder = new StringBuilder();
String key = keyBuilder
.append(user.getEmail())
.append(FENCE_POST)
.append(user.getHashedPassword())
.append(FENCE_POST)
.append(expirationDateInMillis)
.append(FENCE_POST)
.append(secretKey).toString();
byte[] keyBytes = key.getBytes();
return md5er.digest(keyBytes);
}
public User getUserFromToken(String token) {
if (!isTokenValid(token)) { return null; }
String[] components = decodeAndDissectToken(token);
if (components == null || components.length != 3) { return null; }
String email = components[0];
return userService.findUserByEmail(email);
}
private String[] decodeAndDissectToken(String encodedToken) {
if(StringUtils.isBlank(encodedToken) || !Base64.isArrayByteBase64(encodedToken.getBytes())) {
return null;
}
// Apache Commons Base64 expects encoded strings to end with a newline, add one
if(!encodedToken.endsWith(NEWLINE)) { encodedToken = encodedToken + NEWLINE; }
String token = new String(Base64.decodeBase64(encodedToken));
if(!token.contains(FENCE_POST) || token.split(FENCE_POST).length != 3) {
return null;
}
return token.split(FENCE_POST);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment