java - Wildfly web.xml security constraint blocking basic auth header for JAX-RS methods using ContainerRequestFilter -
the web application i'm developing consists of servlets , jax-rs webservices. until now, using containerrequestfilter authenticate rest method calls need secure servlets decided use web.xml define security constraints. web.xml looks 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 http basic --> <login-config> <auth-method>basic</auth-method> <realm-name>restricted zone</realm-name> </login-config> </web-app>
if understand syntax of web.xml correctly, defined means access /rest/* (where jax-rs methods are) unrestricted far loginmodules concerned, , access /protected/* path (where keep secure servlets) requires basic authorization.
when try open 1 of secure servlets, e.g. /protected/test, basic auth login dialog in browser , behavior correct - if enter credentials 'admin' user, i'm allowed access. otherwise, 'forbidden' message.
also, when try access on /rest/ path no basic auth dialog, expect. however, authorization header in containerrequestfilter not 1 i'm sending in rest request it's 1 used /protected/ servlet.
below other parts of puzzle:
standalone.xml (security-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 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 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") }; (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 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 resource", 403, new headers<object>()); @inject private userlogic ul; @override /** * request filter called automatically called each incoming request. checks method being called client and, based on method's annotations, restricts access, verifies identity of caller, checks validity of 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); (string str : requestcontext.getpropertynames()) { logger.debug(str); } // request headers final multivaluedmap<string, string> headers = requestcontext .getheaders(); (string key : headers.keyset()) { (string value : headers.get(key)) { logger.debug(key + " - " + value); } } // access allowed if (method.isannotationpresent(permitall.class)) { return; } // access denied 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'm using restclient firefox send rest requests jax-rs methods. since i'm logging headers, can plainly see comes filter , value doesn't change between calls, if change in restclient. more, value still there if don't use authorization header in restclient.
my question why authorization header blocked , it's not forwarded filter? if remove web.xml file, correct authorization header in containerrequestfilter. there way move /rest part of application zone not affected login-config in web.xml?
any appreciated!
from understand, if specify login-config, it's used resources, specified in web-resource-collection. both /rest/ , /protected/ in case.
first approach 1 thing do, modify login module, assigns admin
role users have provided valid credentials, , assigns anonymous
role those, has not provided valid credentials. modify web.xml this
<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>
second approach instead of modifying login module, adding 1 more login module security domain, assign anonymous
role
third approach use custom authentication mechanism http://undertow.io/documentation/core/security.html basic authentication mechanism expects user send credentials in http header in format authorization: basic: base64encodedcredentials
when using custom authentication mechanism, have access request path, , make custom authentication mechanism skip call login modules in case request made path don't want protected. don't think approach, these kinds of decisions should made login modules+web.xml.
fourth approach (not sure if works, does resources, not specified in security-constraints, aren't checked login modules. so, make /rest/ resource unprotected, remove these lines web.xml:
<security-constraint> <web-resource-collection> <web-resource-name>rest</web-resource-name> <url-pattern>/rest/*</url-pattern> </web-resource-collection> </security-constraint>
Comments
Post a Comment