Skip to content

Commit

Permalink
Always return the WWW-Authenticate header for 401 error (#1007)
Browse files Browse the repository at this point in the history
* Always return the WWW-Authenticate header for 401 error

* constant for the realm name
  • Loading branch information
leleuj committed Oct 9, 2017
1 parent dfc9fd9 commit 90ecc0b
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 68 deletions.
@@ -1,5 +1,6 @@
package org.pac4j.core.client; package org.pac4j.core.client;


import org.pac4j.core.context.HttpConstants;
import org.pac4j.core.context.Pac4jConstants; import org.pac4j.core.context.Pac4jConstants;
import org.pac4j.core.context.WebContext; import org.pac4j.core.context.WebContext;
import org.pac4j.core.credentials.Credentials; import org.pac4j.core.credentials.Credentials;
Expand Down Expand Up @@ -84,14 +85,18 @@ public RedirectAction getRedirectAction(final WebContext context) {
logger.info("AJAX request detected -> returning 401"); logger.info("AJAX request detected -> returning 401");
RedirectAction action = redirectActionBuilder.redirect(context); RedirectAction action = redirectActionBuilder.redirect(context);
cleanRequestedUrl(context); cleanRequestedUrl(context);
throw HttpAction.unauthorized("AJAX request -> 401", context, null, action.getLocation()); final String url = action.getLocation();
if (CommonHelper.isNotBlank(url)) {
context.setResponseHeader(HttpConstants.LOCATION_HEADER, url);
}
throw HttpAction.unauthorized("AJAX request -> 401", context);
} }
// authentication has already been tried -> unauthorized // authentication has already been tried -> unauthorized
final String attemptedAuth = (String) context.getSessionStore().get(context, getName() + ATTEMPTED_AUTHENTICATION_SUFFIX); final String attemptedAuth = (String) context.getSessionStore().get(context, getName() + ATTEMPTED_AUTHENTICATION_SUFFIX);
if (CommonHelper.isNotBlank(attemptedAuth)) { if (CommonHelper.isNotBlank(attemptedAuth)) {
cleanAttemptedAuthentication(context); cleanAttemptedAuthentication(context);
cleanRequestedUrl(context); cleanRequestedUrl(context);
throw HttpAction.unauthorized("authentication already tried -> forbidden", context, null, null); throw HttpAction.unauthorized("authentication already tried -> forbidden", context);
} }


