Skip to content

Commit

Permalink
Simplify authorization
Browse files Browse the repository at this point in the history
The current implementation of authorization is too cumbersome
and requires implementing a big amount of boilerplate code.

This changes inroduce `Authorizer` interface, which is responsible
for authorizing users. It's a replacement for specifiying a function
for creating a new secure context.

Advantage is stem from the fact that users only implement business
logic for authorization and nothing more. Also this type of API
is similar to `Authenticator`, which makes API more consistent.
  • Loading branch information
arteam committed Jun 12, 2015
1 parent a91e0cd commit 4110aa4
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 179 deletions.
38 changes: 6 additions & 32 deletions dropwizard-auth/src/main/java/io/dropwizard/auth/AuthFilter.java
@@ -1,20 +1,16 @@
package io.dropwizard.auth; package io.dropwizard.auth;


import com.google.common.base.Function;

import javax.annotation.Priority; import javax.annotation.Priority;
import javax.ws.rs.Priorities; import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.SecurityContext;
import java.security.Principal; import java.security.Principal;


@Priority(Priorities.AUTHENTICATION) @Priority(Priorities.AUTHENTICATION)
public abstract class AuthFilter<C, P extends Principal> implements ContainerRequestFilter { public abstract class AuthFilter<C, P extends Principal> implements ContainerRequestFilter {
protected String prefix; protected String prefix;
protected String realm; protected String realm;
protected Authenticator<C, P> authenticator; protected Authenticator<C, P> authenticator;
protected Function<Tuple, SecurityContext> securityContextFunction; protected Authorizer<P> authorizer;
protected UnauthorizedHandler unauthorizedHandler = new DefaultUnauthorizedHandler(); protected UnauthorizedHandler unauthorizedHandler = new DefaultUnauthorizedHandler();


protected void setPrefix(String prefix) { protected void setPrefix(String prefix) {
Expand All @@ -29,37 +25,15 @@ protected void setAuthenticator(Authenticator<C, P> authenticator) {
this.authenticator = authenticator; this.authenticator = authenticator;
} }


protected void setSecurityContextFunction(Function<Tuple, SecurityContext> securityContextFunction) { protected void setAuthorizer(Authorizer<P> authorizer) {
this.securityContextFunction = securityContextFunction; this.authorizer = authorizer;
}

protected Function<Tuple, SecurityContext> getSecurityContextFunction() {
return securityContextFunction;
}

public static class Tuple {
private ContainerRequestContext containerRequestContext;
private Principal principal;

public Tuple(ContainerRequestContext containerRequestContext, Principal principal) {
this.containerRequestContext = containerRequestContext;
this.principal = principal;
}

public ContainerRequestContext getContainerRequestContext() {
return containerRequestContext;
}

public Principal getPrincipal() {
return principal;
}
} }


public abstract static class AuthFilterBuilder<C, P extends Principal, T extends AuthFilter<C, P>, A extends Authenticator<C, P>> { public abstract static class AuthFilterBuilder<C, P extends Principal, T extends AuthFilter<C, P>, A extends Authenticator<C, P>> {
protected String realm = "realm"; protected String realm = "realm";
protected String prefix = "Basic"; protected String prefix = "Basic";
protected Authenticator<C, P> authenticator; protected Authenticator<C, P> authenticator;
protected Function<Tuple, SecurityContext> securityContextFunction; protected Authorizer<P> authorizer;


public AuthFilterBuilder<C, P, T, A> setRealm(String realm) { public AuthFilterBuilder<C, P, T, A> setRealm(String realm) {
this.realm = realm; this.realm = realm;
Expand All @@ -71,8 +45,8 @@ public AuthFilterBuilder<C, P, T, A> setPrefix(String prefix) {
return this; return this;
} }


public AuthFilterBuilder<C, P, T, A> setSecurityContextFunction(Function<Tuple, SecurityContext> securityContextFunction) { public AuthFilterBuilder<C, P, T, A> setAuthorizer(Authorizer<P> authorizer) {
this.securityContextFunction = securityContextFunction; this.authorizer = authorizer;
return this; return this;
} }


Expand Down
20 changes: 20 additions & 0 deletions dropwizard-auth/src/main/java/io/dropwizard/auth/Authorizer.java
@@ -0,0 +1,20 @@
package io.dropwizard.auth;

import java.security.Principal;

/**
* An interface for classes which authorize principal objects.
*
* @param <P> the type of principals
*/
public interface Authorizer<P extends Principal> {

/**
* Decides if access is granted for the given principal in the given role.
*
* @param principal a {@link Principal} object, representing a user
* @param role a user role
* @return {@code true}, if the access is granted, {@code false otherwise}
*/
boolean authorize(P principal, String role);
}
Expand Up @@ -13,6 +13,7 @@
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.SecurityContext;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.Principal; import java.security.Principal;
Expand All @@ -25,7 +26,7 @@ private BasicCredentialAuthFilter() {
} }


@Override @Override
public void filter(ContainerRequestContext requestContext) throws IOException { public void filter(final ContainerRequestContext requestContext) throws IOException {
final String header = requestContext.getHeaders().getFirst(HttpHeaders.AUTHORIZATION); final String header = requestContext.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
try { try {
if (header != null) { if (header != null) {
Expand All @@ -42,11 +43,29 @@ public void filter(ContainerRequestContext requestContext) throws IOException {
final String password = decoded.substring(i + 1); final String password = decoded.substring(i + 1);
final BasicCredentials credentials = new BasicCredentials(username, password); final BasicCredentials credentials = new BasicCredentials(username, password);
try { try {
final Optional<P> result = authenticator.authenticate(credentials); final Optional<P> principal = authenticator.authenticate(credentials);
if (result.isPresent()) { if (principal.isPresent()) {
Principal principal = result.get(); requestContext.setSecurityContext(new SecurityContext() {
requestContext.setSecurityContext( @Override
getSecurityContextFunction().apply(new Tuple(requestContext, principal))); public Principal getUserPrincipal() {
return principal.get();
}

@Override
public boolean isUserInRole(String role) {
return authorizer.authorize(principal.get(), role);
}

@Override
public boolean isSecure() {
return requestContext.getSecurityContext().isSecure();
}

@Override
public String getAuthenticationScheme() {
return SecurityContext.BASIC_AUTH;
}
});
return; return;
} }
} catch (AuthenticationException e) { } catch (AuthenticationException e) {
Expand All @@ -70,15 +89,15 @@ public static class Builder<APrincipal extends Principal, AAuthenticator extends


@Override @Override
public BasicCredentialAuthFilter<APrincipal> buildAuthFilter() { public BasicCredentialAuthFilter<APrincipal> buildAuthFilter() {
if (realm == null || authenticator == null || prefix == null || securityContextFunction == null) { if (realm == null || authenticator == null || prefix == null || authorizer == null) {
throw new RuntimeException("Required auth filter parameters not set"); throw new RuntimeException("Required auth filter parameters not set");
} }


BasicCredentialAuthFilter<APrincipal> basicCredentialAuthFilter = new BasicCredentialAuthFilter<>(); BasicCredentialAuthFilter<APrincipal> basicCredentialAuthFilter = new BasicCredentialAuthFilter<>();
basicCredentialAuthFilter.setRealm(realm); basicCredentialAuthFilter.setRealm(realm);
basicCredentialAuthFilter.setAuthenticator(authenticator); basicCredentialAuthFilter.setAuthenticator(authenticator);
basicCredentialAuthFilter.setPrefix(prefix); basicCredentialAuthFilter.setPrefix(prefix);
basicCredentialAuthFilter.setSecurityContextFunction(securityContextFunction); basicCredentialAuthFilter.setAuthorizer(authorizer);
return basicCredentialAuthFilter; return basicCredentialAuthFilter;
} }
} }
Expand Down
Expand Up @@ -13,6 +13,7 @@
import javax.ws.rs.WebApplicationException; import javax.ws.rs.WebApplicationException;
import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.SecurityContext;
import java.io.IOException; import java.io.IOException;
import java.security.Principal; import java.security.Principal;


Expand All @@ -23,7 +24,7 @@ private OAuthCredentialAuthFilter() {
} }


@Override @Override
public void filter(ContainerRequestContext requestContext) throws IOException { public void filter(final ContainerRequestContext requestContext) throws IOException {
final String header = requestContext.getHeaders().getFirst(HttpHeaders.AUTHORIZATION); final String header = requestContext.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (header != null) { if (header != null) {
try { try {
Expand All @@ -32,11 +33,29 @@ public void filter(ContainerRequestContext requestContext) throws IOException {
final String method = header.substring(0, space); final String method = header.substring(0, space);
if (prefix.equalsIgnoreCase(method)) { if (prefix.equalsIgnoreCase(method)) {
final String credentials = header.substring(space + 1); final String credentials = header.substring(space + 1);
final Optional<P> result = authenticator.authenticate(credentials); final Optional<P> principal = authenticator.authenticate(credentials);
if (result.isPresent()) { if (principal.isPresent()) {
final Principal principal = result.get(); requestContext.setSecurityContext(new SecurityContext() {
requestContext.setSecurityContext( @Override
getSecurityContextFunction().apply(new Tuple(requestContext, principal))); public Principal getUserPrincipal() {
return principal.get();
}

@Override
public boolean isUserInRole(String role) {
return authorizer.authorize(principal.get(), role);
}

@Override
public boolean isSecure() {
return requestContext.getSecurityContext().isSecure();
}

@Override
public String getAuthenticationScheme() {
return SecurityContext.BASIC_AUTH;
}
});
return; return;
} }
} }
Expand All @@ -54,15 +73,15 @@ public static class Builder<APrincipal extends Principal, AAuthenticator extends
extends AuthFilterBuilder<String, APrincipal, OAuthCredentialAuthFilter<APrincipal>, AAuthenticator> { extends AuthFilterBuilder<String, APrincipal, OAuthCredentialAuthFilter<APrincipal>, AAuthenticator> {
@Override @Override
public OAuthCredentialAuthFilter<APrincipal> buildAuthFilter() { public OAuthCredentialAuthFilter<APrincipal> buildAuthFilter() {
if (realm == null || authenticator == null || prefix == null || securityContextFunction == null) { if (realm == null || authenticator == null || prefix == null || authorizer == null) {
throw new RuntimeException("Required auth filter parameters not set"); throw new RuntimeException("Required auth filter parameters not set");
} }


OAuthCredentialAuthFilter<APrincipal> oauthCredentialAuthFilter = new OAuthCredentialAuthFilter<>(); OAuthCredentialAuthFilter<APrincipal> oauthCredentialAuthFilter = new OAuthCredentialAuthFilter<>();
oauthCredentialAuthFilter.setRealm(realm); oauthCredentialAuthFilter.setRealm(realm);
oauthCredentialAuthFilter.setAuthenticator(authenticator); oauthCredentialAuthFilter.setAuthenticator(authenticator);
oauthCredentialAuthFilter.setPrefix(prefix); oauthCredentialAuthFilter.setPrefix(prefix);
oauthCredentialAuthFilter.setSecurityContextFunction(securityContextFunction); oauthCredentialAuthFilter.setAuthorizer(authorizer);
return oauthCredentialAuthFilter; return oauthCredentialAuthFilter;
} }
} }
Expand Down
Expand Up @@ -2,7 +2,6 @@


import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
import io.dropwizard.auth.AuthDynamicFeature; import io.dropwizard.auth.AuthDynamicFeature;
import io.dropwizard.auth.AuthFilter;
import io.dropwizard.auth.AuthResource; import io.dropwizard.auth.AuthResource;
import io.dropwizard.auth.Authenticator; import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.util.AuthUtil; import io.dropwizard.auth.util.AuthUtil;
Expand Down Expand Up @@ -158,7 +157,7 @@ private ContainerRequestFilter getAuthFilter() {


BasicCredentialAuthFilter.Builder<Principal, Authenticator<BasicCredentials, Principal>> builder BasicCredentialAuthFilter.Builder<Principal, Authenticator<BasicCredentials, Principal>> builder
= new BasicCredentialAuthFilter.Builder<>(); = new BasicCredentialAuthFilter.Builder<>();
builder.setSecurityContextFunction(AuthUtil.<AuthFilter.Tuple, SecurityContext>getSecurityContextProviderFunction(validUser, VALID_ROLE)); builder.setAuthorizer(AuthUtil.getTestAuthorizer(validUser, VALID_ROLE));
builder.setAuthenticator(AuthUtil.<BasicCredentials, Principal>getTestAuthenticatorBasicCredential(validUser)); builder.setAuthenticator(AuthUtil.<BasicCredentials, Principal>getTestAuthenticatorBasicCredential(validUser));
return builder.buildAuthFilter(); return builder.buildAuthFilter();
} }
Expand Down
Expand Up @@ -149,7 +149,7 @@ private ContainerRequestFilter getAuthFilter() {


BasicCredentialAuthFilter.Builder<Principal, Authenticator<BasicCredentials, Principal>> builder BasicCredentialAuthFilter.Builder<Principal, Authenticator<BasicCredentials, Principal>> builder
= new BasicCredentialAuthFilter.Builder<>(); = new BasicCredentialAuthFilter.Builder<>();
builder.setSecurityContextFunction(AuthUtil.getSecurityContextProviderFunction(validUser, VALID_ROLE)); builder.setAuthorizer(AuthUtil.getTestAuthorizer(validUser, VALID_ROLE));
builder.setAuthenticator(AuthUtil.getTestAuthenticatorBasicCredential(validUser)); builder.setAuthenticator(AuthUtil.getTestAuthenticatorBasicCredential(validUser));
builder.setPrefix("Custom"); builder.setPrefix("Custom");
return builder.buildAuthFilter(); return builder.buildAuthFilter();
Expand Down
@@ -1,12 +1,8 @@
package io.dropwizard.auth.chained; package io.dropwizard.auth.chained;


import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.MetricRegistry;
import com.google.common.base.Function;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import io.dropwizard.auth.AuthDynamicFeature; import io.dropwizard.auth.*;
import io.dropwizard.auth.AuthFilter;
import io.dropwizard.auth.AuthResource;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.basic.BasicCredentialAuthFilter; import io.dropwizard.auth.basic.BasicCredentialAuthFilter;
import io.dropwizard.auth.basic.BasicCredentials; import io.dropwizard.auth.basic.BasicCredentials;
import io.dropwizard.auth.oauth.OAuthCredentialAuthFilter; import io.dropwizard.auth.oauth.OAuthCredentialAuthFilter;
Expand Down Expand Up @@ -133,8 +129,8 @@ public ChainedAuthTestResourceConfig() {


final String validUser = "good-guy"; final String validUser = "good-guy";


final Function<AuthFilter.Tuple, SecurityContext> securityContextFunction = final Authorizer<Principal> authorizer =
AuthUtil.getSecurityContextProviderFunction(validUser, ADMIN_ROLE); AuthUtil.getTestAuthorizer(validUser, ADMIN_ROLE);


final Authenticator<BasicCredentials, Principal> basicAuthenticator = final Authenticator<BasicCredentials, Principal> basicAuthenticator =
AuthUtil.getTestAuthenticatorBasicCredential(validUser); AuthUtil.getTestAuthenticatorBasicCredential(validUser);
Expand All @@ -145,13 +141,13 @@ public ChainedAuthTestResourceConfig() {
final AuthFilter<BasicCredentials, Principal> basicAuthFilter = final AuthFilter<BasicCredentials, Principal> basicAuthFilter =
new BasicCredentialAuthFilter.Builder<>() new BasicCredentialAuthFilter.Builder<>()
.setAuthenticator(basicAuthenticator) .setAuthenticator(basicAuthenticator)
.setSecurityContextFunction(securityContextFunction) .setAuthorizer(authorizer)
.buildAuthFilter(); .buildAuthFilter();


final AuthFilter<String, Principal> oAuthFilter = new OAuthCredentialAuthFilter.Builder<>() final AuthFilter<String, Principal> oAuthFilter = new OAuthCredentialAuthFilter.Builder<>()
.setAuthenticator(oauthAuthenticator) .setAuthenticator(oauthAuthenticator)
.setPrefix("Bearer") .setPrefix("Bearer")
.setSecurityContextFunction(securityContextFunction) .setAuthorizer(authorizer)
.buildAuthFilter(); .buildAuthFilter();


register(new AuthDynamicFeature(new ChainedAuthFilter<>(buildHandlerList(basicAuthFilter, oAuthFilter )))); register(new AuthDynamicFeature(new ChainedAuthFilter<>(buildHandlerList(basicAuthFilter, oAuthFilter ))));
Expand Down
Expand Up @@ -129,8 +129,8 @@ private AuthFilter getAuthFilter() {
final String validUser = "good-guy"; final String validUser = "good-guy";


return new OAuthCredentialAuthFilter.Builder<>() return new OAuthCredentialAuthFilter.Builder<>()
.setAuthenticator(AuthUtil.<String, SecurityContext>getTestAuthenticator(validUser)) .setAuthenticator(AuthUtil.getTestAuthenticator(validUser))
.setSecurityContextFunction(AuthUtil.getSecurityContextProviderFunction(validUser, "ADMIN")) .setAuthorizer(AuthUtil.getTestAuthorizer(validUser, "ADMIN"))
.setPrefix("Custom") .setPrefix("Custom")
.buildAuthFilter(); .buildAuthFilter();
} }
Expand Down
Expand Up @@ -123,9 +123,9 @@ private AuthFilter getAuthFilter() {
final String validUser = "good-guy"; final String validUser = "good-guy";


return new OAuthCredentialAuthFilter.Builder<>() return new OAuthCredentialAuthFilter.Builder<>()
.setAuthenticator(AuthUtil.<String, Principal>getTestAuthenticator(validUser)) .setAuthenticator(AuthUtil.getTestAuthenticator(validUser))
.setAuthorizer(AuthUtil.getTestAuthorizer(validUser, "ADMIN"))
.setPrefix("Bearer") .setPrefix("Bearer")
.setSecurityContextFunction(AuthUtil.getSecurityContextProviderFunction(validUser, "ADMIN"))
.buildAuthFilter(); .buildAuthFilter();
} }
} }
Expand Down
@@ -1,14 +1,9 @@
package io.dropwizard.auth.util; package io.dropwizard.auth.util;


import com.google.common.base.Function;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import io.dropwizard.auth.AuthFilter; import io.dropwizard.auth.*;
import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;
import io.dropwizard.auth.PrincipalImpl;
import io.dropwizard.auth.basic.BasicCredentials; import io.dropwizard.auth.basic.BasicCredentials;


import javax.ws.rs.core.SecurityContext;
import java.security.Principal; import java.security.Principal;


public class AuthUtil { public class AuthUtil {
Expand Down Expand Up @@ -48,37 +43,14 @@ public static Authenticator<String, Principal> getTestAuthenticator(final String
return getTestAuthenticator(presented, presented); return getTestAuthenticator(presented, presented);
} }


public static Function<AuthFilter.Tuple, SecurityContext> getSecurityContextProviderFunction( public static Authorizer<Principal> getTestAuthorizer(final String validUser,
final String validUser, final String validRole) {
final String validRole return new Authorizer<Principal>() {
) {
return new Function<AuthFilter.Tuple, SecurityContext>() {
@Override @Override
public SecurityContext apply(final AuthFilter.Tuple input) { public boolean authorize(Principal principal, String role) {
return new SecurityContext() { return principal != null

&& validUser.equals(principal.getName())
@Override && validRole.equals(role);
public Principal getUserPrincipal() {
return input.getPrincipal();
}

@Override
public boolean isUserInRole(String role) {
return getUserPrincipal() != null
&& validUser.equals(getUserPrincipal().getName())
&& validRole.equals(role);
}

@Override
public boolean isSecure() {
return input.getContainerRequestContext().getSecurityContext().isSecure();
}

@Override
public String getAuthenticationScheme() {
return SecurityContext.BASIC_AUTH;
}
};
} }
}; };
} }
Expand Down

0 comments on commit 4110aa4

Please sign in to comment.