6

Platform: Shiro 1.1.0, Spring 3.0.5

I'm trying to secure the MVC Controller methods using Shiro annotation. However something is wrong with annotations. Regular calls are just working OK. There is nothing specific in Shiro debug also.

My shiro configuration:

<!-- Security Manager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="sessionMode" value="native" />
        <property name="realm" ref="jdbcRealm" />
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

    <!-- Caching -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManager" ref="ehCacheManager" />
    </bean>

    <bean id="ehCacheManager"
        class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" />

    <bean id="sessionDAO"
        class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO" />

    <bean id="sessionManager"
        class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <property name="sessionDAO" ref="sessionDAO" />
    </bean>


    <!-- JDBC Realm Settings -->
    <bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
        <property name="name" value="jdbcRealm" />
        <property name="dataSource" ref="dataSource" />
        <property name="authenticationQuery"
            value="SELECT password FROM system_user_accounts WHERE username=? and status=1" />
        <property name="userRolesQuery"
            value="SELECT role_name FROM system_roles r, system_user_accounts u, system_user_roles ur WHERE u.user_id=ur.user_id AND r.role_id=ur.role_id AND u.username=?" />
        <property name="permissionsQuery"
            value="SELECT permission_name FROM system_roles r, system_permissions p, system_role_permission rp WHERE r.role_id=rp.role_id AND p.permission_id=rp.permission_id AND r.role_name=?" />
        <property name="permissionsLookupEnabled" value="true"></property>
    </bean>

    <!-- Spring Integration -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

    <!-- Enable Shiro Annotations for Spring-configured beans. Only run after 
        the lifecycleBeanProcessor has run: -->
    <bean id="annotationProxy"
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
        depends-on="lifecycleBeanPostProcessor" />
    <bean
        class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>

    <!-- Secure Spring remoting: Ensure any Spring Remoting method invocations 
        can be associated with a Subject for security checks. -->
    <bean id="secureRemoteInvocationExecutor"
        class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">
        <property name="securityManager" ref="securityManager" />
    </bean>

    <!-- Shiro filter -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/login" />
        <property name="successUrl" value="/dashboard" />
        <property name="unauthorizedUrl" value="/error" />
        <property name="filterChainDefinitions">
            <value> 
                <!-- !!! Order matters !!! -->
                /authenticate = anon
                /login = anon
                /logout = anon
                /error = anon
                /** = authc
            </value>
        </property>
    </bean>

I can get the following working correctly:

@RequestMapping(value="/form") 
public String viewPatientForm(Model model, @RequestParam(value="patientId", required=false) Long patientId){    
   if (!SecurityUtils.getSubject().isPermitted("hc:viewPatient")){
      logger.error("Operation not permitted");
      throw new AuthorizationException("No Permission"); 
   }
}

But the below doesn't work:

@RequiresPermissions("hc:patientView")
    @RequestMapping(value="/form")
    public String viewPatientForm(Model model, @RequestParam(value="patientId", required=false) Long patientId){    

Am I missing something? Please help.

Firdous Amir
  • 1,297
  • 5
  • 21
  • 39
  • hello i've also been having a problem very similar to yours posted here : http://stackoverflow.com/questions/6859432/spring-aop-and-apache-shiro-configuration-annotations-not-been-scanned and so far nothing concrete.Am really wondering why people are so silent about it.any way did you find any solution ? – black sensei Nov 16 '11 at 14:00
  • No solution yet, because I don't know where the problem is. There is nothing in the debug also. I think the Shiro team is not posting in Stack overflow anymore. However there are good activities in http://shiro-user.582556.n2.nabble.com/. May we should take stuffs there. – Firdous Amir Nov 20 '11 at 09:44

6 Answers6

9

You were absolutely right. After seeing your comment, I started giving it a thought. Well then I found out that it was NOT an implementation problem with Shiro, but the jar dependecies were not properly configured. Shiro's pom.xml should have dependency for cglib2 too.

So the below changes worked for me :

  1. Include all these four jar files.

    aspectjrt-1.6.11.jar,
    aspectjweaver-1.6.12.jar,
    cglib-2.2.2.jar,
    asm-3.3.1.jar,

If you are using maven then :
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.6.11</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.6.12</version>
</dependency>    
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>

And finally placing the aop:aspectj-autoproxy in the webApplicationContext.xml

<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- Annotation, so that it's easier to search controllers/components -->
<context:component-scan base-package="com.pepsey.soft.web.controller"/>

Note : The above two configuration should be placed together in the same spring-webApplicationContext.xml. Otherwise it won’t work. Moreover remove context:annotation-config if you have used it in your config. context:component-scan already scans all annotations.

Once you start testing , set your log4j to debug or (better) trace mode. Whenever you are starting your server you will find somewhere the following entry in your logs :

08:16:24,684 DEBUG AnnotationAwareAspectJAutoProxyCreator:537 - Creating implicit proxy for bean 'userController' with 0 common interceptor and 1 specific interceptors

Community
  • 1
  • 1
3

If you're avoiding Spring XML and using primarily Java and annotation configuration, the easiest way to fix this is to add

@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)

to all your @Controller classes. You need cglib on the classpath.

artbristol
  • 32,010
  • 5
  • 70
  • 103
  • 1
    This worked perfectly for me, thanks been driving me crazy! I didn't need to explicitly add cglib, or make any other dependency changes. – Jumwah Dec 05 '18 at 06:29
3

Guess Shiro was built when Spring 2.0 was in place. Shiro’s annotations (RequiresRoles etc…) works well for the spring container managed beans (service layer), but it does not work with @Controller annotation. This is due to the fact that @Controller is being component scanned by spring framework. I used AOP to resolve the issue. Below is the solution which worked for me. For the below solution to work you have to include the below four jars:

aspectjrt-1.6.11.jar
aspectjweaver-1.6.12.jar
cglib-2.2.2.jar
asm-3.3.1.jar

If you are using maven then below configuration would be helpful.

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.6.11</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.6.12</version>
</dependency>    
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency> 

Below is a controller class

import org.apache.shiro.authz.annotation.RequiresRoles;

@Controller
public class PatientController {

    @RequiresRoles(“admin,warden”)
    @RequestMapping(value="/form") 
    public String viewPatientForm(Model model,  @RequestParam(value="patientId", required=false) Long patientId){    

        return “somePatientFormJsp”;
    }
}

Create the below Aspect for the annotation (RequiresRoles). You can use the same principle to create pointcuts for RequiresPermission.

import java.util.Arrays;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class WebAuthorizationAspect {

    @Before("@target(org.springframework.stereotype.Controller) && @annotation(requiresRoles)")
    public void assertAuthorized(JoinPoint jp, RequiresRoles requiresRoles) {
        SecurityUtils.getSubject().checkRoles(Arrays.asList(requiresRoles.value()));
    }
}

In your spring-webApplicationContext.xml wherever you have mentioned

<aop:aspectj-autoproxy proxy-target-class="true"/>
<!-- Annotation, so that it's easier to search controllers/components -->
<context:component-scan base-package="com.example.controller"/>

Note : The above two configuration should be placed together in the same spring-webApplicationContext.xml. Otherwise it won’t work. Moreover remove context:annotation-config if you have used it in your config. context:component-scan already scans all annotations.

abatishchev
  • 98,240
  • 88
  • 296
  • 433
  • Debashish, I see your point. However, the Service layer is also component scanned in my application, so I can't really narrow down the real distinction here between the [at]Controller and [at]Service. Anyway, let me try your approach. I hope the Shiro team will come up with something more straight forward built in. Thanks. – Firdous Amir Nov 27 '11 at 03:39
  • Hello THere i just tried your suggestion and it seems to be working. But i have one question though. where should we put the stuff? in the config application context or in dispather application context or both? – black sensei Mar 05 '12 at 14:04
1

I have only used spring-hibernate example from sample. To use annotations like @RequiresPermissions and others I tried configuration from shiro manual, configuration from this post, but I was either unsuccessful to compile or run the valid urls. So I only commented all the @RequiresPermissions from ManageUserController and started to use it in service implementation. E.g In DefaultUserService in getAllUsers method I added the annotation @RequiresPermissions("user:manage"). Magically now the application works as expected. Whenever the url manageUsers is called it displays the list page if the user has role user:manage and throws the user to /unauthorized if the user don't have that permission.

I have even configured the application to use mysql instead. To make the permissions independent of roles according to new RBAC(http://www.stormpath.com/blog/new-rbac-resource-based-access-control) I have created a new class called Permission as

@Entity
@Table(name = "permissions")
@Cache(usage= CacheConcurrencyStrategy.READ_WRITE)
public class Permission {
    @Id
    @GeneratedValue
    private Long id;
    private String element;
    private String description;
    // setter and getter

Now Role class is configured as

 @CollectionOfElements
    @JoinTable(name="roles_permissions")
    @Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
    public Set<Permission> getPermissions() {
        return permissions;
    }

And finally SampleRealm as

 for (Role role : user.getRoles()) {
        info.addRole(role.getName());

        System.out.println("Roles " + role.getName());

        // Get permissions first
        Set<Permission> permissions = role.getPermissions();
        Set<String> permissionsStrings = new HashSet<String>();

        for (Permission permission : permissions) {
            permissionsStrings.add(permission.getelement());
            System.out
                    .println("Permissions " + permission.getelement());
        }

        info.addStringPermissions(permissionsStrings);
    }

It creates five tables as | permissions | | roles | | roles_permissions | | users | | users_roles |

And permissions is independent of any other. According to new RBAC you have both ways (explicit and implicit) way of authorising resources.

Pramod
  • 806
  • 1
  • 8
  • 10
0

You need to write the AuthorizationAttributeSourceAdvisor to enable Shiro's annotations bean as per the Shiro documentation

If you have written ShiroConfiguration class, make sure you include this:

@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
    return new LifecycleBeanPostProcessor();
}

@Bean
@ConditionalOnMissingBean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(DefaultSecurityManager securityManager) {
    // This is to enable Shiro's security annotations
    AuthorizationAttributeSourceAdvisor sourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    sourceAdvisor.setSecurityManager(securityManager);
    return sourceAdvisor;
}

@ConditionalOnMissingBean
@Bean(name = "defaultAdvisorAutoProxyCreator")
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
    proxyCreator.setProxyTargetClass(true);
    return proxyCreator;
}

Example ShiroConfiguration on Github

Manish Singh
  • 5,848
  • 4
  • 43
  • 31
0

I had the same problem. My fix was changing my jersey version from 2.2 to 2.22.2 and all @RequiresPermissions worked on my controllers.