4

I've a set of APIs under /api. If my shiro.ini lists this as:

/api/** = authcBasic

Then basic auth is required. If anon is present in place of authcBasic then no auth is required. I'd like to be able to use the APIs with basic auth so I can e.g. programatically check the user is authenticated for POSTs and yet still allow anonymous access to GETs on the same URI. Alternatively to hide restricted data at the same URI for anonymous users and reveal it for auth'd users.

Is this possible?

rich
  • 18,987
  • 11
  • 75
  • 101

3 Answers3

4

You can roll your own custom shiro filter. Extend class BasicHttpAuthenticationFilter and override onPreHandle where you can check the servlet request method if it is GET or POST and act on it.

So something like:

public class MyFilter extends BasicHttpAuthenticationFilter {

    @Override
    protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) {
        if ("GET".equals((HttpServletRequest)request).getMethod()){
            return true;
        }
        return super.onPreHandle(request, response, mappedValue);
    }

}

And in shiro.ini:

[main]
myfilter = mypackage.MyFilter

[urls]
/api/** = myfilter
Wouter
  • 3,976
  • 2
  • 31
  • 50
  • I'm not sure this quite works - it allows access, but when basic auth credentials are provided the current Subject doesn't seem to be authenticated. That's even with calling super.isAccessAllowed(), but returning true regardless, if it's a GET request. – rich Aug 20 '14 at 12:12
  • I think you should use onPreHandle, that is the same the anonymous filter uses. changed the answer – Wouter Aug 20 '14 at 12:24
  • I think I don't quite understand. Do you get the correct behaviour with POSTs (basic auth), but anonymous access on GET won't work? – Wouter Aug 21 '14 at 05:41
  • It does work at restricting auth to POSTs, yes. But I need to still allow auth against GETs so I can show those users extra data they're privileged to see and fallback to just showing public data if no auth is provided. This way doesn't seem to accept basic auth as an option on GET requests, it's just anonymous access. Is that clearer? – rich Aug 21 '14 at 07:04
  • 1
    Yes I get it now. I think you are better off splitting the specific urls that can be accessed anonymously and have the basic auth on all the rest. So /api/public/** = anon /api/** = authcBasic. Why won't this setup work for you? – Wouter Aug 21 '14 at 07:12
  • That might work. I'll have to figure out which URL to call from the various clients ahead of time, I'll give it a go. – rich Aug 21 '14 at 09:12
2

Have you tried:

/api/** = authcBasic[permissive]

  • if user/password is set, shiro sends 401 if they are wrong
  • if user/password is not set, no 401. SecurityUtils.getSubject().authenticated is false
c-toesca
  • 947
  • 1
  • 11
  • 13
1

I think this works.

    @Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    boolean loggedIn = false; //false by default or we wouldn't be in this method
    if (isLoginAttempt(request, response)) {
        loggedIn = executeLogin(request, response);
    }
    if (!loggedIn) {
//            sendChallenge(request, response);
            return true;
    }
    return loggedIn;
}

i.e. if authorisation details are provided execute login as normal (401 if auth details are invalid), else allow them in anyway (then check if authenticated, authorised later).

There's a caveat to this method though in that while it works with curl tests, using Apache's HttpClient Fluent API seems to send a request without authorisation and then send a second request with the credentials after a challenge response, which we're obviously now not sending. Arguably a bug in HttpClient but seeing as we've presumably deviated from the basic auth spec it's probably asking for it. So YMMV. This can be worked around by using preemptive auth and specifying the header value as suggested here.

Community
  • 1
  • 1
rich
  • 18,987
  • 11
  • 75
  • 101