ensured standard API response header is returned if an APIKeyError occurs
This commit is contained in:
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2018 joern.muehlencord.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package de.muehlencord.shared.account.business.account.boundary;
|
||||
|
||||
import de.muehlencord.shared.jeeutil.restexfw.APIError;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author joern.muehlencord
|
||||
*/
|
||||
public enum ApiKeyError implements APIError {
|
||||
|
||||
JWT_NO_TOKEN(Response.Status.BAD_REQUEST, "1000", "jwt_no_token"),
|
||||
JWT_TOKEN_INVALID (Response.Status.FORBIDDEN, "1001", "jwt_token_invalid");
|
||||
|
||||
private final Response.Status status;
|
||||
private final String errorCode;
|
||||
private final String messageKey;
|
||||
|
||||
private ApiKeyError(Response.Status status, String errorCode, String messageKey) {
|
||||
this.status = status;
|
||||
this.errorCode = errorCode;
|
||||
this.messageKey = messageKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response.Status getStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorCode() {
|
||||
return this.errorCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessageKey() {
|
||||
return this.messageKey;
|
||||
}
|
||||
}
|
||||
@ -137,11 +137,19 @@ public class ApiKeyService implements Serializable {
|
||||
|
||||
public boolean validateJWT(String encodedJWT) {
|
||||
JWTDecoder decoder = new JWTDecoder(password, issuer, encodedJWT);
|
||||
ApiKeyEntity validKey = getValidKey(decoder.getSubject(), decoder.getUniqueId(), encodedJWT);
|
||||
ApiKeyEntity validKey;
|
||||
try {
|
||||
validKey = getValidKey(decoder.getSubject(), decoder.getUniqueId(), encodedJWT);
|
||||
} catch (JWTException ex) {
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace(ex.toString(), ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return validKey != null;
|
||||
}
|
||||
|
||||
private ApiKeyEntity getValidKey(String userName, String apiKey, String authorizationHeader) {
|
||||
private ApiKeyEntity getValidKey(String userName, String apiKey, String authorizationHeader) throws JWTException {
|
||||
AccountEntity userAccount = accountControl.getAccountEntity(userName, false);
|
||||
List<ApiKeyEntity> apiKeys = getUsersApiKeys(userAccount);
|
||||
|
||||
@ -151,15 +159,9 @@ public class ApiKeyService implements Serializable {
|
||||
ApiKeyEntity key = it.next();
|
||||
if (key.getApiKey().equals(apiKey)) {
|
||||
ZonedDateTime issuedOn = ZonedDateTime.ofInstant(key.getIssuedOn().toInstant(), ZoneOffset.UTC);
|
||||
String testString;
|
||||
|
||||
try {
|
||||
testString = JWTEncoder.encode(password, issuer, issuedOn, key.getAccount().getUsername(), key.getApiKey(), key.getExpiration());
|
||||
if (authorizationHeader.equals(testString)) {
|
||||
return key;
|
||||
}
|
||||
} catch (JWTException ex) {
|
||||
|
||||
String testString = JWTEncoder.encode(password, issuer, issuedOn, key.getAccount().getUsername(), key.getApiKey(), key.getExpiration());
|
||||
if (authorizationHeader.equals(testString)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -206,7 +208,16 @@ public class ApiKeyService implements Serializable {
|
||||
if (jwtObject.isValid()) {
|
||||
String userName = jwtObject.getUserName();
|
||||
|
||||
ApiKeyEntity keyToLogout = getValidKey(userName, jwtObject.getUnqiueId(), authorizationHeader);
|
||||
ApiKeyEntity keyToLogout;
|
||||
try {
|
||||
keyToLogout = getValidKey(userName, jwtObject.getUnqiueId(), authorizationHeader);
|
||||
} catch (JWTException ex) {
|
||||
if (LOGGER.isTraceEnabled()) {
|
||||
LOGGER.trace(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
keyToLogout = null;
|
||||
}
|
||||
|
||||
if (keyToLogout == null) {
|
||||
// no valid key found - must not happen, JWTVeryfingFIlter should have catched this
|
||||
|
||||
@ -15,22 +15,27 @@
|
||||
*/
|
||||
package de.muehlencord.shared.account.shiro.filter;
|
||||
|
||||
import de.muehlencord.shared.account.business.account.boundary.ApiKeyError;
|
||||
import de.muehlencord.shared.account.business.account.boundary.ApiKeyService;
|
||||
import de.muehlencord.shared.account.business.account.entity.JWTObject;
|
||||
import de.muehlencord.shared.account.shiro.token.JWTAuthenticationToken;
|
||||
import de.muehlencord.shared.account.util.AccountSecurityException;
|
||||
import de.muehlencord.shared.jeeutil.restexfw.APIException;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.Locale;
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonObject;
|
||||
import javax.json.JsonReader;
|
||||
import javax.naming.Context;
|
||||
import javax.naming.InitialContext;
|
||||
import javax.naming.NamingException;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.apache.shiro.authc.UsernamePasswordToken;
|
||||
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
|
||||
@ -45,9 +50,9 @@ public final class JWTAuthenticationFilter extends AuthenticatingFilter {
|
||||
|
||||
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
|
||||
protected static final String AUTHORIZATION_HEADER = "Authorization"; // NOI18N
|
||||
protected static final String USERNAME = "username"; // NOI18N
|
||||
protected static final String PASSWORD = "password"; // NOI18N
|
||||
|
||||
public static final String USERNAME = "username";
|
||||
public static final String PASSWORD = "password";
|
||||
private final ApiKeyService apiKeyService = lookupApiKeyServiceBean();
|
||||
|
||||
public JWTAuthenticationFilter() {
|
||||
@ -97,22 +102,57 @@ public final class JWTAuthenticationFilter extends AuthenticatingFilter {
|
||||
return new UsernamePasswordToken();
|
||||
}
|
||||
|
||||
protected boolean isLoggedAttempt(ServletRequest request, ServletResponse response) {
|
||||
private boolean isLoggedAttempt(ServletRequest request, ServletResponse response) {
|
||||
String authzHeader = getAuthzHeader(request);
|
||||
return authzHeader != null;
|
||||
}
|
||||
|
||||
protected String getAuthzHeader(ServletRequest request) {
|
||||
private String getAuthzHeader(ServletRequest request) {
|
||||
HttpServletRequest httpRequest = WebUtils.toHttp(request);
|
||||
return httpRequest.getHeader(AUTHORIZATION_HEADER);
|
||||
}
|
||||
|
||||
public JWTAuthenticationToken createToken(String token) {
|
||||
private JWTAuthenticationToken createToken(String token) throws AccountSecurityException {
|
||||
if (apiKeyService.validateJWT(token)) {
|
||||
JWTObject jwtObject = apiKeyService.getJWTObject(token);
|
||||
return new JWTAuthenticationToken(jwtObject.getUserName(), token);
|
||||
} else {
|
||||
throw new AuthenticationException("provided API key invalid");
|
||||
throw new APIException(ApiKeyError.JWT_TOKEN_INVALID, Locale.ENGLISH); // TODO - how to get the correct locale
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite cleanup to ensure no exception is thrown if an
|
||||
* AccountSecurityException / APIException is raised during login. As long
|
||||
* as the user is not logged in JERSEYs ExceptionMapper and intercepor
|
||||
* classes are overruled by Shiro.
|
||||
*
|
||||
* @param request the incoming request
|
||||
* @param response the response to return
|
||||
* @param existing the raised exception
|
||||
* @throws ServletException may be thrown by AuthenticatingFilter.cleanup if
|
||||
* existing is not a AccountSecurityException
|
||||
* @throws IOException may be thrown by AuthenticatingFilter.cleanup if
|
||||
* existing is not a AccountSecurityException
|
||||
*/
|
||||
@Override
|
||||
protected void cleanup(ServletRequest request, ServletResponse response, Exception existing) throws ServletException, IOException {
|
||||
if ((existing != null) && (existing.getClass().isAssignableFrom(AccountSecurityException.class))) {
|
||||
HttpServletResponse httpResponse = WebUtils.toHttp(response);
|
||||
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
} else if ((existing != null) && (existing.getClass().isAssignableFrom(APIException.class))) {
|
||||
APIException apiException = (APIException) existing;
|
||||
|
||||
HttpServletResponse httpResponse = WebUtils.toHttp(response);
|
||||
httpResponse.setStatus(apiException.getHttpResponse().getStatus());
|
||||
httpResponse.addHeader(APIException.HTTP_HEADER_X_ERROR, apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ERROR));
|
||||
httpResponse.addHeader(APIException.HTTP_HEADER_X_ERROR_CODE, apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ERROR_CODE));
|
||||
|
||||
if(apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ROOT_CAUSE) != null) {
|
||||
httpResponse.addHeader(APIException.HTTP_HEADER_X_ROOT_CAUSE, apiException.getHttpResponse().getHeaderString(APIException.HTTP_HEADER_X_ROOT_CAUSE));
|
||||
}
|
||||
} else {
|
||||
super.cleanup(request, response, existing);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2018 joern.muehlencord.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
jwt_no_token=No token provided
|
||||
jwt_token_invalid=The provided token is invalid
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2018 joern.muehlencord.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
jwt_no_token=Kein Token vorhanden
|
||||
jwt_token_invalid=Das Token ist ung\u00fcltig
|
||||
@ -0,0 +1,16 @@
|
||||
# Copyright 2018 joern.muehlencord.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
jwt_no_token=No token provided
|
||||
jwt_token_invalid=The provided token is invalid
|
||||
Reference in New Issue
Block a user