return redirectActionBuilder.redirect(context); return redirectActionBuilder.redirect(context);
Expand Down
Expand Up @@ -73,4 +73,6 @@ public interface Pac4jConstants {
String CENTRAL_LOGOUT = "centralLogout"; String CENTRAL_LOGOUT = "centralLogout";


String DEFAULT_CLIENT_NAME_PARAMETER = "client_name"; String DEFAULT_CLIENT_NAME_PARAMETER = "client_name";

String DEFAULT_REALM_NAME = "authentication required";
} }
Expand Up @@ -256,7 +256,7 @@ protected HttpAction redirectToIdentityProvider(final C context, final List<Clie
* @return an unauthorized error * @return an unauthorized error
*/ */
protected HttpAction unauthorized(final C context, final List<Client> currentClients) { protected HttpAction unauthorized(final C context, final List<Client> currentClients) {
return HttpAction.unauthorized("unauthorized", context, null, null); return HttpAction.unauthorized("unauthorized", context);
} }


public ClientFinder getClientFinder() { public ClientFinder getClientFinder() {
Expand Down
Expand Up @@ -78,62 +78,9 @@ public static HttpAction ok(final String message, final WebContext context, Stri
* *
* @param message message * @param message message
* @param context context * @param context context
* @param realmName realm name
* @return a basic auth popup credentials * @return a basic auth popup credentials
*/ */
public static HttpAction unauthorized(final String message, final WebContext context, final String realmName) { public static HttpAction unauthorized(final String message, final WebContext context) {
return unauthorized(message, context, realmName, null);
}

/**
* Build a basic auth popup credentials.
*
* @param message message
* @param context context
* @param realmName realm name
* @param url url
* @return a basic auth popup credentials
*/
public static HttpAction unauthorized(final String message, final WebContext context, final String realmName, final String url) {
if (CommonHelper.isNotBlank(realmName)) {
context.setResponseHeader(HttpConstants.AUTHENTICATE_HEADER, "Basic realm=\"" + realmName + "\"");
}
if (CommonHelper.isNotBlank(url)) {
context.setResponseHeader(HttpConstants.LOCATION_HEADER, url);
}
context.setResponseStatus(HttpConstants.UNAUTHORIZED);
return new HttpAction(message, HttpConstants.UNAUTHORIZED);
}

/**
* Build a digest auth popup credentials.
*
* @param message message
* @param context context
* @param realmName realm name
* @param qop qop
* @param nonce nonce
* @return a digest auth popup credentials
*/
public static HttpAction unauthorizedDigest(final String message, final WebContext context, final String realmName, final String qop,
final String nonce) {
if (CommonHelper.isNotBlank(realmName)) {
context.setResponseHeader(HttpConstants.AUTHENTICATE_HEADER, "Digest realm=\"" + realmName + "\", qop=\""
+ qop + "\", nonce=\"" + nonce + "\"");
}
context.setResponseStatus(HttpConstants.UNAUTHORIZED);
return new HttpAction(message, HttpConstants.UNAUTHORIZED);
}

/**
* Build a response requesting to provide credentials via Kerberos/SPNEGO Negotiate mechanism.
*
* @param message message
* @param context context
* @return 401 Unauthorized with "WWW-Authenticate: Negotiate"
*/
public static HttpAction unauthorizedNegotiate(final String message, final WebContext context) {
context.setResponseHeader(HttpConstants.AUTHENTICATE_HEADER, "Negotiate");
context.setResponseStatus(HttpConstants.UNAUTHORIZED); context.setResponseStatus(HttpConstants.UNAUTHORIZED);
return new HttpAction(message, HttpConstants.UNAUTHORIZED); return new HttpAction(message, HttpConstants.UNAUTHORIZED);
} }
Expand Down
@@ -1,11 +1,15 @@
package org.pac4j.http.client.direct; package org.pac4j.http.client.direct;


import org.pac4j.core.client.DirectClient; import org.pac4j.core.client.DirectClient;
import org.pac4j.core.context.HttpConstants;
import org.pac4j.core.context.Pac4jConstants;
import org.pac4j.core.context.WebContext;
import org.pac4j.core.credentials.UsernamePasswordCredentials; import org.pac4j.core.credentials.UsernamePasswordCredentials;
import org.pac4j.core.credentials.authenticator.Authenticator; import org.pac4j.core.credentials.authenticator.Authenticator;
import org.pac4j.core.profile.CommonProfile; import org.pac4j.core.profile.CommonProfile;
import org.pac4j.core.credentials.extractor.BasicAuthExtractor; import org.pac4j.core.credentials.extractor.BasicAuthExtractor;
import org.pac4j.core.profile.creator.ProfileCreator; import org.pac4j.core.profile.creator.ProfileCreator;
import org.pac4j.core.util.CommonHelper;


/** /**
* <p>This class is the client to authenticate users directly through HTTP basic auth.</p> * <p>This class is the client to authenticate users directly through HTTP basic auth.</p>
Expand All @@ -15,7 +19,10 @@
*/ */
public class DirectBasicAuthClient extends DirectClient<UsernamePasswordCredentials, CommonProfile> { public class DirectBasicAuthClient extends DirectClient<UsernamePasswordCredentials, CommonProfile> {


public DirectBasicAuthClient() {} private String realmName = Pac4jConstants.DEFAULT_REALM_NAME;

public DirectBasicAuthClient() {
}


public DirectBasicAuthClient(final Authenticator usernamePasswordAuthenticator) { public DirectBasicAuthClient(final Authenticator usernamePasswordAuthenticator) {
defaultAuthenticator(usernamePasswordAuthenticator); defaultAuthenticator(usernamePasswordAuthenticator);
Expand All @@ -29,6 +36,31 @@ public DirectBasicAuthClient(final Authenticator usernamePasswordAuthenticator,


@Override @Override
protected void clientInit() { protected void clientInit() {
CommonHelper.assertNotBlank("realmName", this.realmName);

defaultCredentialsExtractor(new BasicAuthExtractor(getName())); defaultCredentialsExtractor(new BasicAuthExtractor(getName()));
} }

@Override
protected UsernamePasswordCredentials retrieveCredentials(final WebContext context) {
// set the www-authenticate in case of error
context.setResponseHeader(HttpConstants.AUTHENTICATE_HEADER, "Basic realm=\"" + realmName + "\"");

return super.retrieveCredentials(context);
}

public String getRealmName() {
return realmName;
}

public void setRealmName(final String realmName) {
this.realmName = realmName;
}

@Override
public String toString() {
return CommonHelper.toString(this.getClass(), "name", getName(), "credentialsExtractor", getCredentialsExtractor(),
"authenticator", getAuthenticator(), "profileCreator", getProfileCreator(),
"authorizationGenerators", getAuthorizationGenerators(), "realmName", this.realmName);
}
} }
@@ -1,6 +1,7 @@
package org.pac4j.http.client.direct; package org.pac4j.http.client.direct;


import org.pac4j.core.client.DirectClient; import org.pac4j.core.client.DirectClient;
import org.pac4j.core.context.HttpConstants;
import org.pac4j.core.context.WebContext; import org.pac4j.core.context.WebContext;
import org.pac4j.core.credentials.authenticator.Authenticator; import org.pac4j.core.credentials.authenticator.Authenticator;
import org.pac4j.core.exception.HttpAction; import org.pac4j.core.exception.HttpAction;
Expand Down Expand Up @@ -53,7 +54,9 @@ protected DigestCredentials retrieveCredentials(final WebContext context) {
DigestCredentials credentials = super.retrieveCredentials(context); DigestCredentials credentials = super.retrieveCredentials(context);
if (credentials == null) { if (credentials == null) {
String nonce = calculateNonce(); String nonce = calculateNonce();
HttpAction.unauthorizedDigest("Digest required", context, realm, "auth", nonce); context.setResponseHeader(HttpConstants.AUTHENTICATE_HEADER, "Digest realm=\"" + realm + "\", qop=\"auth\", nonce=\""
+ nonce + "\"");
throw HttpAction.unauthorized("Digest required", context);
} }
return credentials; return credentials;
} }
Expand Down
@@ -1,6 +1,8 @@
package org.pac4j.http.client.indirect; package org.pac4j.http.client.indirect;


import org.pac4j.core.client.IndirectClient; import org.pac4j.core.client.IndirectClient;
import org.pac4j.core.context.HttpConstants;
import org.pac4j.core.context.Pac4jConstants;
import org.pac4j.core.redirect.RedirectAction; import org.pac4j.core.redirect.RedirectAction;
import org.pac4j.core.context.WebContext; import org.pac4j.core.context.WebContext;
import org.pac4j.core.credentials.authenticator.Authenticator; import org.pac4j.core.credentials.authenticator.Authenticator;
Expand All @@ -23,7 +25,7 @@
*/ */
public class IndirectBasicAuthClient extends IndirectClient<UsernamePasswordCredentials, CommonProfile> { public class IndirectBasicAuthClient extends IndirectClient<UsernamePasswordCredentials, CommonProfile> {


private String realmName = "authentication required"; private String realmName = Pac4jConstants.DEFAULT_REALM_NAME;


public IndirectBasicAuthClient() {} public IndirectBasicAuthClient() {}


Expand Down Expand Up @@ -54,20 +56,23 @@ protected UsernamePasswordCredentials retrieveCredentials(final WebContext conte
CommonHelper.assertNotNull("credentialsExtractor", getCredentialsExtractor()); CommonHelper.assertNotNull("credentialsExtractor", getCredentialsExtractor());
CommonHelper.assertNotNull("authenticator", getAuthenticator()); CommonHelper.assertNotNull("authenticator", getAuthenticator());


// set the www-authenticate in case of error
context.setResponseHeader(HttpConstants.AUTHENTICATE_HEADER, "Basic realm=\"" + realmName + "\"");

final UsernamePasswordCredentials credentials; final UsernamePasswordCredentials credentials;
try { try {
// retrieve credentials // retrieve credentials
credentials = getCredentialsExtractor().extract(context); credentials = getCredentialsExtractor().extract(context);
logger.debug("credentials : {}", credentials); logger.debug("credentials : {}", credentials);


if (credentials == null) { if (credentials == null) {
throw HttpAction.unauthorized("Requires authentication", context, this.realmName, null); throw HttpAction.unauthorized("Requires authentication", context);
} }


// validate credentials // validate credentials
getAuthenticator().validate(credentials, context); getAuthenticator().validate(credentials, context);
} catch (final CredentialsException e) { } catch (final CredentialsException e) {
throw HttpAction.unauthorized("Requires authentication", context, this.realmName, null); throw HttpAction.unauthorized("Requires authentication", context);
} }


return credentials; return credentials;
Expand All @@ -83,8 +88,11 @@ public void setRealmName(String realmName) {


@Override @Override
public String toString() { public String toString() {
return CommonHelper.toString(this.getClass(), "callbackUrl", this.callbackUrl, "name", getName(), return CommonHelper.toString(this.getClass(), "name", getName(), "callbackUrl", this.callbackUrl,
"realmName", this.realmName, "extractor", getCredentialsExtractor(), "authenticator", getAuthenticator(), "callbackUrlResolver", this.callbackUrlResolver, "ajaxRequestResolver", getAjaxRequestResolver(),
"profileCreator", getProfileCreator()); "redirectActionBuilder", getRedirectActionBuilder(), "credentialsExtractor", getCredentialsExtractor(),
"authenticator", getAuthenticator(), "profileCreator", getProfileCreator(),
"logoutActionBuilder", getLogoutActionBuilder(), "authorizationGenerators", getAuthorizationGenerators(),
"realmName", this.realmName);
} }
} }
Expand Up @@ -7,6 +7,8 @@
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;


import org.pac4j.core.context.HttpConstants;
import org.pac4j.core.context.Pac4jConstants;
import org.pac4j.core.context.WebContext; import org.pac4j.core.context.WebContext;
import org.pac4j.core.credentials.TokenCredentials; import org.pac4j.core.credentials.TokenCredentials;
import org.pac4j.core.credentials.authenticator.Authenticator; import org.pac4j.core.credentials.authenticator.Authenticator;
Expand Down Expand Up @@ -54,6 +56,8 @@ public class JwtAuthenticator extends ProfileDefinitionAware<JwtProfile> impleme


private List<SignatureConfiguration> signatureConfigurations = new ArrayList<>(); private List<SignatureConfiguration> signatureConfigurations = new ArrayList<>();


private String realmName = Pac4jConstants.DEFAULT_REALM_NAME;

public JwtAuthenticator() {} public JwtAuthenticator() {}


public JwtAuthenticator(final List<SignatureConfiguration> signatureConfigurations) { public JwtAuthenticator(final List<SignatureConfiguration> signatureConfigurations) {
Expand All @@ -77,6 +81,8 @@ public JwtAuthenticator(final SignatureConfiguration signatureConfiguration, fin


@Override @Override
protected void internalInit() { protected void internalInit() {
CommonHelper.assertNotBlank("realmName", this.realmName);

defaultProfileDefinition(new CommonProfileDefinition<>(x -> new JwtProfile())); defaultProfileDefinition(new CommonProfileDefinition<>(x -> new JwtProfile()));


if (signatureConfigurations.isEmpty()) { if (signatureConfigurations.isEmpty()) {
Expand Down Expand Up @@ -124,6 +130,11 @@ public void validate(final TokenCredentials credentials, final WebContext contex
init(); init();
final String token = credentials.getToken(); final String token = credentials.getToken();


if (context != null) {
// set the www-authenticate in case of error
context.setResponseHeader(HttpConstants.AUTHENTICATE_HEADER, "Bearer realm=\"" + realmName + "\"");
}

try { try {
// Parse the token // Parse the token
JWT jwt = JWTParser.parse(token); JWT jwt = JWTParser.parse(token);
Expand Down Expand Up @@ -280,9 +291,17 @@ public void setEncryptionConfigurations(final List<EncryptionConfiguration> encr
this.encryptionConfigurations = encryptionConfigurations; this.encryptionConfigurations = encryptionConfigurations;
} }


public String getRealmName() {
return realmName;
}

public void setRealmName(final String realmName) {
this.realmName = realmName;
}

@Override @Override
public String toString() { public String toString() {
return CommonHelper.toString(this.getClass(), "signatureConfigurations", signatureConfigurations, return CommonHelper.toString(this.getClass(), "signatureConfigurations", signatureConfigurations,
"encryptionConfigurations", encryptionConfigurations); "encryptionConfigurations", encryptionConfigurations, "realmName", this.realmName);
} }
} }
@@ -1,6 +1,7 @@
package org.pac4j.kerberos.client.indirect; package org.pac4j.kerberos.client.indirect;


import org.pac4j.core.client.IndirectClient; import org.pac4j.core.client.IndirectClient;
import org.pac4j.core.context.HttpConstants;
import org.pac4j.core.context.WebContext; import org.pac4j.core.context.WebContext;
import org.pac4j.core.credentials.authenticator.Authenticator; import org.pac4j.core.credentials.authenticator.Authenticator;
import org.pac4j.core.exception.CredentialsException; import org.pac4j.core.exception.CredentialsException;
Expand Down Expand Up @@ -41,18 +42,21 @@ protected KerberosCredentials retrieveCredentials(final WebContext context) {
CommonHelper.assertNotNull("credentialsExtractor", getCredentialsExtractor()); CommonHelper.assertNotNull("credentialsExtractor", getCredentialsExtractor());
CommonHelper.assertNotNull("authenticator", getAuthenticator()); CommonHelper.assertNotNull("authenticator", getAuthenticator());


// set the www-authenticate in case of error
context.setResponseHeader(HttpConstants.AUTHENTICATE_HEADER, "Negotiate");

final KerberosCredentials credentials; final KerberosCredentials credentials;
try { try {
// retrieve credentials // retrieve credentials
credentials = getCredentialsExtractor().extract(context); credentials = getCredentialsExtractor().extract(context);
logger.debug("kerberos credentials : {}", credentials); logger.debug("kerberos credentials : {}", credentials);
if (credentials == null) { if (credentials == null) {
throw HttpAction.unauthorizedNegotiate("Kerberos Header not found", context); throw HttpAction.unauthorized("Kerberos Header not found", context);
} }
// validate credentials // validate credentials
getAuthenticator().validate(credentials, context); getAuthenticator().validate(credentials, context);
} catch (final CredentialsException e) { } catch (final CredentialsException e) {
throw HttpAction.unauthorizedNegotiate("Kerberos auth failed", context); throw HttpAction.unauthorized("Kerberos auth failed", context);
} }


return credentials; return credentials;
Expand Down

0 comments on commit 90ecc0b

Please sign in to comment.