簡體   English   中英

Wildfly web.xml安全性約束使用ContainerRequestFilter阻止JAX-RS方法的基本auth頭

[英]Wildfly web.xml security constraint blocking basic auth header for JAX-RS methods using ContainerRequestFilter

我正在開發的Web應用程序包含一些servlet以及JAX-RS Web服務。 到目前為止,我使用ContainerRequestFilter來驗證REST方法調用,但現在我還需要保護servlet,所以我決定使用web.xml來定義安全性約束。 我的web.xml看起來像這樣:

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

如果我正確理解了web.xml的語法,我定義的意味着對於/ rest / *(我所有的JAX-RS方法都是)的訪問權限就LoginModule而言是無限制的,並且對/ protected的所有訪問權限都是如此/ * path(我保留我的安全servlet)需要基本授權。

當我嘗試打開其中一個安全servlet時,例如/ protected / test,我在瀏覽器中獲得了基本的auth登錄對話框,行為是正確的 - 如果我輸入'admin'用戶的憑據,我就可以訪問。 否則,我會收到“禁止”消息。

此外,當我嘗試訪問/ rest / path上的任何內容時,我沒有得到任何基本的auth對話框,這就是我所期望的。 但是,我在ContainerRequestFilter中獲取的Authorization標頭不是我在REST請求中發送的那個,而是我之前用於進入/ protected / servlet的那個。

以下是這個難題的其他部分:

standalone.xml(security-domains部分)

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

我正在使用RESTClient for Firefox將REST請求發送到JAX-RS方法。 由於我正在記錄所有標題,我可以清楚地看到過濾器的內容,並且調用之間的值不會改變,即使我在RESTClient中更改它也是如此。 更重要的是,即使我不在RESTClient中使用Authorization標頭,該值仍然存在。

我的問題是為什么授權標題被阻止而且它沒有被轉發到我的過濾器? 如果我刪除web.xml文件,我會在ContainerRequestFilter中獲得正確的Authorization標頭。 有沒有辦法將應用程序的/ rest部分移動到不受web.xml中login-config影響的區域?

任何幫助是極大的贊賞!

根據我的理解,如果您指定login-config,它將用於web-resource-collection中指定的所有資源。 在你的情況下/ rest /和/ protected /。

第一種方法您可以做的一件事是修改您的登錄模塊,以便為已提供有效憑據的用戶分配admin角色,並為尚未提供有效憑據的用戶分配anonymous角色。 然后你可以像這樣修改你的web.xml

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


第二種方法是向安全域添加一個登錄模塊,而不是修改登錄模塊,這將為每個人分配anonymous角色

第三種方法使用自定義身份驗證機制http://undertow.io/documentation/core/security.html BASIC身份驗證機制要求用戶以http格式的頭中發送憑據,格式為Authorization:Basic:base64encodedCredentials

使用自定義身份驗證機制時,您可以訪問請求路徑,並且可以使自定義身份驗證機制跳過對登錄模塊的調用,以防請求發送到您不希望受到保護的路徑。 但我認為這不是一個好方法,因為這些決策應該由登錄模塊+ web.xml做出。


第四種方法(不確定它是否有效,但希望它確實在安全性約束中未指定的資源不會被登錄模塊檢查。因此,要使/ rest / resource不受保護,請從web.xml中刪除這些行:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM