Bug 46902 - LoginValve to bypass restrictions of j_security_check
Summary: LoginValve to bypass restrictions of j_security_check
Status: RESOLVED WONTFIX
Alias: None
Product: Tomcat 6
Classification: Unclassified
Component: Catalina (show other bugs)
Version: unspecified
Hardware: All All
: P2 enhancement with 1 vote (vote)
Target Milestone: default
Assignee: Tomcat Developers Mailing List
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2009-03-24 07:10 UTC by Gregor Schneider
Modified: 2015-02-05 19:43 UTC (History)
1 user (show)



Attachments
Patch-file for Tomcat's latest trunk (9.42 KB, patch)
2009-03-24 07:10 UTC, Gregor Schneider
Details | Diff
Patch-file for Tomcat 6.0.x-trunk (9.42 KB, patch)
2009-03-24 08:15 UTC, Gregor Schneider
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Gregor Schneider 2009-03-24 07:10:05 UTC
Created attachment 23408 [details]
Patch-file for Tomcat's latest trunk

Implementation of the <b>Valve</b> interface forcing any request to protected
content to a pre-configured login-page if no valid session exists

Since j_security_check always forwards to the last request after presenting
the credentials, it's sometimes annoying that after a session-timeout and re-entering the credentials, the last url requested often points to some location which might be included in a website but should not be served on their own (i.e. a graphics-file, some JavaScript, a css-file or similar.

This problem especially exists for website using frames / iframes.
Within LoginValve, protected paths and their default redirect-paths after
successful authentication can be specified.

Example:
 <Context>
       <Valve  className="org.apache.catalina.valves.LoginValve"
                       protectedPath="/foo/baar"
                       redirectAfterAuth="/foo/index.html"/>
 </Context>

In the example above there are two paths specified:
   protectedPath 	- protected Path (recursive) having a default redirect
   redirectAfterAuth	- url to be displayed after successful authentication
Comment 1 Gregor Schneider 2009-03-24 08:15:03 UTC
Created attachment 23410 [details]
Patch-file for Tomcat 6.0.x-trunk

Added patch for Tomcat 6.0.x-trunk
Comment 2 Christopher Schultz 2009-05-06 19:08:09 UTC
A few questions:
1. Why can't the "redirectAfterAuth" path be within the protected space?
2. Why do you check to see if the request URI /startsWith/ the
   redirectAfterAuth instead of being equal to it?
3. Why are you checking to see if characters 10 - 16 of the request URI
   are "y_check". Why not check for the whole "j_security_check" string?
   Why not check the /end/ of the request URI for j_security_check,
   since the URI for j_security_check is not required to be
   /j_security_check but pretty much */j_security_check?
4. Why are killing the session if the authtype is null?
5. Why does your valve pass-through any requests before the component
   has "started"? Is there a valid use case where NOT performing these
   checks and redirects is appropriate?

It appears that your valve does nothing but murder the session and redirect the user if authtype=null and you are requesting a resource from a particular URI space. This does not seem particularly useful.

Maybe I'm missing something subtle.
Comment 3 Gregor Schneider 2009-05-07 04:16:18 UTC
Chris,

On Thu, May 7, 2009 at 4:07 AM, Christopher Schultz <chris@christopherschultz.net> wrote:
>
> A few questions:
>
Chris, maybe you'll get the hang of this Valve if I explain the business-requirement I had:

My primary target was to cirumvent the problem having a framed web-app, where some content is requested after the session has timed out.

let's say we have the following website-structure:

+----------------------------------------+
| menue1 |                               |    
| menue2 |     some_content              |    
| menue3 |                               |    
| menue4 |                               |    
| menue5 |                               |    
+----------------------------------------+

(hope the formatting is ok )

"some_content" is an iframe, and the content of this iframe is changed by selecting one of the left menue-items.
The iframe is specified in "index.html such as:

<html>
    <body>
           <iframe name="some_content" src="/protected/somepage.html">
                    Some iframe-error-message
            </iframe>
    </body>
</html>

