简介

  • Shiro的组件都是Javabean/pojo式的组件,所以非常容易使用Spring进行组件管理,可以非常方便得从ini配置转为Spring配置(如xml配置文件)。

JavaSE

  • 依赖

    <!--单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.9</version>
      <scope>test</scope>
    </dependency>
    <!--集成Spring做测试必备的依赖,比如要注入Spring容器中的bean-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>4.2.4.RELEASE</version>
      <scope>test</scope>
    </dependency>
    <dependency> <!--用于开启事务,否则测试将无法自动回滚-->
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>4.2.4.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.3</version>
    </dependency>
    
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.4.0</version>
    </dependency>
    
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.2.2</version>
    </dependency>
    
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.2.2</version>
    </dependency>
    
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>4.2.4.RELEASE</version>
    </dependency>
    
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-quartz</artifactId>
      <version>1.2.2</version>
    </dependency>
    <dependency> <!--使用quartz要用到的-->
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
    </dependency>
    
    <!--mysql数据库及druid连接池-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.25</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>0.2.23</version>
    </dependency>
    <dependency> <!--使用JdbcTemplate,方便数据库操作-->
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>4.2.4.RELEASE</version>
    </dependency>
    
  • spring-shiro.xml:取代ini配置,提供了普通JavaSE应用的Spring配置。

    <!-- 缓存管理器 使用Ehcache实现 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
    </bean>
    
    <!-- 凭证匹配器 -->
    <bean id="credentialsMatcher" class="
    com.github.zhangkaitao.shiro.chapter12.credentials.RetryLimitHashedCredentialsMatcher">
        <constructor-arg ref="cacheManager"/>
        <property name="hashAlgorithmName" value="md5"/>
        <property name="hashIterations" value="2"/>
        <property name="storedCredentialsHexEncoded" value="true"/>
    </bean>
    
    <!-- Realm实现 -->
    <bean id="userRealm" class="com.github.zhangkaitao.shiro.chapter12.realm.UserRealm">
        <property name="userService" ref="userService"/>
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
        <property name="cachingEnabled" value="true"/>
        <property name="authenticationCachingEnabled" value="true"/>
        <property name="authenticationCacheName" value="authenticationCache"/>
        <property name="authorizationCachingEnabled" value="true"/>
        <property name="authorizationCacheName" value="authorizationCache"/>
    </bean>
    <!-- 会话ID生成器 -->
    <bean id="sessionIdGenerator" 
    class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>
    <!-- 会话DAO -->
    <bean id="sessionDAO" 
    class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">
        <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>
        <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
    </bean>
    <!-- 会话验证调度器 -->
    <bean id="sessionValidationScheduler" 
    class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">
        <property name="sessionValidationInterval" value="1800000"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>
    <!-- 会话管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">
        <property name="globalSessionTimeout" value="1800000"/>
        <property name="deleteInvalidSessions" value="true"/>
        <property name="sessionValidationSchedulerEnabled" value="true"/>
       <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
        <property name="sessionDAO" ref="sessionDAO"/>
    </bean>
    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
        <property name="realms">
            <list><ref bean="userRealm"/></list>
        </property>
        <property name="sessionManager" ref="sessionManager"/>
        <property name="cacheManager" ref="cacheManager"/>
    </bean>
    <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
    <property name="staticMethod" 
    value="org.apache.shiro.SecurityUtils.setSecurityManager"/>
        <property name="arguments" ref="securityManager"/>
    </bean>
    <!-- Shiro生命周期处理器-->
    <bean id="lifecycleBeanPostProcessor" 
    class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    
  • 其中,Realm实现需要注入userService的bean,它不在本配置文件,而在以下spring-beans.xml中,一样可以找到的。

  • spring-beans.xml

    <!-- 数据库连接池 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/shiro"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    
    <!-- Base DAO: abstract="true"即该类不能被实例化,默认false;抽象bean可以不映射任何类 -->
    <bean id="baseDao" abstract="true">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- DAO -->
    <bean id="permissionDao" class="com.haien.shirochapter12.dao.impl.PermissionDaoImpl" parent="baseDao"/>
    <bean id="roleDao" class="com.haien.shirochapter12.dao.impl.RoleDaoImpl" parent="baseDao"/>
    <bean id="userDao" class="com.haien.shirochapter12.dao.impl.UserDaoImpl" parent="baseDao"/>
    
    <!-- Service -->
    <bean id="permissionService" class="com.haien.shirochapter12.service.impl.PermissionServiceImpl">
        <property name="permissionDao" ref="permissionDao"/>
    </bean>
    
    <bean id="roleService" class="com.haien.shirochapter12.service.impl.RoleServiceImpl">
        <property name="roleDao" ref="roleDao"/>
    </bean>
    
    <!--帮助加密密码-->
    <bean id="passwordHelper" class="com.haien.shirochapter12.service.impl.PasswordHelper">
        <property name="algorithmName" value="md5"/>
        <property name="hashIterations" value="2"/>
    </bean>
    
    <bean id="userService" class="com.haien.shirochapter12.service.impl.UserServiceImpl">
        <property name="userDao" ref="userDao"/>
        <property name="passwordHelper" ref="passwordHelper"/>
    </bean>
    
    <!-- (事务管理)transaction manager, use JtaTransactionManager for global tx -->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!--开启事务-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
  • 分别写User,Role,Permission的entity,dao,service层、实现身份验证和授权的UserRealm。
  • ShiroTest:测试类,测试前删除各表数据,重新插入,分配角色和用户给用户;主要测试方法则是以某个用户登录,判断是否拥有权限。
@Test
public void test() {
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken(u1.getUsername(), password);
    subject.login(token);

    Assert.assertTrue(subject.isAuthenticated());
    subject.checkRole("admin");
    subject.checkPermission("user:create");

    userService.changePassword(u1.getId(), password + "1");
    userRealm.clearCache(subject.getPrincipals());

    token = new UsernamePasswordToken(u1.getUsername(), password + "1");
    subject.login(token);
}

web应用

  • spring-shiro-web.xml:web应用和JavaSE应用的shiro配置是类似的,此处提供一些不一样的配置.

    <!-- web:会话Cookie模板 -->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!--设置cookie名,默认为JSESSIONID-->
        <constructor-arg value="sid"/>
        <!--设置cookie过期时间,单位秒,默认-1,即关闭浏览器时过期-->
        <property name="httpOnly" value="true"/>
        <!--true表示客户端不会暴露脚本代码,有助于减少某些类型的跨站点脚本攻击,
        Servlet2.5及以上才支持-->
        <property name="maxAge" value="180000"/>
    </bean>
    
    <!-- web:会话管理器,使用用于web环境的DefaultWebSessionManager -->
    <bean id="sessionManager"
          class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!--自己管理-->
        <property name="globalSessionTimeout" value="1800000"/>
        <property name="deleteInvalidSessions" value="true"/>
        <property name="sessionValidationSchedulerEnabled" value="true"/>
        <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>
        <property name="sessionDAO" ref="sessionDAO"/>
        <property name="sessionIdCookieEnabled" value="true"/>
        <property name="sessionIdCookie" ref="sessionIdCookie"/>
    </bean>
    
    <!-- web:安全管理器,使用用于web环境的DefaultWebSecurityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm"/>
        <property name="sessionManager" ref="sessionManager"/>
        <property name="cacheManager" ref="cacheManager"/>
    </bean>
    
    <!-- web:基于Form表单的身份验证过滤器 -->
    <bean id="formAuthenticationFilter"
          class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
        <property name="usernameParam" value="username"/>
        <property name="passwordParam" value="password"/>
        <property name="loginUrl" value="/login.jsp"/>
    </bean>
    
    <!-- web:Shiro的Web过滤器,使用ShiroFilterFactoryBean创建ShiroFilter过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        <!--定义自己的过滤器-->
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="formAuthenticationFilter"/>
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /index.jsp = anon
                /unauthorized.jsp = anon
                /login.jsp = authc
                /logout = logout
                /** = user
            </value>
        </property>
    </bean>
    
  • 会话管理器和安全管理器都要换成web环境下的,还要加个过滤器。
  • 过滤器使用ShiroFilterFactoryBean来创建ShiroFilter过滤器,filters属性用于定义自己的过滤器,即ini配置中的[filters],filterChianDefinitions用于声明url和filter的关系,即ini配置中的[urls]。
  • web.xml:

    <!-- Spring配置文件开始  -->
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
          classpath:spring-beans.xml,
          classpath:spring-shiro-web.xml
        </param-value>
      </context-param>
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
      <!-- Spring配置文件结束 -->
    
      <!-- shiro 安全过滤器 -->
      <!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->
      <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <async-supported>true</async-supported>
        <init-param>
          <param-name>targetFilterLifecycle</param-name>
          <param-value>true</param-value>
        </init-param>
      </filter>
      <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
    
      <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
      </servlet>
      <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
  • DelegatingFilterProxy:自动到Spring容器中查找名为shiroFilter的bean(它就定义在spring-shiro-web.xml中),并把filter请求交给它处理。

  • spring-mvc.xml:

    <!--use-default-filters:使用默认过滤器,它会扫描包含@Service、@Component、
    @Repository、@Controller注解的类;不使用则需用include-filter指定扫描哪些类-->
    <context:component-scan base-package="com.haien.shirochapter12.web" 
        use-default-filters="false">
        <!--指定只扫描base-package下有@Controller的类-->
        <context:include-filter type="annotation"
                                expression="org.springframework.stereotype.Controller"/>
        <!--指定只扫描base-package下有@ControllerAdvice的类-->
        <context:include-filter type="annotation"
                 expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
    </context:component-scan>
    
    <!--添加shiro spring aop权限注解的支持,即使用注解式授权-->
    <aop:config proxy-target-class="true"></aop:config> <!--表示使用代理类-->
    <bean class="org.apache.shiro.spring.security.interceptor
                        .AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>
    
    <!--启动SpringMvc注解功能,完成请求和注解controller类的映射-->
    <mvc:annotation-driven/>
    <mvc:view-controller path="/" view-name="index"/> <!--url映射视图,无需通过控制器-->
    
    <!-- 默认的视图解析器 在上边的解析错误时使用 (默认使用html)- -->
    <bean id="defaultViewResolver"
          class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="contentType" value="text/html"/>
        <property name="prefix" value="/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    
    <!-- 控制器异常处理(好像不写也可以) -->
    <bean id="exceptionHandlerExceptionResolver" class="org.springframework.web.servlet
                        .mvc.method.annotation.ExceptionHandlerExceptionResolver">
    </bean>
    <bean class="com.haien.shirochapter12.web.exception.DefaultExceptionHandler"/>
    
  • AnnotationController:上面开启了Shiro注解式访问控制,那么接下来就可以在Controller中使用注解

@Controller
public class AnnotationController {
    @RequestMapping("/hello1")
    public String hello1() {
        SecurityUtils.getSubject().checkRole("admin");
        return "success";
    }

    @RequiresRoles("admin")
    @RequestMapping("/hello2")
    public String hello2() {
        return "success";
    }
}
  • DefaultExceptionHandler:全局异常处理,无权限则返回unauthorized.jsp页面,提示用户无权限。目前数据库只有zhang/123拥有权限,其他如li/123都没有权限。
@ControllerAdvice
public class DefaultExceptionHandler {
    @ExceptionHandler({UnauthorizedException.class}) //处理controller抛出的该类异常及其子类
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public ModelAndView processUnauthenticatedException(NativeWebRequest request
            ,UnauthorizedException e){
        ModelAndView mv=new ModelAndView();
        mv.addObject("exception",e);
        mv.setViewName("unauthorized");
        return mv;
    }
}