Implemented token-based API authentication.

Replaced the ApiKey concept with ephemeral tokens. Users and apps obtain a
token by authenticating the user credentials (to be implemented). The service
then generates a temporary token that is stored by the client and sent with
every request using the `Authorization-Token` header. The server verifies this
token to recognize and authenticate the request. With an authenticated user,
the server can use the user's role to authorize requests.

This implementation uses JSR 250 SecurityContext and security annotations.
This commit is contained in:
Jonathan Bernard
2015-03-02 21:20:25 -06:00
parent 38e0432c1e
commit 83a0f7275c
9 changed files with 141 additions and 16 deletions

View File

@ -0,0 +1,43 @@
package com.jdbernard.nlsongs.rest.security
import java.security.Principal
import javax.ws.rs.container.ContainerRequestContext
import javax.ws.rs.core.SecurityContext
import com.jdbernard.nlsongs.model.Role
import com.jdbernard.nlsongs.model.Token
import com.jdbernard.nlsongs.servlet.NLSongsContext
public class NLSongsSecurityContext implements SecurityContext {
public final TokenPrincipal principal
public NLSongsSecurityContext(ContainerRequestContext ctx) {
// Extract the authentication token (if present)
String tokenVal = ctx.getHeaderString("Authorization-Token")
// Look up the token in the database.
Token token = NLSongsContext.songsDB.findToken(tokenVal)
// Create our principal based on this token.
this.principal = new TokenPrincipal(token)
}
@Override
public String getAuthenticationScheme() { return "Authentication-Token" }
@Override
public Principal getUserPrincipal() { return principal }
@Override
public boolean isSecure() { /* TODO */ return false }
@Override
public boolean isUserInRole(String role) {
println "Required Role: $role"
println "User's Role: ${principal?.token?.user?.role}"
println "Required Role == User's Role? ${principal?.token?.user?.role == ((Role)role)} "
return principal?.token?.user?.role == ((Role) role) }
}

View File

@ -0,0 +1,17 @@
package com.jdbernard.nlsongs.rest.security;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.ext.Provider;
import com.jdbernard.nlsongs.servlet.NLSongsContext;
@Provider
@PreMatching
public class SecurityRequestFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext reqCtx) {
reqCtx.setSecurityContext(new NLSongsSecurityContext(reqCtx)); }
}

View File

@ -0,0 +1,35 @@
package com.jdbernard.nlsongs.rest.security;
import java.security.Principal;
import com.jdbernard.nlsongs.model.Token;
public class TokenPrincipal implements Principal {
public final Token token;
public TokenPrincipal(Token token) { this.token = token; }
@Override
public boolean equals(Object thatObj) {
if (thatObj == null) return false;
if (!(thatObj instanceof TokenPrincipal)) return false;
TokenPrincipal that = (TokenPrincipal) thatObj;
if (this.token == null) { return that.token == null; }
else { return this.token.equals(that.token); } }
@Override
public String getName() {
if (this.token == null) return null;
else return this.token.getUser().getUsername(); }
@Override
public int hashCode() {
if (this.token == null) return 0;
return this.token.getUser().getUsername().hashCode(); }
@Override
public String toString() { return getName(); }
}