made JWTAuthentication filter work again. Ensured realms not supporting
the JTWtoken based are not considdered when logging in via API key
This commit is contained in:
@ -129,7 +129,7 @@ public class AccountControl implements Serializable {
|
||||
Subject currentUser = SecurityUtils.getSubject();
|
||||
String currentLoggedInUser = currentUser.getPrincipal().toString();
|
||||
|
||||
account.setLastUpdatedBy(currentLoggedInUser);
|
||||
account.setLastUpdatedBy(currentLoggedInUser); // FIXME - should be done via updateable
|
||||
account.setLastUpdatedOn(now);
|
||||
|
||||
boolean newAccount = (account.getCreatedOn() == null);
|
||||
|
||||
@ -19,6 +19,7 @@ import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import javax.persistence.Basic;
|
||||
import javax.persistence.Cacheable;
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
@ -27,6 +28,7 @@ import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.NamedQueries;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.QueryHint;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Temporal;
|
||||
import javax.persistence.TemporalType;
|
||||
@ -41,13 +43,18 @@ import org.hibernate.annotations.Type;
|
||||
* @author Joern Muehlencord <joern at muehlencord.de>
|
||||
*/
|
||||
@Entity
|
||||
@Cacheable
|
||||
@Table(name = "api_key")
|
||||
@XmlRootElement
|
||||
@NamedQueries({
|
||||
@NamedQuery(name = "ApiKeyEntity.findAll", query = "SELECT a FROM ApiKeyEntity a"),
|
||||
@NamedQuery(name = "ApiKeyEntity.findByApiKey", query = "SELECT a FROM ApiKeyEntity a WHERE a.apiKey = :apiKey"),
|
||||
@NamedQuery(name = "ApiKeyEntity.findByApiKey", query = "SELECT a FROM ApiKeyEntity a WHERE a.apiKey = :apiKey", hints = {
|
||||
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
|
||||
@QueryHint(name = "org.hibernate.cacheRegion", value = "Queries")}),
|
||||
@NamedQuery(name = "ApiKeyEntity.findByIssuedOn", query = "SELECT a FROM ApiKeyEntity a WHERE a.issuedOn = :issuedOn"),
|
||||
@NamedQuery(name = "ApiKeyEntity.findByAccount", query = "SELECT a FROM ApiKeyEntity a WHERE a.account = :account"),
|
||||
@NamedQuery(name = "ApiKeyEntity.findByAccount", query = "SELECT a FROM ApiKeyEntity a WHERE a.account = :account", hints = {
|
||||
@QueryHint(name = "org.hibernate.cacheable", value = "true"),
|
||||
@QueryHint(name = "org.hibernate.cacheRegion", value = "Queries")}),
|
||||
@NamedQuery(name = "ApiKeyEntity.findByExpiration", query = "SELECT a FROM ApiKeyEntity a WHERE a.expiration = :expiration")})
|
||||
public class ApiKeyEntity implements Serializable {
|
||||
|
||||
|
||||
@ -94,8 +94,14 @@ public final class JWTAuthenticationFilter extends AuthenticatingFilter {
|
||||
|
||||
if (isLoggedAttempt(request, response)) {
|
||||
String jwtToken = getAuthzHeader(request);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("found jwtToke in header = {}", jwtToken);
|
||||
}
|
||||
|
||||
if (jwtToken != null) {
|
||||
return createToken(jwtToken);
|
||||
JWTObject jwtObject = apiKeyService.getJWTObject(jwtToken);
|
||||
return new JWTAuthenticationToken(jwtObject.getUserName(), jwtToken);
|
||||
// return createToken(jwtToken);
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +154,7 @@ public final class JWTAuthenticationFilter extends AuthenticatingFilter {
|
||||
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) {
|
||||
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 {
|
||||
|
||||
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright 2019 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.shiro.pam;
|
||||
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.authc.AuthenticationInfo;
|
||||
import org.apache.shiro.authc.AuthenticationToken;
|
||||
import org.apache.shiro.authc.UnknownAccountException;
|
||||
import org.apache.shiro.authc.pam.AbstractAuthenticationStrategy;
|
||||
import org.apache.shiro.realm.Realm;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author joern.muehlencord
|
||||
*/
|
||||
public class AllSupportedSuccessfulStrategy extends AbstractAuthenticationStrategy {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AllSupportedSuccessfulStrategy.class);
|
||||
|
||||
public AuthenticationInfo beforeAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
|
||||
if (!realm.supports(token)) {
|
||||
return info;
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo info, AuthenticationInfo aggregate, Throwable t)
|
||||
throws AuthenticationException {
|
||||
if (t != null) {
|
||||
if (t instanceof AuthenticationException) {
|
||||
//propagate:
|
||||
throw ((AuthenticationException) t);
|
||||
} else {
|
||||
String msg = "Unable to acquire account data from realm [" + realm + "]. The ["
|
||||
+ getClass().getName() + " implementation requires all configured realm(s) to operate successfully "
|
||||
+ "for a successful authentication.";
|
||||
throw new AuthenticationException(msg, t);
|
||||
}
|
||||
}
|
||||
if (info == null) {
|
||||
if (realm.supports(token)) {
|
||||
String msg = "Realm [" + realm + "] could not find any associated account data for the submitted "
|
||||
+ "AuthenticationToken [" + token + "]. The [" + getClass().getName() + "] implementation requires "
|
||||
+ "all configured realm(s) to acquire valid account data for a submitted token during the "
|
||||
+ "log-in process.";
|
||||
throw new UnknownAccountException(msg);
|
||||
} else {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Realm [{}] does not support token of type [{}], skipped realm", realm, token);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOGGER.debug("Account successfully authenticated using realm [{}]", realm);
|
||||
}
|
||||
|
||||
// If non-null account is returned, then the realm was able to authenticate the
|
||||
// user - so merge the account with any accumulated before
|
||||
// if the realm does not support the token, null is returned
|
||||
if (realm.supports(token)) {
|
||||
merge(info, aggregate);
|
||||
}
|
||||
|
||||
return aggregate;
|
||||
}
|
||||
|
||||
}
|
||||
@ -54,15 +54,98 @@ public class AccountRealm extends JdbcRealm {
|
||||
|
||||
public AccountRealm() {
|
||||
this.authenticationQuery = "select al.account_password from account a, account_login al where al.account = a.id and a.username = ? and status not in ('LOCKED','DELETED','DISABLED')";
|
||||
this.userRolesQuery = "select r.role_name from application_role r, account_role ar, account a WHERE a.username = ? AND a.id = ar.account AND ar.account_role = r.id";
|
||||
this.userRolesQuery = "select r.role_name from application_role r, account_role ar, account a WHERE a.username = ? AND a.id = ar.account AND ar.account_role = r.id AND r.application = ?";
|
||||
this.permissionsQuery = "select permission_name from application_role appr, role_permission rp, application_permission appp WHERE appr.role_name = ? AND appr.application = ? AND rp.application_role = appr.id AND rp.role_permission = appp.id";
|
||||
this.permissionsLookupEnabled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(AuthenticationToken token) {
|
||||
super.supports(token);
|
||||
return token != null && (token instanceof JWTAuthenticationToken || token instanceof UsernamePasswordToken);
|
||||
return (token != null && ((JWTAuthenticationToken.class.isAssignableFrom(token.getClass())) || (UsernamePasswordToken.class.isAssignableFrom(token.getClass()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
Set<String> roleNames = new LinkedHashSet<>();
|
||||
try {
|
||||
ps = conn.prepareStatement(userRolesQuery);
|
||||
ps.setString(1, username);
|
||||
ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm
|
||||
|
||||
// Execute query
|
||||
rs = ps.executeQuery();
|
||||
|
||||
// Loop over results and add each returned role to a set
|
||||
while (rs.next()) {
|
||||
|
||||
String roleName = rs.getString(1);
|
||||
|
||||
// Add the role to the list of names if it isn't null
|
||||
if (roleName != null) {
|
||||
roleNames.add(roleName);
|
||||
} else {
|
||||
LOGGER.error("Null role name found while retrieving role names for user [{}]", username);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
JdbcUtils.closeResultSet(rs);
|
||||
JdbcUtils.closeStatement(ps);
|
||||
}
|
||||
return roleNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* overwritten getPermissions. Only change is to inject the applicationId
|
||||
* into the the query
|
||||
*
|
||||
* @param conn the connection to use
|
||||
* @param username the user to lookup
|
||||
* @param roleNames the users roles
|
||||
* @return a list of permissions
|
||||
* @throws SQLException if the SQL query fails
|
||||
*/
|
||||
@Override
|
||||
protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("user {} has the following roles: {}", username, roleNames.toString());
|
||||
LOGGER.debug("looking up permissions for user");
|
||||
}
|
||||
|
||||
PreparedStatement ps = null;
|
||||
Set<String> permissions = new LinkedHashSet<>();
|
||||
try {
|
||||
ps = conn.prepareStatement(permissionsQuery);
|
||||
for (String roleName : roleNames) {
|
||||
|
||||
ps.setString(1, roleName);
|
||||
ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm
|
||||
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
// Execute query
|
||||
rs = ps.executeQuery();
|
||||
// Loop over results and add each returned role to a set
|
||||
while (rs.next()) {
|
||||
String permissionString = rs.getString(1);
|
||||
// Add the permission to the set of permissions
|
||||
permissions.add(permissionString);
|
||||
}
|
||||
} finally {
|
||||
JdbcUtils.closeResultSet(rs);
|
||||
}
|
||||
|
||||
}
|
||||
} finally {
|
||||
JdbcUtils.closeStatement(ps);
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("user {} has the following permissions: {}", username, permissions.toString());
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
public boolean isJwtAuthentication(AuthenticationToken token) {
|
||||
@ -85,53 +168,6 @@ public class AccountRealm extends JdbcRealm {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* overwritten getPermissions. Only change is to inject the applicationId
|
||||
* into the the query
|
||||
*
|
||||
* @param conn the connection to use
|
||||
* @param username the user to lookup
|
||||
* @param roleNames the users roles
|
||||
* @return a list of permissions
|
||||
* @throws SQLException if the SQL query fails
|
||||
*/
|
||||
@Override
|
||||
protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
Set<String> permissions = new LinkedHashSet<>();
|
||||
try {
|
||||
ps = conn.prepareStatement(permissionsQuery);
|
||||
for (String roleName : roleNames) {
|
||||
|
||||
ps.setString(1, roleName);
|
||||
ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm
|
||||
|
||||
ResultSet rs = null;
|
||||
|
||||
try {
|
||||
// Execute query
|
||||
rs = ps.executeQuery();
|
||||
|
||||
// Loop over results and add each returned role to a set
|
||||
while (rs.next()) {
|
||||
|
||||
String permissionString = rs.getString(1);
|
||||
|
||||
// Add the permission to the set of permissions
|
||||
permissions.add(permissionString);
|
||||
}
|
||||
} finally {
|
||||
JdbcUtils.closeResultSet(rs);
|
||||
}
|
||||
|
||||
}
|
||||
} finally {
|
||||
JdbcUtils.closeStatement(ps);
|
||||
}
|
||||
|
||||
return permissions;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
|
||||
if (isJwtAuthentication(token)) {
|
||||
|
||||
@ -41,6 +41,11 @@ public class UserNameActiveDirectoryRealm extends ActiveDirectoryRealm {
|
||||
private boolean permissionsLookupEnabled = true;
|
||||
protected String fallbackPrincipalSuffix = null;
|
||||
|
||||
@Override
|
||||
public boolean supports(AuthenticationToken token) {
|
||||
return (token != null && (UsernamePasswordToken.class.isAssignableFrom(token.getClass())));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken token, LdapContextFactory ldapContextFactory) throws NamingException {
|
||||
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
|
||||
|
||||
@ -43,6 +43,10 @@ public class APIExceptionInterceptor {
|
||||
// if an exception is thrown during processing, this is passed in to the catch block below
|
||||
proceedResponse = context.proceed();
|
||||
} catch (Exception ex) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Detailed stacktrace", new Object[]{ex});
|
||||
}
|
||||
|
||||
Response errorResponse;
|
||||
if (ex instanceof APIException) {
|
||||
errorResponse = ((APIException) ex).getHttpResponse();
|
||||
@ -57,11 +61,6 @@ public class APIExceptionInterceptor {
|
||||
} else {
|
||||
errorResponse = new APIException(ex, locale).getHttpResponse();
|
||||
}
|
||||
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(ex.toString(), ex);
|
||||
}
|
||||
|
||||
return errorResponse;
|
||||
}
|
||||
return proceedResponse;
|
||||
|
||||
@ -36,7 +36,7 @@ import javax.ws.rs.ext.Provider;
|
||||
@Provider
|
||||
public class ConstraintViolationMapper implements ExceptionMapper<ConstraintViolationException> {
|
||||
|
||||
private static List<Variant> acceptableMediaTypes = Variant.mediaTypes(MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE).build();
|
||||
private static final List<Variant> ACCEPTABLE_MEDIA_TYPES = Variant.mediaTypes(MediaType.APPLICATION_JSON_TYPE, MediaType.APPLICATION_XML_TYPE).build();
|
||||
|
||||
@Context
|
||||
protected Request request;
|
||||
@ -45,15 +45,15 @@ public class ConstraintViolationMapper implements ExceptionMapper<ConstraintViol
|
||||
public Response toResponse(ConstraintViolationException ex) {
|
||||
Set<ConstraintViolation<?>> constViolations = ex.getConstraintViolations();
|
||||
List<ConstraintViolationEntry> errorList = new ArrayList<>();
|
||||
for (ConstraintViolation<?> constraintViolation : constViolations) {
|
||||
constViolations.forEach((constraintViolation) -> {
|
||||
errorList.add(new ConstraintViolationEntry(constraintViolation));
|
||||
}
|
||||
});
|
||||
GenericEntity<List<ConstraintViolationEntry>> entity = new GenericEntity<List<ConstraintViolationEntry>>(errorList) {};
|
||||
return Response.status(Response.Status.BAD_REQUEST).entity(entity).type(getNegotiatedMediaType()).build();
|
||||
}
|
||||
|
||||
protected MediaType getNegotiatedMediaType() {
|
||||
final Variant selectedMediaType = request.selectVariant(acceptableMediaTypes);
|
||||
final Variant selectedMediaType = request.selectVariant(ACCEPTABLE_MEDIA_TYPES);
|
||||
if (selectedMediaType == null) {
|
||||
return MediaType.APPLICATION_JSON_TYPE;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user