Discussion:
combination of RemoteAddrValve und basic authentication
Remon Sadikni
2011-09-26 10:40:26 UTC
Permalink
Dear Tomcat developers and users,

I managed to restrict a web application by IP-adress with
RemoteAddrValve and to restrict another one by basic authentication. Now
I would like to restrict the same web application by both methods:
- If the user is inside a specific network (e.g. 134.134.*.*), then he
should get direct access to the web application (without login window).
- If he is outside this network he has to authenticate via username /
password.
I tried to combine RemoteAddrValve und basic authentication, but I only
managed an "AND" conjunction. What I want is a disjunctive combination
("OR") of these 2 methods . How can I do that?

Thank you very much,
Remon
André Warnier
2011-09-26 13:29:25 UTC
Permalink
Post by Remon Sadikni
Dear Tomcat developers and users,
I managed to restrict a web application by IP-adress with
RemoteAddrValve and to restrict another one by basic authentication. Now
- If the user is inside a specific network (e.g. 134.134.*.*), then he
should get direct access to the web application (without login window).
- If he is outside this network he has to authenticate via username /
password.
I tried to combine RemoteAddrValve und basic authentication, but I only
managed an "AND" conjunction. What I want is a disjunctive combination
("OR") of these 2 methods . How can I do that?
Hi.

Those 2 things do not happen at the same time.
The Valve executes first, and it either blocks the request totally, or lets it through
unchanged.
Then, if the Valve let the request pass through, comes the authentication/authorization logic.
But by then of course it is too late, because if the request was blocked by the Valve, it
will not even make it to the AA stage.

What you could do, is write a custom Valve. This Valve would have to check the client IP
address, and if it matches the "specific network", it should "pre-authenticate" the
request, using some dummy user-id (like "internal_user") before it forwards the request.

I am not sure how easy that is however, since I do not know if Tomcat's Basic
Authentication mechanism /always/ checks the "Authorization:" header first, or if it first
checks if the request has already a UserPrincipal assigned to it.

(Gurus, please fill in here : ........ )

If the Valve needs to add an "Authorization:" header to the request, then you are in for a
bit more complexity.

This would be a case where I would handle the problem at the level of a front-end Apache
httpd, where such things are easier (for me) by an order of magnitude.
I even happen to have written an Apache/mod_perl module which does exactly what you want,
and more.

