Search code examples
spring-mvcannotationsshiro

Shiro Spring with filterchain definitions equivalent for @RequiresRoles logical.OR


I am trying to enhance my spring-enabled web-app's security using Apache Shiro and am thus configuring filterchain definitions into a spring-configured file. How do i achieve the equivalent of

@Controller
@RequestMapping("/mywebapp")
// @RequiresAuthentication (is this possible ? wish i could do this !)
public class MyWebAppController  {

@RequiresRoles(value={"Role1","Role2","Role3"},logical=Logical.OR)
@RequestMapping(value="/home", method = RequestMethod.GET)
public String home() { return .. }

and my spring-config file contains this : assume that my dispatcherservlet is mapped to /rest/*

  <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/rest/secure/windowslogin"/>
    <property name="successUrl" value="/mywebapp/rest/menu"/>
    <property name="unauthorizedUrl" value="/mywebapp/rest/unauthorized"/>
    <property name="filters">
        <util:map>
            <entry key="anon">
                <bean class="org.apache.shiro.web.filter.authc.AnonymousFilter"/>
            </entry>
            <entry key="authc">
       <!-- why is this not invoked ? -->
                <bean class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter">
                </bean>
            </entry>
            <entry key="roles">
                <bean class="org.apache.shiro.web.filter.authz.RolesAuthorizationFilter"/>
            </entry>
        </util:map>
    </property>
    <property name="filterChainDefinitions">
        <value>
            /rest/secure/** = anon
            /rest/mywebapp/** = authc, roles[Role1,Role2,Role3]
        </value>
    </property>
</bean>

In the code above i need a logical.OR kind of mapping to the /rest/mywebapp/** using the roles mentioned. This is possible via shiro annotations and it works but rather than specifying at every method i would rather handle it here (since i dont think shiro supports class level annotations yet ?) . Is this possible ?

Also on a side note why is the authc filter not invoked ? ( for now we assume that the windows login can serve as authentication, using shiro only for authorization )

home page = meta refresh to /rest/secure/windowslogin/
             if within intranet -> login ...
             else /rest/secure/login ... login page.

Is it because the loginurl is different ? How do i circumvent this ? Note that my realm's getAuthorizationInfo is invoked though using the roles[ .. ] part specified in the config file.. but i was assuming that there should be a check to see if the request is 'authc' ? (which probably means that the filter is invoked and SubjectUtils.getSubject() is checked for authentication). Am i missing something in the flow or configuration ?


Solution

  • This is how shiro-security.xml looks like.

    <bean id="customFilter1" class="com.pkg.RolesAuthorizationFilter">
            <property name="roles" value="ROLE1,ROLE3,ROLE5"></property>
    </bean>
    <bean id="customFilter2" class="com.pkg.RolesAuthorizationFilter">
            <property name="roles" value="ROLE1,ROLE2,ROLE5,ROLE6"></property>
    </bean>
    
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager" />
            <property name="loginUrl" value="/login" />
            <property name="successUrl" value="/home" />
            <property name="unauthorizedUrl" value="/unauthorized" />    
         <property name="filters">
            <util:map>
                <entry key="authc">
                    <bean   class="org.apache.shiro.web.filter.authc.PassThruAuthenticationFilter" />
                </entry>
            </util:map>
        </property>
    
    <property name="filterChainDefinitions">
                <value>
                 /resources/** = anon
                    /login = anon
                    /logout = authc             
                    /unauthorized = authc
                    /someurl/** = customFilter2
                    /** = customFilter1
                </value>
            </property>
    </bean>
    

    And this is RolesAuthorizationFilter class

    package com.pkg;
    
    import java.util.Arrays;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    
    import org.apache.log4j.Logger;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.web.filter.authz.AuthorizationFilter;
    
    public class RolesAuthorizationFilter extends AuthorizationFilter {
        protected Logger logger = Logger.getLogger(this.getClass()
                .getCanonicalName());
    
        private String[] roles;
    
        @Override
        protected boolean isAccessAllowed(ServletRequest request,
                ServletResponse response, Object mappedValue) throws Exception {
    
            logger.info("= Roles = " + Arrays.toString(roles));
    
            Subject subject = getSubject(request, response);
            boolean allowAccess = false;
    
            for (String role : roles) {
                if (subject.hasRole(role)) {
    
                    logger.info("Authenticated role " + role);
    
                    allowAccess = true;
                    break;
                }
            }
    
            return allowAccess;
        }
    
        public void setRoles(String[] roles) {
            this.roles = roles;
        }   
    }