How to configure both BASIC and FORM authentication methods in one Java EE application

I need to configure both BASIC and FORM authentication methods depending on the web resource in a Java EE application. This means, for example, for paths / applications / services that I want to authenticate using BASIC, and for the rest of the application method, it will be FORM.

Is this possible without Spring, but in pure Java EE?

+3


source to share


4 answers


Possibly, but you need to create and install your own authentication module instead of using the two built-in BASIC and FORM.



Java EE has API / SPI for this JASPIC name. In addition, many application servers have an alternative API for this.

+2


source


Yes, there is a workaround (I did this for Tomcat 7.0.68).

1) Configure yours web.xml

to use the auth FORM method:

<login-config>
 <auth-method>FORM</auth-method>
  <form-login-config>
    <form-login-page>/login.jsp</form-login-page>
    <form-error-page>/loginError.jsp</form-error-page>
  </form-login-config>
</login-config>

      

2) Install url-pattern

which one you want to authenticate BASIC without auth-constraint

:

<security-constraint>
  <web-resource-collection>
    <web-resource-name>BASIC auth path</web-resource-name>
      <url-pattern>/app/services/*</url-pattern>
    </web-resource-collection>
  </security-constraint>

      

3) Set up a filter for this template:



<filter>
  <filter-name>BasicLoginFilter</filter-name>
  <filter-class>pa.cka.ge.BasicLoginFilter</filter-class>
  <init-param>
    <param-name>role-names-comma-sep</param-name>
    <param-value>role1,andRole2,andRole3</param-value>
  </init-param>
</filter>
<filter-mapping>
  <filter-name>BasicLoginFilter</filter-name>
  <url-pattern>/app/services/*</url-pattern>
</filter-mapping>

enter code here

      

where role-names-comma-sep

is your custom parameter that defines the roles to access /app/services

. This is useful because the path /app/services

should not contain auth restrictions (see above) and you generally cannot define roles as usual. In my example, the implementation checks these roles using AND (you can change it).

4) Process the login manually in the filter:

package pa.cka.ge;

import java.io.IOException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
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.catalina.Role;
import org.apache.catalina.users.MemoryUser;
import org.apache.tomcat.util.buf.B2CConverter;
import org.apache.tomcat.util.codec.binary.Base64;

public class BasicLoginFilter implements Filter {

  /**
   * List of roles the user must have to authenticate
   */
  private final List&lt;String&gt; roleNames = new ArrayList&lt;String&gt;();

  @Override
  public void init(FilterConfig filterConfig) throws ServletException {
    String roleNamesParam = filterConfig.getInitParameter("role-names-comma-sep");
    if (roleNamesParam != null) {
      for (String roleName: roleNamesParam.split(",")) {
        roleNames.add(roleName);
      }
    }
  }

  private static final String AUTHORIZATION_HEADER = "Authorization";
  private static final String BASIC_PREFIX = "Basic ";

  @Override
  public void doFilter(ServletRequest req, ServletResponse resp,
      FilterChain chain) throws IOException, ServletException {
    HttpServletRequest request = (HttpServletRequest)req;
    HttpServletResponse response = (HttpServletResponse)resp;

    // get username and password from the Authorization header
    String authHeader = request.getHeader(AUTHORIZATION_HEADER);
    if (authHeader == null || !authHeader.startsWith(BASIC_PREFIX)) {
      throwBasicAuthRequired();
    }

    String userPassBase64 = authHeader.substring(BASIC_PREFIX.length());
    String userPassDecoded = new String(Base64.decodeBase64(userPassBase64), B2CConverter.ISO_8859_1);// decode from base64 any other way, if this won't work for you. Finally userPassDecoded must contain readable "username:password"
    if (!userPassDecoded.contains(":")) {
      throwBasicAuthRequired();
    }

    String authUser = userPassDecoded.substring(0, userPassDecoded.indexOf(':'));
    String authPass = userPassDecoded.substring(userPassDecoded.indexOf(':') + 1);

    // do login manually
    request.login(authUser, authPass);

    // check roles for the user
    final Principal userPrincipal = request.getUserPrincipal();

    // Your Principal will be another class, not MemoryUser. Run in debug mode to see what class you actually have. The role checking will depend on that class.
    MemoryUser user = (MemoryUser)userPrincipal; 

    boolean hasRoles = true;
    for (String role: roleNames) {
      if (role == null) {
        continue;
      }
      boolean hasRole = false;
      Iterator&lt;Role&gt; roles = user.getRoles();
      while (roles.hasNext()) {
        if (role.equals(roles.next().getName())) {
          hasRole = true;
          break;
        }
      }
      if (!hasRole) {
        hasRoles = false;
        break;
      }
    }

    if (hasRoles) {
      // login successful
      chain.doFilter(request, response);
      request.logout();// optional
    } else {
      // login failed
      throwLoginFailed();
    }
  }

  @Override
  public void destroy() {
  }

  public static void throwBasicAuthRequired() throws ServletException {
    throw new ServletException("The /app/services resources require BASIC authentication");
  }

  public static void throwLoginFailed() throws ServletException {
    throw new ServletException("Login failed");
  }
}

      

Done! Now /app/services

supports BASIC auth, but the rest of the application supports FORM.

+1


source


Since Servlet 3.0 (Tomcat 7+) you can programmatically do HttpServletRequest.login()

Get user: go through as in romanov to answer and login.

0


source


Spring protection installs its own filter. You can write your own filter / filters and install them instead. If you can easily tell which url patterns are using one auth and the other is simple enough with two filters

-1


source







All Articles