You may also want to have a look at SecurityFilter, which could well be an easier way for
you (http://securityfilter.sourceforge.net/)
I do not think that it has provisions for "automatically" authenticating a user based on
his client IP address, but it may be easier to just add the required code there.
Christopher Schultz
2011-09-26 19:34:42 UTC
Permalink
André,
Post by André Warnier
You may also want to have a look at SecurityFilter, which could
well be an easier way for you
(http://securityfilter.sourceforge.net/) I do not think that it has
provisions for "automatically" authenticating a user based on his
client IP address, but it may be easier to just add the required
code there.
Securityfilter is a bit simpler than Tomcat's authentication system,
and so be a bit more hackable. But sf itself does not have any
CombinedRealm (like Tomcat, which checks attempts several realms for
authentication until one succeeds) nor can it use IP address for
credentials (what is the user's username/id when the IP address is
sufficient for authentication?).

I would have suggested a custom Realm in Tomcat but Tomcat doesn't
give you access to the HttpServletRequest and therefore you can't
sniff the IP address. :(

The use of HTTP BASIC authentication confuses things here because of
the credential transfer mechanism (HTTP headers). I suppose you could
write a Valve that sniffs the user's IP address and then adds HTTP
headers to the request for the "Authentication" header to essentially
force a login. You'll have to decide what the user's Principal will
need to look like (because Tomcat will actually try to /verify/ the
fake-user's credentials and maintain a "login" for them, running
proper authorization checks, etc.) in order to actually work.

- -chris
Remon Sadikni
2011-09-27 09:14:22 UTC
Permalink
Hi André, hi Christopher,

thanks for your answers.
Post by Christopher Schultz
The use of HTTP BASIC authentication confuses things here because of
the credential transfer mechanism (HTTP headers). I suppose you could
write a Valve that sniffs the user's IP address and then adds HTTP
headers to the request for the "Authentication" header to essentially
force a login. You'll have to decide what the user's Principal will
need to look like (because Tomcat will actually try to /verify/ the
fake-user's credentials and maintain a "login" for them, running
proper authorization checks, etc.) in order to actually work.
I think I will try this. Are there any tutorials for writing a Valve? I
am a Java programmer but new to Valves.

Thank you very much,
Remon
André Warnier
2011-09-27 11:40:55 UTC
Permalink
Post by Remon Sadikni
Hi André, hi Christopher,
thanks for your answers.
Post by Christopher Schultz
The use of HTTP BASIC authentication confuses things here because of
the credential transfer mechanism (HTTP headers). I suppose you could
write a Valve that sniffs the user's IP address and then adds HTTP
headers to the request for the "Authentication" header to essentially
force a login. You'll have to decide what the user's Principal will
need to look like (because Tomcat will actually try to /verify/ the
fake-user's credentials and maintain a "login" for them, running
proper authorization checks, etc.) in order to actually work.
I think I will try this. Are there any tutorials for writing a Valve? I
am a Java programmer but new to Valves.
I am not really a Java programmer, so what I say below may be wrong, and should be
confirmed by a better guru.

The reason why I was mentioning further complexity for the Valve solution, is that as far
as I know, the HttpServletRequest object is "immutable" (iow read-only), as it is
received. So you cannot just take the incoming HttpServletRequest, and if the IP address
matches, add a "fake" "Authorization:" header to it with some generic user-id/password.
You will have to wrap the original HttpServletRequest into a custom HttpServletRequest
wrapper, (a la "class CustomRequest extends HttpServletRequestWrapper"), add the fake
header there, and forward this CustomRequest instead of the original for further
processing. In the wrapper class, you also have to override whichever method the Tomcat
Basic authentication mechanism uses to retrieve the additional "Authorization:" header.
(getHeaderNames, getHeader, getHeaders,..)

At least, that is what I had to do the last time I wrote some Tomcat authentication code
as a Servlet Filter. Maybe for a Valve, the situation is different.
Maybe for a seasoned Java programmer this is all a piece of cake; but as for me I had to
find out the above the hard way, and it was all a bit of a challenge.
Christopher Schultz
2011-09-27 18:03:09 UTC
Permalink
André,
Post by André Warnier
The reason why I was mentioning further complexity for the Valve
solution, is that as far as I know, the HttpServletRequest object
is "immutable" (iow read-only), as it is received.
For the most part, this is true.
Post by André Warnier
So you cannot just take the incoming HttpServletRequest, and if the
IP address matches, add a "fake" "Authorization:" header to it with
some generic user-id/password. You will have to wrap the original
HttpServletRequest into a custom HttpServletRequest wrapper, (a la
"class CustomRequest extends HttpServletRequestWrapper"), add the
fake header there, and forward this CustomRequest instead of the
original for further processing.
Correct.
Post by André Warnier
At least, that is what I had to do the last time I wrote some
Tomcat authentication code as a Servlet Filter. Maybe for a Valve,
the situation is different.
Hmm... I was about to say that the Request object is mutable (which it
is), but apparently, headers are something that can't be modified, so
you'll need to wrap the Request in the same way as described above.

Those interested in doing something like this might be interested in
the attachment to this bug:

https://issues.apache.org/bugzilla/show_bug.cgi?id=45014

Hmm... I should go ahead and commit that. :)

- -chris
Christopher Schultz
2011-09-27 17:37:49 UTC
Permalink
Remon,
Post by Remon Sadikni
Hi André, hi Christopher,
thanks for your answers.
Post by Christopher Schultz
The use of HTTP BASIC authentication confuses things here because
of the credential transfer mechanism (HTTP headers). I suppose
you could write a Valve that sniffs the user's IP address and
then adds HTTP headers to the request for the "Authentication"
header to essentially force a login. You'll have to decide what
the user's Principal will need to look like (because Tomcat will
actually try to /verify/ the fake-user's credentials and maintain
a "login" for them, running proper authorization checks, etc.) in
order to actually work.
I think I will try this. Are there any tutorials for writing a
Valve? I am a Java programmer but new to Valves.
http://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/Valve.html
You
should probably extend ValveBase so you don't have to implement
all the silly management methods.
Post by Remon Sadikni
http://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/valves/ValveBase.html
This
will let you implement only the important method: invoke().

Note that the arguments to that method are Request and Response, which
are Tomcat internal classes. They are similar to the ServletRequest
and ServletResponse classes with which you may have some familiarity,
but they are different so you should make sure you have the Tomcat
Javadocs handy when writing your Valve.

See the documentation for the invoke() method for tips on how to
implement it properly. At some point, you have to make up your own
code to accomplish your own requirements, but you need to follow the
rules for making sure that the Valve actually works when you install it.

Install the Valve by adding a <Valve> declaration in your <Context>
element. I'm not entirely sure if your Valve will be invoked before
the Valves that do Tomcat's authentication and authorization, but you
have to make sure that yours run first. If yours appears to be
skipped, post back and we'll figure out how to get it to run before
the auth stuff.

- -chris
Remon Sadikni
2011-09-28 09:01:58 UTC
Permalink
Hi Christopher,
Post by Christopher Schultz
You
should probably extend ValveBase so you don't have to implement
all the silly management methods.
Post by Remon Sadikni
http://tomcat.apache.org/tomcat-7.0-doc/api/org/apache/catalina/valves/ValveBase.html
This
will let you implement only the important method: invoke().
ok, I will try this.

Thank you very much!
Regards,
Remon
Remon Sadikni
2011-10-19 11:57:43 UTC
Permalink
Hi André, hi Christopher,
Post by Christopher Schultz
The use of HTTP BASIC authentication confuses things here because
of the credential transfer mechanism (HTTP headers). I suppose
you could write a Valve that sniffs the user's IP address and
then adds HTTP headers to the request for the "Authentication"
header to essentially force a login. You'll have to decide what
the user's Principal will need to look like (because Tomcat will
actually try to /verify/ the fake-user's credentials and maintain
a "login" for them, running proper authorization checks, etc.) in
order to actually work.
I managed to get it working. If you are interested in my solution for
Tomcat 6: I extended the Valve RequestFilterValve and overwrote the
method process with this content:

