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?
source to share
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<String> roleNames = new ArrayList<String>();
@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<Role> 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.
source to share