Now let's assume, session is timing out, and after that timeout the user selects one of the menue-entries on the left side.
What's happening?

The url requested will look like "http://mysite/protected/some_stuff"

The HTML in that case looks like

<a href="http://mysite/protected/some_stuff.html" target="some_content">menue4</a>

Now this triggers j_security_check, but unfortunately j_security_check just stores the last request, and after passing the credentials,
you'll won't see your "index.html" but "/protected/some_stuff.html" - without the iframe and aboviously without the menue.

So the purpose of this Valve is to provide a mechanism which makes sure, that if a non-authorized request comes in requesting anything else but your "/protected/index.html", that the original request (i.e. "/protected/some_stuff") is replaced by
"/protected/index.html" (or any other url being specified in the Valve-descriptor).

Now take a look at some example-Valve-descriptor:

<Context>
    <Valve  className="org.apache.catalina.valves.LoginValve"
            protectedPath="/protected"
            redirectAfterAuth="/protected/index.html"/>
</Context>

This basically says, that all /non-authorized/ requests to the protected content will be re-routed to "/protected/index.html" (redirectAfterAuth).

> 1. Why can't the "redirectAfterAuth" path be within the protected space?
>

Actually I do not see why this shouldn't be possible: Actually the idea is, that redirectAfterAuth /must/ be in the protected area

If you take a look at the first condition:

