BasicLoginFilter.java
/*
* Copyright (C) 2018 B3Partners B.V.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package nl.b3p.brmo.verschil.util;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
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.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Een servlet filter om BASIC athentication te kunnen gebruiken in een FORM
* beveiligde webapp, bijvoorbeeld voor losses REST services.
*
* Voeg het filter toe in de {@code web.xml}:
* <pre>
* {@code
* <filter>
* <display-name>BasicLogin Filter</display-name>
* <filter-name>BasicLoginFilter</filter-name>
* <filter-class>nl.b3p.brmo.verschil.util.BasicLoginFilter</filter-class>
* <init-param>
* <description>1 of meer rollen, komma gescheiden</description>
* <param-name>auth-role-names</param-name>
* <param-value>BRKMutaties</param-value>
* </init-param>
* </filter>
* <filter-mapping>
* <filter-name>BasicLoginFilter</filter-name>
* <url-pattern>/rest/*</url-pattern>
* </filter-mapping>
* }
* </pre> Voeg een constraint toe voor de specifieke resource:
* <pre>
* {@code
* <security-constraint>
* <display-name>rest endpoints</display-name>
* <web-resource-collection>
* <web-resource-name>service endpoint</web-resource-name>
* <url-pattern>/rest/*</url-pattern>
* </web-resource-collection>
* </security-constraint>
* }
* </pre>
*
* @author mprins
*/
public class BasicLoginFilter implements Filter {
private static final Log LOG = LogFactory.getLog(BasicLoginFilter.class);
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BASIC_PREFIX = "Basic ";
/**
* List of roles the user must have to authenticate
*/
private final List<String> roleNames = new ArrayList<>();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
String roleNamesParam = filterConfig.getInitParameter("auth-role-names");
if (roleNamesParam != null) {
for (String roleName : roleNamesParam.split(",")) {
roleNames.add(roleName.trim());
}
}
LOG.debug("beschikbare rollen voor basic auth: " + String.join(", ", roleNames));
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// haal username and password uit de Authorization header
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
if (authHeader == null || !authHeader.startsWith(BASIC_PREFIX)) {
LOG.debug("BASIC Authorization header ontbreekt (401)");
this.handle401(response);
return;
}
String userPassBase64 = authHeader.substring(BASIC_PREFIX.length());
// decode credentials base64
String credentials = new String(Base64.getDecoder().decode(userPassBase64), StandardCharsets.UTF_8);
if (!credentials.contains(":")) {
LOG.debug("geen `:` in user:password combo (401)");
this.handle401(response);
return;
}
final String[] values = credentials.split(":", 2);
String authUser = values[0];
String authPass = values[1];
try {
// do login, nodig voor request.isUserInRole(...)
request.login(authUser, authPass);
} catch (ServletException ex) {
this.handle403(response);
return;
}
// check user rollen
boolean hasRoles = false;
for (String role : roleNames) {
if (role == null) {
continue;
}
if (request.isUserInRole(role)) {
// rol gevonden
hasRoles = true;
break;
}
}
if (hasRoles) {
// login successful en user in rol
chain.doFilter(request, response);
// logout when done
request.logout();
} else {
request.logout();
handle403(response);
return;
}
}
@Override
public void destroy() {
// void
}
private void handle401(HttpServletResponse response) throws IOException {
response.addHeader("WWW-Authenticate", "Basic realm=\"REST service Realm\", charset=\"UTF-8\"");
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "BASIC authentication credentials invalid.");
}
private void handle403(HttpServletResponse response) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Basic login failed.");
}
}