// Check the allow patterns
for (int i = 0; i < allows.length; i++) {
if (allows[i].matcher(property).matches()) {
// create a principal for an existing fake user
final List<String> roles = new ArrayList<String>();
roles.add("ROLE");
final Principal principal = new GenericPrincipal(null, "USER",
"PASS", roles);
// set the principal in this request
request.setUserPrincipal(principal);
}
}
// pass this request to the next valve (basic authentication)
getNext().invoke(request, response);
return;

If the User has an allowed IP address, the UserPrincipal will be set in
this request, so that the next valve (the Basic Authentication) will not
show the login window. If the User has another IP address, the request
will be forwarded to the next valve without any changes, so that you
need to log in.

At first I tried solving it with RequestWrappers and changing Headers,
but that failed, because the Basic Authentication Method tests for the
UserPrincipal.

Thanks for your help,
Remon
Christopher Schultz
2011-10-19 14:22:15 UTC
Permalink
Remon,
Post by Remon Sadikni
I managed to get it working. If you are interested in my solution
// Check the allow patterns for (int i = 0; i < allows.length; i++)
{ if (allows[i].matcher(property).matches()) { // create a
principal for an existing fake user final List<String> roles = new
ArrayList<String>(); roles.add("ROLE"); final Principal principal =
new GenericPrincipal(null, "USER", "PASS", roles); // set the
principal in this request request.setUserPrincipal(principal); } }
// pass this request to the next valve (basic authentication)
getNext().invoke(request, response); return;
If you overrode the process() method (and I'm sure you changed other
things, too, since the variable "allows" is not part of
RequestFilterValve), then you really aren't getting anything by
extending RequestFilterValve.

Note that there has been some grumbling on the list about the use of
Matcher.matches() instead of Matcher.lookingAt(): you might want to
consider your requirements before choosing one over the other: most
regular expression folks will expect the behavior of lookingAt and not
matches().
Post by Remon Sadikni
If the User has an allowed IP address, the UserPrincipal will be
set in this request, so that the next valve (the Basic
Authentication) will not show the login window. If the User has
another IP address, the request will be forwarded to the next valve
without any changes, so that you need to log in.
Hey, that's an idea: I didn't think of just shoving the principal into
the request. Just be aware that you will do this on every request,
because Tomcat isn't storing the Principal anywhere to maintain the
"login".
Post by Remon Sadikni
At first I tried solving it with RequestWrappers and changing
Headers, but that failed, because the Basic Authentication Method
tests for the UserPrincipal.
It should be doing both, but the Principal is more efficient because
you don't have to use "real" user that can be authenticated using the
webapp's Realm.

- -chris
Remon Sadikni
2011-10-19 16:23:24 UTC
Permalink
Hi Chris,
Post by Christopher Schultz
If you overrode the process() method (and I'm sure you changed other
things, too, since the variable "allows" is not part of
RequestFilterValve), then you really aren't getting anything by
extending RequestFilterValve.
but "allows" is part of RequestFilterValve. I only extended this class.
I took the same invoke() method as RequestAddrValve, so that I get the
IP-address of the user:

public void invoke(Request request, Response response)
throws IOException, ServletException {
process(request.getRequest().getRemoteAddr(), request, response);
}

and overwrote the process method to react on this IP address.
Post by Christopher Schultz
Note that there has been some grumbling on the list about the use of
Matcher.matches() instead of Matcher.lookingAt(): you might want to
consider your requirements before choosing one over the other: most
regular expression folks will expect the behavior of lookingAt and not
matches().
I will look at it.
Post by Christopher Schultz
Hey, that's an idea: I didn't think of just shoving the principal into
the request. Just be aware that you will do this on every request,
because Tomcat isn't storing the Principal anywhere to maintain the
"login".
That's ok for me.