if (aRequest.getRequestURI().startsWith(protectedPath)
    && 
    !aRequest.getRequestURI().startsWith(redirectAfterAuth)
    && 
    !aRequest.getRequestURI().startsWith("/j_security_check", 10)) {

Basically it says:

- Only URLs are handled being in my protected area
- the URL must /not/ be equal my default protected starting-URL
- the URL requested must /not/ be j_security_check

The two latter conditions are necessary to avoid an infinite loop when accessing protected content

> 2. Why do you check to see if the request URI /startsWith/ the
>   redirectAfterAuth instead of being equal to it?

Because there might be some parameters after the adress in the URL - i.e., if Cookies are not possible so that the session-information is stored within the URL

> 3. Why are you checking to see if characters 10 - 16 of the request URI
>   are "y_check". Why not check for the whole "j_security_check" string?
>   Why not check the /end/ of the request URI for j_security_check,
>   since the URI for j_security_check is not required to be
>   /j_security_check but pretty much */j_security_check?

You are right with this:

Actually I made a mistake here:

When "j_security_check" is triggered, the URL will look like

/protected/j_security_check

As you can see, in this example it works since "/protected" is exactly 10 characters long.

Therefore, the correct code would be 

	&& !aRequest.getRequestURI().startsWith("/j_security_check"
		, protectedPath.length())) {

I'll correct that with a new patch during the weekend.

Why do I not ask for the String ending with "j_security_check"? 
I was not sure how that URL looks like if session-info is encoded within the URL - therefore I'm using startsWith()

> 4. Why are killing the session if the authtype is null?

Because we experienced with some users, esp. behind company-proxies, that situations may occur where a session still exists, but the Principal was null.
Therefore, if Principal is null, better be safe than sorry and make sure you definately have a new session

> 5. Why does your valve pass-through any requests before the component
>   has "started"? Is there a valid use case where NOT performing these
>   checks and redirects is appropriate?

Nope. I took this code from AccessLogValve (I believe it was that one), and my assumption was those checks don't make sense /before/ the Valve is completely set (started).
If you feel that a different approach does make more sense here, I'm happy for your suggestions

>
> It appears that your valve does nothing but murder the session and
> redirect the user if authtype=null and you are requesting a resource
> from a particular URI space. This does not seem particularly useful.
>
> Maybe I'm missing something subtle.
>

Seems to be - see my explanations on top.

Cheers
Comment 4 Mark Thomas 2015-02-05 10:41:43 UTC
Getting back to this after far too long.

I understand the problem you are trying to solve but I don't think that this Valveis generic enough to include in Tomcat. My largest concern is that the requirement that all the protected pages are under a single URL space.

Since this enhancement request was made, the landingPage option has been added to the FORM authenticator. I believe an additional option of "forceLandingPageAfterAuth" that always redirected the user to the landing page rather than their requested page would meet this requirement. This should be fairly easy to implement.
Comment 5 Konstantin Kolinko 2015-02-05 13:36:24 UTC
(In reply to Mark Thomas from comment #4)
> I don't think that this Valve is generic enough to include in Tomcat.

Agreed.

> 
> I believe an additional option of
> "forceLandingPageAfterAuth" that always redirected the user to the landing
> page rather than their requested page would meet this requirement. This
> should be fairly easy to implement.

I think it does not match the OP's use case of iframes.


In OP's use case #1 (Comment 3) request for protected resource http://mysite/protected/some_stuff.html  goes to iframe.  So the login form will be displayed in the iframe.  Generally you wouldn't want login form in a iframe, so I think that the login page has to perform some logic to detect that it is an iframe and to change the URL of the containing page.

In this case the form page shall adapt its behaviour depending on requested URI. Instead of displaying a form it shall redirect the top frame to /protected/index.html. (E.g. return a small placeholder page that changes top frame URL via javascript).

If the top frame URL is /protected/index.html then after authentication the User will be directed to /protected/index.html, as expected.


In OP's use case #2 (Comment 1) - request to a graphics-file, some JavaScript, a css-file.

Again, the form page shall adapt its behaviour dependent on requested URI. It shall not return a HTML form instead of an image. A browser won't be happy to receive one.


I think that instead of using "forceLandingPageAfterAuth" one shall implement a dynamic login form page that does check request URI, and when (requestURI != landingPageURI) it responds with 302 redirect to the landing page instead of displaying a form.

Note "forceLandingPageAfterAuth" feature and using 302 redirect in login form differ in the URL that is displayed in location bar of a web browser during authentication.

In case of "forceLandingPageAfterAuth":
The URL displayed in location bar of a browser will be the one of the protected resource.

It is confusing. After authentication you are not directed to that URL but 302-redirected to the landing page. What have you been authenticating for?

In case of login form page sending a 302-redirect to the landing page:
The displayed URL during authentication will be the same as the one after authentication - the landing page.  I think that this behaviour is better.
Comment 6 Mark Thomas 2015-02-05 13:44:33 UTC
In which case the logic has to exist in the login page and there is nothing for us to do here.
Comment 7 Christopher Schultz 2015-02-05 19:43:09 UTC
As much as I like this kind of capability (particularly the addition of "landingPage" and something like the proposal to add "forceLandingPageAfterAuth"), I don't think is belongs in Tomcat because it's out-of-spec.

I implemented a bunch of things in securityfilter to get around holes I saw in the spec and while tempted to bring them into Tomcat, discarded the notion because of that very fact (out-of-spec). I really hate product lock-in and it would be a shame for Tomcat to go down that path.

Specifically, I implemented the following features in this area:

1. A landing page if there was no protected-page-access that caused the login form to be displayed (same as the "langingPage" feature)
2. The ability for the login page to accept a "next" page that would basically take the place of a "landing page", or override it
3. The ability for the client to specify whether or not the post-authentication action would be to REDIRECT or FORWARD to the landing page
4. The ability to pass request parameters to the login page which would then be forwarded to the landing page

Some of this doesn't make sense unless you have very specific requirements (which we did). In our use-case, we had two web applications that share a URL space and one of them does not use sessions at all... it just passes-through the session-id of the other application when we make loopback-requests to the "other" application. If there's no session in the authenticated-application, we have to sent the user to a login page, but then have them sent back to the resource they were trying to access (which is in the non-authenticated application). So, we can't just send the user to the "protected resource" because that resource isn't directly-protected... instead, it's protected-by-proxy.

It sounds outrageously stupid, but I promise it makes sense, and these features helped us pull it off. :)

These days, with HttpServletRequest.authenticate() and HttpServletRequest.login() being available, it seems like all of the capability could be provided by a Filter on an unprotected resource rather than trying to shoehorn it into the existing authentication valve.