Wildfly web.xml security-constraint blockieren basic-auth-header für die JAX-RS-Methoden mit ContainerRequestFilter
Die web-Anwendung entwickle ich aus einigen servlets und auch JAX-RS webservices. Bis jetzt war ich mit einem ContainerRequestFilter zu authentifizieren, die REST-Methode ruft aber jetzt habe ich auch sichern müssen die servlets so entschied ich mich für web.xml um die Sicherheit zu definieren Einschränkungen. Meine web.xml sieht wie folgt aus:
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<security-constraint>
<web-resource-collection>
<web-resource-name>rest</web-resource-name>
<url-pattern>/rest/*</url-pattern>
</web-resource-collection>
</security-constraint>
<security-constraint>
<web-resource-collection>
<web-resource-name>protected</web-resource-name>
<url-pattern>/protected/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>admin</role-name>
</security-role>
<security-role>
<role-name>user</role-name>
</security-role>
<!-- Configure login to be HTTP Basic -->
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>Restricted Zone</realm-name>
</login-config>
</web-app>
Wenn ich die syntax verstehen web.xml korrekt, was für mich bedeutet, dass der Zugriff auf /rest/* (wo alle meine JAX-RS-Methoden sind) ist unbeschränkt, soweit die LoginModules betroffen sind, und alle Zugang zu der /protected/* Pfad (wo ich immer meine sichere servlets) erfordert Grundlegende Berechtigung.
Wenn ich versuche zu öffnen eines der sichere servlets, z.B. /protected/Tests, erhalte ich die basic-auth login-dialog im browser und das Verhalten ist richtig, wenn ich geben Sie die Anmeldeinformationen für einen "admin" - Benutzer, ich bin der Zugriff erlaubt. Ansonsten bekomme ich eine 'Forbidden' - Meldung.
Auch, wenn ich versuche, Zugriff auf alles auf der /rest/Pfad bekomme ich keine basic auth-dialog, das ist, was ich erwarten würde. Aber der Authorization-header bekomme ich in der ContainerRequestFilter ist nicht die, die ich bin, Versand in die REST-Anforderung, aber es ist die, die ich zuvor verwendet, um in zu erhalten /geschützt/servlet.
Unten sind andere Teile des puzzle:
standalone.xml (security-Bereich Domänen)
<security-domain name="PaloSecurityDomain" cache-type="default">
<authentication>
<login-module code="com.palo.security.PaloLoginModule" flag="required"/>
</authentication>
</security-domain>
jboss-web.xml
<?xml version="1.0" encoding="UTF-8"?>
<jboss-web>
<security-domain>PaloSecurityDomain</security-domain>
</jboss-web>
PaloLoginModule.java
package com.palo.security;
import java.security.acl.Group;
import java.util.Set;
import javax.inject.Inject;
import javax.naming.NamingException;
import javax.security.auth.login.LoginException;
import org.apache.log4j.Logger;
import org.jboss.security.SimpleGroup;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.auth.spi.UsernamePasswordLoginModule;
import com.palo.PaloRealmRole;
import com.palo.model.PaloRealmUser;
import com.palo.utils.CdiHelper;
import com.palo.utils.PasswordHandler;
public class PaloRealmLoginModule extends UsernamePasswordLoginModule {
private static Logger logger = Logger
.getLogger(PaloRealmLoginModule.class);
@Inject
private PaloRealmLogic realmLogic;
@Override
protected String getUsersPassword() throws LoginException {
if (null == realmLogic) {
try {
CdiHelper.programmaticInjection(PaloRealmLoginModule.class,
this);
} catch (NamingException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
logger.debug("Getting password for user " + super.getUsername());
PaloRealmUser user = realmLogic.getUserByName(super.getUsername());
if (null == user) {
logger.error("User not found");
throw new LoginException("User " + super.getUsername()
+ " not found");
}
logger.debug("Found " + user.getPassword());
return user.getPassword();
}
@Override
protected Group[] getRoleSets() throws LoginException {
logger.debug("Getting roles for user " + super.getUsername());
if (null == realmLogic) {
try {
CdiHelper.programmaticInjection(PaloRealmLoginModule.class,
this);
} catch (NamingException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}
}
PaloRealmUser user = realmLogic.getUserByName(super.getUsername());
if (null == user) {
throw new LoginException("User " + super.getUsername()
+ " not found");
}
Set<PaloRealmRole> roles = user.getRoles();
Group[] groups = { new SimpleGroup("Roles") };
for (PaloRealmRole role : roles) {
logger.debug("Found role " + role.getRole());
SimplePrincipal prole = new SimplePrincipal(role.getRole());
groups[0].addMember(prole);
}
return groups;
}
@Override
protected boolean validatePassword(String inputPassword,
String expectedPassword) {
logger.debug("Validating password " + inputPassword + "|"
+ expectedPassword);
return PasswordHandler.getInstance().verifyPassword(inputPassword,
expectedPassword);
}
}
SecurityInterceptor.java
package com.palo.web.rest;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.StringTokenizer;
import javax.annotation.security.DenyAll;
import javax.annotation.security.PermitAll;
import javax.inject.Inject;
import javax.json.JsonObjectBuilder;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
import org.apache.log4j.Logger;
import org.jboss.resteasy.annotations.interception.ServerInterceptor;
import org.jboss.resteasy.core.Headers;
import org.jboss.resteasy.core.ResourceMethodInvoker;
import org.jboss.resteasy.core.ServerResponse;
import com.palo.analytics.GoogleAnalyticsEvent;
import com.palo.logic.UserLogic;
import com.palo.web.utils.HttpUtils;
@Provider
@ServerInterceptor
public class SecurityInterceptor implements ContainerRequestFilter {
private static Logger logger = Logger.getLogger(SecurityInterceptor.class);
private static final String AUTHORIZATION_PROPERTY = "Authorization";
private static final ServerResponse ACCESS_DENIED = new ServerResponse(
"Access denied for this resource", 401, new Headers<Object>());
private static final ServerResponse ACCESS_DENIED_FOR_USER = new ServerResponse(
"User not authorized", 401, new Headers<Object>());
private static final ServerResponse ACCESS_FORBIDDEN = new ServerResponse(
"Nobody can access this resource", 403, new Headers<Object>());
@Inject
private UserLogic ul;
@Override
/**
* The request filter is called automatically called for each incoming request. It checks which method is being called by the client and, based on that method's annotations, restricts access, verifies the identity of the caller, checks the validity of the session token, etc.
*/
public void filter(ContainerRequestContext requestContext)
throws IOException {
logger.debug("------------- request filter ------------");
ResourceMethodInvoker methodInvoker = (ResourceMethodInvoker) requestContext
.getProperty("org.jboss.resteasy.core.ResourceMethodInvoker");
Method method = methodInvoker.getMethod();
String methodName = method.getName();
String uri = requestContext.getUriInfo().getPath();
logger.debug("Accessing method " + methodName + " via URI " + uri);
for (String str : requestContext.getPropertyNames()) {
logger.debug(str);
}
//Get request headers
final MultivaluedMap<String, String> headers = requestContext
.getHeaders();
for (String key : headers.keySet()) {
for (String value : headers.get(key)) {
logger.debug(key + " - " + value);
}
}
//Access allowed for all
if (method.isAnnotationPresent(PermitAll.class)) {
return;
}
//Access denied for all
if (method.isAnnotationPresent(DenyAll.class)) {
requestContext.abortWith(ACCESS_FORBIDDEN);
return;
}
//Fetch authorization header
final List<String> authorization = headers.get(AUTHORIZATION_PROPERTY);
//If no authorization information present; block access
if (null == authorization || authorization.isEmpty()) {
requestContext.abortWith(ACCESS_DENIED);
return;
}
final String username = HttpUtils.getUsernameFromAuthorizationHeader(
authorization, HttpUtils.AUTHENTICATION_SCHEME_BASIC);
final String password = HttpUtils.getPasswordFromAuthenticationHeader(
authorization, HttpUtils.AUTHENTICATION_SCHEME_BASIC);
if (null == username || null == password || username.isEmpty()
|| password.isEmpty()) {
requestContext.abortWith(ACCESS_DENIED_FOR_USER);
return;
}
boolean authenticated = ul.authenticate(username, password);
if (false == authenticated) {
requestContext.abortWith(ACCESS_DENIED);
return;
}
return;
}
}
Ich bin mit RESTClient für Firefox senden Sie den REST Anforderungen an die JAX-RS-Methoden. Da bin ich Protokollierung der Header, ich kann deutlich sehen, was kommt in den Filter und der Wert ändert sich nicht zwischen den anrufen, selbst wenn ich es ändern in RESTClient. Was ist mehr, der Wert ist immer noch da, auch wenn ich nicht verwenden, der Authorization-header in RESTClient.
Meine Frage ist, warum ist der Authorization-header blockiert und es nicht an meinen Filter? Wenn ich entfernen Sie die web.xml Datei, bekomme ich die richtige Authorization-header in der ContainerRequestFilter. Gibt es eine Möglichkeit, den /rest-Teil der Anwendung in eine zone, die nicht betroffen ist der login-config in web.xml?
Jede Hilfe wird sehr geschätzt!
Du musst angemeldet sein, um einen Kommentar abzugeben.
Aus, was ich verstehe, wenn Sie geben Sie den login-config, es ist dann für alle Ressourcen, angegeben in web-resource-collection. Beide /rest/und /geschützt/in Ihrem Fall.
Ersten Ansatz
Eine Sache, die Sie tun können, ist zu ändern Sie Ihre login-Modul, so dass es ordnet
admin
Rolle diejenigen Benutzer gültige Anmeldeinformationen, und weistanonymous
Rolle für diejenigen, die keine gültigen Anmeldedaten an. Dann könnte man ändern Sie Ihre web.xml wie dieseZweite Ansatz
anstatt ändern Sie Ihre login-Modul, ist das hinzufügen einer weiteren login-Modul zu Ihrer Sicherheit-Domäne, die in würde abtreten
anonymous
Rolle zu jederDritten Ansatz
Verwenden Sie Die Benutzerdefinierte Authentifizierung-Mechanismus http://undertow.io/documentation/core/security.html
Die BASIC-Authentifizierung-Mechanismus erwartet, dass der Benutzer das senden der Anmeldeinformationen im http-header im format
Authorization: Basic: base64encodedCredentials
Beim verwenden der benutzerdefinierten Authentifizierung-Mechanismus, haben Sie Zugriff auf den Anfrage-Pfad, und Sie machen Ihre benutzerdefinierten Authentifizierungsmechanismus überspringen der Aufruf der login-Modulen im Falle der Anforderung an den Pfad, auf dem Sie nicht geschützt werden wollen. Aber ich glaube nicht, dass dies ist ein guter Ansatz, da diese Arten von Entscheidungen getroffen werden sollten, die von login-Module+web.xml.
Vierten Ansatz (nicht sicher, ob es funktioniert, aber hoffentlich tut es
Ressourcen, die nicht angegebene Sicherheit-Einschränkungen, sind nicht geprüft, die von login-Modulen. Also, - /rest - /Ressourcen-ungeschützt, entfernen Sie diese Zeilen aus Ihrem web.xml: