Wildfly web.xml security restriction blocking the base auth header for JAX-RS methods using ContainerRequestFilter

The web application I'm developing consists of some servlets as well as JAX-RS web services. So far I have used the ContainerRequestFilter to authenticate calls to the REST method, but now I also need to protect the servlets, so I decided to use web.xml to determine the security restrictions. My web.xml looks like this:

<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> 

If I understand the syntax of web.xml correctly, then what I defined means that access to / rest / * (where all my JAX-RS methods are) is unlimited with LoginModules, and all access to the path / protected / * (where I keep my protected servlets) requires basic authorization.

When I try to open one of the safe servlets, for example. / protected / test, I get the basic login dialog in the browser, and the behavior is correct - if I enter the credentials for the user โ€œadminโ€, Iโ€™m allowed access. Otherwise, I get a Forbidden message.

Also, when I try to access something on the / rest / path, I don't get any basic auth dialog, which I expect. However, the authorization header that I get in the ContainerRequestFilter is not the one I send in the REST request, but the one I used earlier to get into / protected / servlet.

The following are other parts of the puzzle:

standalone.xml (secure domains section)

 <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 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; } } 

I use RESTClient for Firefox to send REST requests to JAX-RS methods. Since I am registering all the headers, I can clearly see what is happening with the filter and the value does not change between calls, even if I change it in RESTClient. Moreover, the value still exists, even if I do not use the authorization header in RESTClient.

My question is: why is the authorization header blocked and it is not redirected to my filter? If I delete the web.xml file, I get the correct authorization header in the ContainerRequestFilter. Is there a way to move / rest part of the application to a zone that is not affected by login-config in web.xml?

Any help is much appreciated!

+5
source share
1 answer

From what I understand, if you specify login-config, it will be used for all resources specified in the web resource collection. And / rest / and / protected / in your case.

First approach One thing you can do is change your login module to assign the admin role to those users who provided valid credentials, and assign the anonymous role to those who did not provide valid credentials. Then you can change your web.xml as follows

  <security-constraint> <web-resource-collection> <web-resource-name>rest</web-resource-name> <url-pattern>/rest/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>anonymous</role-name> <role-name>admin</role-name> </auth-constraint> </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> 


The second approach, instead of modifying your login module, adds another login module to your security domain, which assigns the anonymous role to everyone

Third approach Use the user authentication mechanism http://undertow.io/documentation/core/security.html The BASIC authentication mechanism expects the user to send credentials in the HTTP header in the format Authorization: Basic: base64encodedCredentials

When using a custom authentication mechanism, you have access to the request path, and you can force your own authentication mechanism to skip the login module if the request is made in a way that you do not want to protect, But I do not think this is a good approach , since such decisions must be made by the login + web.xml modules.


The fourth approach (not sure if it works, but I hope it does. Resources that are not specified in the security restrictions are not checked by the input modules. Thus, to make / rest / resource unprotected, remove these lines from your web.xml:

 <security-constraint> <web-resource-collection> <web-resource-name>rest</web-resource-name> <url-pattern>/rest/*</url-pattern> </web-resource-collection> </security-constraint> 
+3
source

Source: https://habr.com/ru/post/1200551/


All Articles