細說shiro之五:在spring框架中集成shiro


官網:https://shiro.apache.org/

 

1. 下載
在Maven項目中的依賴配置如下:

<!-- shiro配置 -->
<dependency>
  <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-core</artifactId>
   <version>${version.shiro}</version>
</dependency>
<!-- Enables support for web-based applications. -->
<dependency>
  <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-web</artifactId>
   <version>${version.shiro}</version>
</dependency>
<!-- Enables AspectJ support for Shiro AOP and Annotations. -->
<dependency>
  <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-aspectj</artifactId>
   <version>${version.shiro}</version>
</dependency>
<!-- Enables Ehcache-based famework caching. -->
<dependency>
  <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-ehcache</artifactId>
   <version>${version.shiro}</version>
</dependency>
<!-- Enables Spring Framework integration. -->
<dependency>
  <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-spring</artifactId>
   <version>${version.shiro}</version>
</dependency>

特別地!Shiro使用了日志框架slf4j,因此需要對應配置指定的日志實現組件,如:log4j,logback等。
在此,以使用log4j為日志實現為例:

<!-- 日志工具 -->
<!--
shiro使用slf4j作為日志框架,所以必需配置slf4j。
同時,使用log4j作為底層的日志實現框架。
-->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.7.25</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-log4j12</artifactId>
  <version>1.7.25</version>
</dependency>
<dependency>
  <groupId>log4j</groupId>
  <artifactId>log4j</artifactId>
  <version>1.2.17</version>
</dependency>

 

