Search code examples
restbasic-authenticationshiro

How to do role-based authorization with Apache Shiro depending on HTTP request method


I'm struggling to figure out how I can do role-based authorization depending on what HTTP method a request is using. I use HTTP basic auth and depending on the users role and the HTTP method used a request should succeed or fail.

Example:

  • a GET request to http://localhost/rest/ should always be allowed, even to non-authenticated users (anon access)
  • a PUT request to http://localhost/rest/ (same resource!) should only be allowed if user is authenticated
  • a DELETE request to http://localhost/rest/ (same resource!) should only be allowed if user is authenticated and has the role ADMINISTRATOR

My current (non-working) attempt of configuring shiro.ini looks like this:

/rest = authcBasic[PUT], roles[SERVICE_PROVIDER]
/rest = authcBasic[POST], roles[EXPERIMENTER]
/rest = authcBasic[DELETE], roles[ADMINISTRATOR]
/rest = authcBasic

Update

I've just found https://issues.apache.org/jira/browse/SHIRO-107 and updated my shiro.ini to be

/rest/**:put    = authcBasic, roles[SERVICE_PROVIDER]
/rest/**:post   = authcBasic, roles[EXPERIMENTER]
/rest/**:delete = authcBasic, roles[ADMINISTRATOR]
/rest/**        = authcBasic

but it still doesn't work. It seems that only the last rule matches. Also, the commit comment also seems to indicate that this only works with permission-based authorization. Is there no equivalent implementation for role-based authz?


Solution

  • I had a similar situation with Shiro and my REST application. While there may be a better way (I hadn't seen SHIRO-107), my solution was to create a custom filter extending the Authc filter (org.apache.shiro.web.filter.authc.FormAuthenticationFilter). You could do something similar extending the authcBasic filter or the Roles filter (although I think authcBasic would be better as it is probably more complicated).

    The method you want to override is "protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)". Your argument (e.g. "ADMINISTRATOR") will come in on the mappedValue as String[] where the arguments were split by commas.

    Since I needed the possibility of both a method and a role, I ended up have my arguements looks like "-". For example:

    /rest/** = customFilter[DELETE-ADMINISTRATOR]

    That let me split out the role required to perform a delete from the role required for a POST by doing something like:

    /rest/** = customFilter[DELETE-ADMINISTRATOR,POST-EXPERIMENTER]

    I think if you play with this, you'll be able to get the functionality you need.

    BTW, I hadn't seen SHIRO-107, so I've not tried that technique and probably won't since I've already invented my own custom filter. However that may provide a cleaner solution than what I did.

    Hope that helps!