Regards,
Remon
Christopher Schultz
2011-10-19 18:11:29 UTC
Permalink
Remon,
Post by Remon Sadikni
Hi Chris,
Post by Christopher Schultz
If you overrode the process() method (and I'm sure you changed
other things, too, since the variable "allows" is not part of
RequestFilterValve), then you really aren't getting anything by
extending RequestFilterValve.
but "allows" is part of RequestFilterValve.
Not in the current trunk. Your code expects the "allows" variable to
be of type String[], and no such variable exists in RequestFilterValve.
Post by Remon Sadikni
I only extended this class. I took the same invoke() method as
public void invoke(Request request, Response response) throws
IOException, ServletException {
process(request.getRequest().getRemoteAddr(), request, response);
}
and overwrote the process method to react on this IP address.
Right: the point of the RequestFilterValve is that you don't have to
override the process() method. Overriding it kind of defeats the
purpose of the class, because it really doesn't have any other methods
other than the accessors and mutators for the 'deny' and 'allow'
properties.

- -chris
Remon Sadikni
2011-10-20 09:35:04 UTC
Permalink
Hi Chris,
Post by Christopher Schultz
Post by Remon Sadikni
but "allows" is part of RequestFilterValve.
Not in the current trunk. Your code expects the "allows" variable to
be of type String[], and no such variable exists in RequestFilterValve.
Right: the point of the RequestFilterValve is that you don't have to
override the process() method. Overriding it kind of defeats the
purpose of the class, because it really doesn't have any other methods
other than the accessors and mutators for the 'deny' and 'allow'
properties.
I only mentioned once, that I use Tomcat 6: And there is an "allows"
attribute and an additional method "precalculate".

Regards,
Remon

André Warnier
2011-09-26 17:19:33 UTC
Permalink
Post by Remon Sadikni
Dear Tomcat developers and users,
I managed to restrict a web application by IP-adress with
RemoteAddrValve and to restrict another one by basic authentication. Now
- If the user is inside a specific network (e.g. 134.134.*.*), then he
should get direct access to the web application (without login window).
- If he is outside this network he has to authenticate via username /
password.
I tried to combine RemoteAddrValve und basic authentication, but I only
managed an "AND" conjunction. What I want is a disjunctive combination
("OR") of these 2 methods . How can I do that?
Remon,

I do not know how familiar you are with the "web authentication area", but I am quite
familiar with it, and apart from the purely technical side, let me give you some tips
based on experience :

If you are going to do user authentication :

It is almost always a bad idea to do such "group authentication" (like you say above :
"all users within this network"). Some reasons are :
a) it does not allow you afterward, to know "who did what". This is not only in a
"police" kind of way, but also for support when something goes wrong. You will have for
example a bunch of lines in your server's logfiles, and will not know which ones are
related to the user who just called you for a problem.
b) it is almost guaranteed that as soon as this works, whoever asked you to do this, will
come back to you within the next weeks/months, saying : "Now I would like that the users
of /this/ sub-group (e.g. this sub-series of IP addresses within 134.134.*.*) get
something slightly different". (Or, "Now we would like access statistics by country".)

So my recommendation would be that, right from the start, you design a system that allows
to identify *every* user individually, even if for some of them you do not present a login
dialog and get their user-id from somewhere else, and even if initially the rules for all
of them are the same.
You will probably thank yourself later.

For example, it is possible that your network "134.134.*.*" is some kind of "inside
network", which also is a Windows Domain; and that all users within that domain which
access your server, have first to login to the Windows Domain on their workstation.
In such a case, you could use a module which allows Tomcat to authenticate the user
automatically (without any visible login dialog) via his Domain user-id.
And such a module, if it cannot find a Domain user-id for a user, could have a "fall-back"
feature that is Basic Authentication.

I do not know if the relatively recent Tomcat NTLM Realm has such a fall-back feature; but
one module that has it is Jespa, which you can read about at http://www.ioplex.com.
It is a commercial module, but it is not expensive, and it works.
It also allows you, for a user authenticated automatically through the Windows Domain, to
request some "user attributes" from the AD directory, such as "user groups", which you can
then use much like the "roles" in Tomcat, to allow or not access to some applications.
And it works as a Servlet Filter, which means that you can combine it with other filters
(maybe of your own design), to achieve precisely what you want, on an
application-by-application base.

I am not trying to sell you one particular module or method. Maybe your case is different,
and maybe the above is not applicable. I am just trying to get you to think maybe a bit
ahead of the particular issue you are having now, and a maybe in a more general way.

(And I have no percentage on Jespa sales; I am just a satisfied user of it).
Loading...