开发者

Shiro Authorization Permission check using Annotation not working

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.


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


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.


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.


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.


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


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