1. 程式人生 > >shiro框架之五-----在spring框架中使用shiro

shiro框架之五-----在spring框架中使用shiro

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