org.picketlink.http.internal.authentication.schemes.TokenAuthenticationScheme Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2012, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.picketlink.http.internal.authentication.schemes;
import org.picketlink.Identity;
import org.picketlink.authentication.AuthenticationException;
import org.picketlink.config.http.TokenAuthenticationConfiguration;
import org.picketlink.credential.DefaultLoginCredentials;
import org.picketlink.http.authentication.HttpAuthenticationScheme;
import org.picketlink.idm.credential.Token;
import org.picketlink.idm.credential.TokenCredential;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import static org.picketlink.idm.credential.Token.Builder.create;
import static org.picketlink.idm.credential.Token.Consumer;
import static org.picketlink.idm.credential.Token.Provider;
/**
* A custom {@link org.picketlink.http.authentication.HttpAuthenticationScheme} that knows how to extract a header from
* the request containing a token to authenticate/re-authenticate an user.
*
* Tokens are issued by providing specific credentials for the primary authentication scheme. This scheme will be used
* to validate user's credentials (eg.: username/password over BASIC) and if successful, issue a token.
*
* By default, the primary authentication scheme is {@link org.picketlink.http.internal.authentication.schemes.BasicAuthenticationScheme}. In order to
* change it, subclasses may override the getPrimaryAuthenticationScheme
method.
*
* Once a token is issued, it will be written to the {@link javax.servlet.http.HttpServletResponse} using a JSON format. In order to
* change how tokens are returned to clients, subclasses may override the {@link org.picketlink.http.internal.authentication.schemes.TokenAuthenticationScheme#writeToken(String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}.
*
* This scheme is used by the {@link org.picketlink.http.internal.SecurityFilter}, which is configured in the web application
* deployment descriptor (web.xml).
*
* @author Pedro Igor
*/
public class TokenAuthenticationScheme implements HttpAuthenticationScheme {
public static final String AUTHORIZATION_TOKEN_HEADER_NAME = "Authorization";
public static final String AUTHENTICATION_SCHEME_NAME = "Token";
public static final String REQUIRES_AUTHENTICATION_HEADER_NAME = "WWW-Authenticate";
@Inject
private Instance identityInstance;
@Inject
private Instance credentialsInstance;
@Inject
private BasicAuthenticationScheme basicAuthenticationScheme;
@Inject
private Instance> tokenProvider;
@Inject
private Instance> tokenConsumer;
@Override
public void initialize(TokenAuthenticationConfiguration config) {
}
@Override
public void extractCredential(HttpServletRequest request, DefaultLoginCredentials creds) {
// we first try to extract the credentials using the primary authentication scheme
getPrimaryAuthenticationScheme().extractCredential(request, creds);
// if credentials are not present, we try to extract the token from the request.
if (creds.getCredential() == null) {
String extractedToken = extractTokenFromRequest(request);
if (extractedToken != null) {
creds.setCredential(createCredential(extractedToken));
}
}
}
/**
* We use a 401 http status code to sinalize to clients that authentication is required.
*
* We only challenge clients if the authentication failed. In other words, if there is a token in the request bu it is
* invalid.
*
* @param request
* @param response
*
* @throws java.io.IOException
*/
@Override
public void challengeClient(HttpServletRequest request, HttpServletResponse response) {
try {
if (isPrimaryAuthenticationRequest()) {
getPrimaryAuthenticationScheme().challengeClient(request, response);
} else {
response.setHeader(REQUIRES_AUTHENTICATION_HEADER_NAME, AUTHENTICATION_SCHEME_NAME);
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
} catch (Exception e) {
throw new RuntimeException("Could not challeng client credentials.", e);
}
}
@Override
public void onPostAuthentication(HttpServletRequest request, HttpServletResponse response) {
if (isPrimaryAuthenticationRequest() && getIdentity().isLoggedIn()) {
String issuedToken = issueToken(request, response);
writeToken(issuedToken, request, response);
}
}
/**
* Returns the current {@link org.picketlink.credential.DefaultLoginCredentials} associated with the request.
*
* @return
*/
protected DefaultLoginCredentials getCredentials() {
return this.credentialsInstance.get();
}
/**
* Returns the current {@link org.picketlink.Identity} associated with the request.
*
* @return
*/
protected Identity getIdentity() {
return this.identityInstance.get();
}
/**
* Returns the primary {@link org.picketlink.http.authentication.HttpAuthenticationScheme} that will be used to validate user's
* credential before issuing a new token.
*
* Default authentication scheme is {@link org.picketlink.http.internal.authentication.schemes.BasicAuthenticationScheme}.
*
* @return
*/
protected HttpAuthenticationScheme getPrimaryAuthenticationScheme() {
return this.basicAuthenticationScheme;
}
/**
* Extracts the token from the {@link javax.servlet.http.HttpServletRequest}.
* Subclasses can override this method to customize how tokens are extracted from the request.
*
* @param request
* @return A String representing the token extracted from the request.
*/
protected String extractTokenFromRequest(HttpServletRequest request) {
String authorizationHeader = request.getHeader(AUTHORIZATION_TOKEN_HEADER_NAME);
if (authorizationHeader != null && authorizationHeader.contains(AUTHENTICATION_SCHEME_NAME)) {
return authorizationHeader.substring(AUTHENTICATION_SCHEME_NAME.length() + 1);
}
return null;
}
/**
* Creates a {@link org.picketlink.idm.credential.TokenCredential} using the token previously extracted from the request.
*
* Subclasses can override this method to customize how the credential is created. Defaults to an instance of {@link org.picketlink.idm.credential.TokenCredential}.
*
* @param extractedToken The token previously extracted from the request.
* @return
*/
protected TokenCredential createCredential(String extractedToken) {
Token token;
Provider tokenProvider = getTokenProvider();
if (tokenProvider != null) {
token = create(getTokenProvider().getTokenType().getName(), extractedToken);
} else {
Consumer tokenConsumer = getTokenConsumer();
if (tokenConsumer == null) {
throw new AuthenticationException("You must provide a " + Provider.class.getName() + " or " + Consumer.class.getName() + ".");
}
token = create(getTokenConsumer().getTokenType().getName(), extractedToken);
}
return new TokenCredential(token);
}
/**
* Issues a token for a previously authenticated {@link org.picketlink.idm.model.Account} using the
* configured {@link org.picketlink.idm.credential.Token.Provider}.
*
* @param request
* @param response
*/
protected String issueToken(HttpServletRequest request, HttpServletResponse response) {
Provider tokenProvider = getTokenProvider();
if (tokenProvider == null) {
throw new AuthenticationException("No " + Provider.class.getName() + " was found.");
}
return tokenProvider.issue(getIdentity().getAccount()).getToken();
}
/**
* Writes the issuedToken
to the {@link javax.servlet.http.HttpServletResponse}.
*
* @param issuedToken
* @param request
* @param response
*/
protected void writeToken(String issuedToken, HttpServletRequest request, HttpServletResponse response) {
try {
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = response.getWriter();
writer.print("{\"authctoken\":\"" + issuedToken + "\"}");
writer.flush();
} catch (Exception e) {
throw new AuthenticationException("Could not write token to response.", e);
}
}
protected Provider getTokenProvider() {
if (this.tokenProvider.isAmbiguous()) {
throw new AuthenticationException("You must provide exactly one " + Provider.class.getName() + " implementation.");
}
if (!this.tokenProvider.isUnsatisfied()) {
return this.tokenProvider.get();
}
return null;
}
protected Consumer getTokenConsumer() {
if (this.tokenConsumer.isAmbiguous()) {
throw new AuthenticationException("You must provide exactly one " + Consumer.class.getName() + " implementation.");
}
if (!this.tokenConsumer.isUnsatisfied()) {
return this.tokenConsumer.get();
}
return null;
}
private boolean isPrimaryAuthenticationRequest() {
return getCredentials().getCredential() != null && !TokenCredential.class.isInstance(getCredentials().getCredential());
}
}