2.集成Shiro
在Spring框架中集成Shiro,本質上是與Spring IoC容器和Spring MVC框架集成,所以應該分為2部分來說。
(1)與Spring IoC容器集成
Spring IoC容器提供了一個非常重要的功能,就是依賴注入,將Bean的定義以及Bean之間關系的耦合通過容器來處理。
也就是說,在Spring中集成Shiro時,Shiro中的相應Bean的定義以及他們的關系也需要通過Spring IoC容器實現,配置如下:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
  <property name="securityManager" ref="securityManager"/>
  <property name="loginUrl" value="/index"/>
  <property name="successUrl" value="/home"/>
  <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
  <!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean  -->
  <!-- defined will be automatically acquired and available via its beanName in chain        -->
  <!-- definitions, but you can perform instance overrides or name aliases here if you like: -->
  <!-- <property name="filters">
      <util:map>
          <entry key="logout" value-ref="logoutFilter" />
      </util:map>
  </property> -->
  <property name="filterChainDefinitions">
      <value>
          # some example chain definitions:
          # /admin/** = authc, roles[admin]
          # /docs/** = authc, perms[document:read]
          /login = anon
          /logout = anon
          /error = anon
          /** = user
          # more URL-to-FilterChain definitions here
      </value>
  </property>
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
  <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
  <property name="realm" ref="myRealm" />
  <!-- By default the servlet container sessions will be used.  Uncomment this line
       to use shiro's native sessions (see the JavaDoc for more): -->
  <!-- <property name="sessionMode" value="native"/> -->
</bean>
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

<!-- Define the Shiro Realm implementation you want to use to connect to your back-end -->
<!-- security datasource: -->
<bean id="myRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
  <property name="dataSource" ref="dataSource"/>
  <property name="permissionsLookupEnabled" value="true"/>
</bean>

<!-- Enable Shiro Annotations for Spring-configured beans.  Only run after -->
<!-- the lifecycleBeanProcessor has run: -->
<bean 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>

(2)與Spring MVC集成

跟在普通Java Web應用中使用Shiro一樣,集成Shiro到Spring MVC時,實際上就是通過在web.xml中添加指定Filter實現。配置如下:

<!-- 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>
    <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>

<!-- Make sure any request you want accessible to Shiro is filtered. /* catches all -->
<!-- requests.  Usually this filter mapping is defined first (before all others) to -->
<!-- ensure that Shiro works in subsequent filters in the filter chain:             -->
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

也就是說,其實在Spring中集成Shiro的原理就是:通過在web.xml中配置的Shiro Filter與Spring IoC中定義的相應的Shiro Bean定義建立關系,從而實現在Spring框架集成Shiro。
實際上,通常就是在web.xml添加的Filter與某個Shiro Spring Bean的定義name是相同的,參見示例。
Shiro Filter類圖:

 

3. 數據源配置
在Shiro中,Realm定義了訪問數據的方式,用來連接不同的數據源,如:LDAP,關系數據庫,配置文件等等。
Realm類圖:

也就是說,可以根據實際需求及應用的權限管理復雜度靈活選擇指定數據源。
在此,以org.apache.shiro.realm.jdbc.JdbcRealm為例,將用戶信息存放在關系型數據庫中。

在使用org.apache.shiro.realm.jdbc.JdbcRealm時,必須要在關系型數據庫中存在3張表,分別是:
(1)users表,存放認證用戶基本信息,在該表中必須存在2個字段:username,password。
(2)roles_permissions表,存放角色和權限定義,在該表中必須存在2個字段:role_name,permission。
(3)user_roles表,存放用戶角色對應關系,在該表中必須存在2個字段:username,role_name。
實際上,在更加復雜的應用場景下,通常需要擴展org.apache.shiro.realm.jdbc.JdbcRealm。

 

4. 認證
在Shiro中,認證即執行用戶登錄,讀取指定Realm連接的數據源,以驗證用戶身份的有效性與合法性。
關於Shiro在Web應用中的認證流程,與Shiro在非Web環境的獨立應用中的認證流程一樣,都需要執行用戶登錄,即:

Subject currentUser = SecurityUtils.getSubject();
if(!currentUser.isAuthenticated()) {
  UsernamePasswordToken token = new UsernamePasswordToken(name, password);
  try {
    currentUser.login(token);
  } catch (UnknownAccountException e) {
    logger.error(String.format("user not found: %s", name), e);
  } catch(IncorrectCredentialsException e) {
    logger.error(String.format("user: %s pwd: %s error", name, password), e);
  } catch (ConcurrentAccessException e) {
    logger.error(String.format("user has been authenticated: %s", name), e);
  } catch (AuthenticationException e) {
    logger.error(String.format("account except: %s", name), e);
  }
}

唯一的區別就是,在Java Web環境中,用戶名和密碼參數是通過前端頁面進行傳遞。

 

5. 授權
需要再三強調!!!Shiro作為權限框架,僅僅只能控制對資源的操作權限,並不能完成對數據權限的業務需求。
而對於Java Web環境下Shiro授權,包含個方面的含義。
其一,對於前端來說,用戶只能看到他對應訪問權限的元素。
其二,當用戶執行指定操作(即:訪問某個uri資源)時,需要驗證用戶是否具備對應權限。
對於第一點,在Java Web環境下,通過Shiro提供的JSP標簽實現。

<shiro:hasRole name="admin">
  <a>用戶管理</a>
</shiro:hasRole>
<shiro:hasPermission name="winnebago:drive:eagle5">
  <a>操作審計</a>
</shiro:hasPermission>

必須在jsp頁面中引入shiro標簽庫:

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>

對於第二點,與在非Java Web環境下一樣,需要在后端調用API進行權限(或者角色)檢驗。
api調用:

String roleAdmin = "admin";
Subject currentUser = SecurityUtils.getSubject();
if(!currentUser.hasRole(roleAdmin)) {
  //todo something
}

在Spring框架中集成Shiro,還可以直接通過Java注解方式實現:

@Controller
public class HomeController {
  @RequestMapping("/home")
  @RequiresPermissions(value={"log:manage:*"})
  public ModelAndView home(HttpServletRequest req) {
    ModelAndView mv = new ModelAndView("home");
    return mv;
  }
}

 

6.Spring集成Shiro注意事項

假設存在如下幾個配置文件,分別是:
springDAO.xml:數據源定義
springMVC.xml:Spring MVC配置
springService.xml:其他Spring組件配置
springShiro.xml:Shiro相關Bean配置

第一,在不同版本的Spring中集成Shiro,實現方式不同。
(1)在Spring 4.2.0 RELEASE+版本中集成Shiro:
web.xml:

<servlet>
  <servlet-name>SpringMVC</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:/spring*.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>SpringMVC</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

(2)在Spring 4.1.9 RELEASE-版本中集成Shiro:
web.xml:

<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>classpath:/springDAO.xml,classpath:/springService.xml,classpath:/springShiro.xml</param-value>
</context-param>
<servlet>
  <servlet-name>SpringMVC</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:/springMVC.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>SpringMVC</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>

同時,還需要將在springShiro.xml中配置的org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator放到springMVC.xml中,即:

<!-- 解決在spring 4.1.9 RELEASE及以下版本,集成shiro時注解不生效的問題 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor"/>

 

第二,雖然shiro的注解定義是在Class級別的,但是實際驗證只能支持方法級別:
@RequiresAuthentication
@RequiresPermissions
@RequiresRoles

 

7. 完整示例
詳見:https://git.oschina.net/cchanghui/test-shirospring.git

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM