1. 程式人生 > >【Shiro】Apache Shiro架構之實際運用(整合到Spring中)

【Shiro】Apache Shiro架構之實際運用(整合到Spring中)

寫在前面:前面陸陸續續對Shiro的使用做了一些總結,如題,這篇博文主要是總結一下如何將Shiro運用到實際專案中,也就是將Shiro整到Spring中進行開發。後來想想既然要整,就索性把SpringMVC和MyBatis也整進去吧,整個比較完整的,也能幫助更多的初學者。所以本文實際是將Shiro整到SSM中(基於maven)。

  其實Shiro整合到Spring中並沒有想象的那麼困難,整到Spring中後,我們自定義的realm啊、securityManager啊等等都會交給spring去管理了,包括我們需要指定哪些url需要做什麼樣的驗證,都是交給spring,也就是說,完全可以擺脫原來的那個.ini配置檔案了,會覺得非常清爽,非常有邏輯。好了,下面開始整,Shiro部分參考了它的官方文件:

http://shiro.apache.org/spring.html

1. 整個工程目錄結構

首先看一下整個工程的目錄結構,先在巨集觀上對整體有個把握。
工程目錄

2. 配置檔案

首先來完成一些配置檔案,先從pom.xml開始,我已經用註釋將它們歸歸類了,很清晰。

2.1 pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
>
<modelVersion>4.0.0</modelVersion> <groupId>demo.shiro</groupId> <artifactId>ShiroSpring</artifactId> <packaging>war</packaging> <version>0.0.1-SNAPSHOT</version> <name>ShiroSpring Maven Webapp</name> <url>http://maven.apache.org</url
>
<dependencies> <!-- shiro核心包 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.5</version> </dependency> <!-- 新增shiro web支援 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.5</version> </dependency> <!-- 新增shiro spring支援 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.5</version> </dependency> <!-- 新增sevlet支援 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <!-- 新增jsp支援 --> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency> <!-- 新增jstl支援 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- 新增log4j日誌 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency> <!-- 新增spring支援 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.3.0.RELEASE</version> </dependency> <!-- 新增mybatis支援 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.0</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>ShiroSpring</finalName> </build> </project>

2.2 log4j.properties

這個檔案,我都是直接從官方拷貝過來的,也貼一下:

log4j.rootLogger=DEBUG, Console  

#Console  
log4j.appender.Console=org.apache.log4j.ConsoleAppender  
log4j.appender.Console.layout=org.apache.log4j.PatternLayout  
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n  

log4j.logger.java.sql.ResultSet=INFO  
log4j.logger.org.apache=INFO  
log4j.logger.java.sql.Connection=DEBUG  
log4j.logger.java.sql.Statement=DEBUG  
log4j.logger.java.sql.PreparedStatement=DEBUG  

2.3 web.xml

接下來在web.xml中新增Spring監聽器、Shiro過濾器和SpringMVC支援:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>ShiroSpring</display-name>
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <!-- spring監聽器 -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>

  <!-- 新增springmvc支援 -->
  <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-mvc.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>springMVC</servlet-name>
    <url-pattern>*.do</url-pattern>
  </servlet-mapping>

  <!-- 新增shiro過濾器 -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
        <!-- 該值預設為false,表示宣告週期由SpringApplicationContext管理,設定為true表示ServletContainer管理 -->
        <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>

  <!-- 編碼過濾器 -->
  <filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <async-supported>true</async-supported>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

2.4 applicationContext.xml

這是整個工程的核心配置檔案,先看一下配置,我再做一些分析。

<?xml version="1.0" encoding="UTF-8"?>    
<beans xmlns="http://www.springframework.org/schema/beans"    
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
    xmlns:p="http://www.springframework.org/schema/p"  
    xmlns:aop="http://www.springframework.org/schema/aop"   
    xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:jee="http://www.springframework.org/schema/jee"  
    xmlns:tx="http://www.springframework.org/schema/tx"  
    xsi:schemaLocation="    
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd  
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd  
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd  
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd  
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">    

    <!-- 自動掃描 -->
    <context:component-scan base-package="demo.service" />

    <!-- 配置資料來源 -->
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/db_shiro"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!-- 配置mybatis的sqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <!-- 自動掃描mappers.xml檔案 -->
        <property name="mapperLocations" value="classpath:demo/mappers/*.xml"></property>
        <!-- mybatis配置檔案 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    </bean>

    <!-- DAO介面所在包名,Spring會自動查詢其下的類 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="demo.dao" />
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>

    <!-- (事務管理)transaction manager, use JtaTransactionManager for global tx -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 自定義Realm -->
    <bean id="myRealm" class="demo.realm.MyRealm"/>  

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
      <property name="realm" ref="myRealm"/>  
    </bean>  

    <!-- Shiro過濾器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
        <!-- Shiro的核心安全介面,這個屬性是必須的 -->  
        <property name="securityManager" ref="securityManager"/>
        <!-- 身份認證失敗,則跳轉到登入頁面的配置 -->  
        <property name="loginUrl" value="/login.jsp"/>
        <!-- 許可權認證失敗,則跳轉到指定頁面 -->  
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>  
        <!-- Shiro連線約束配置,即過濾鏈的定義 -->  
        <property name="filterChainDefinitions">  
            <value>  
                /login=anon
                /user/admin*=authc
                /user/student*/**=roles[teacher]
                /user/teacher*/**=perms["user:create"]
            </value>  
        </property>
    </bean>  

    <!-- 保證實現了Shiro內部lifecycle函式的bean執行 -->  
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>  

    <!-- 開啟Shiro註解 -->
    <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>  

    <!-- 配置事務通知屬性 -->  
    <tx:advice id="txAdvice" transaction-manager="transactionManager">  
        <!-- 定義事務傳播屬性 -->  
        <tx:attributes>  
            <tx:method name="insert*" propagation="REQUIRED" />  
            <tx:method name="update*" propagation="REQUIRED" />  
            <tx:method name="edit*" propagation="REQUIRED" />  
            <tx:method name="save*" propagation="REQUIRED" />  
            <tx:method name="add*" propagation="REQUIRED" />  
            <tx:method name="new*" propagation="REQUIRED" />  
            <tx:method name="set*" propagation="REQUIRED" />  
            <tx:method name="remove*" propagation="REQUIRED" />  
            <tx:method name="delete*" propagation="REQUIRED" />  
            <tx:method name="change*" propagation="REQUIRED" />  
            <tx:method name="check*" propagation="REQUIRED" />  
            <tx:method name="get*" propagation="REQUIRED" read-only="true" />  
            <tx:method name="find*" propagation="REQUIRED" read-only="true" />  
            <tx:method name="load*" propagation="REQUIRED" read-only="true" />  
            <tx:method name="*" propagation="REQUIRED" read-only="true" />  
        </tx:attributes>  
    </tx:advice>  

    <!-- 配置事務切面 -->  
    <aop:config>  
        <aop:pointcut id="serviceOperation"  
            expression="execution(* demo.service.*.*(..))" />  
        <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceOperation" />  
    </aop:config>         
</beans>

  spring的配置檔案中,我主要來說明一下shiro這一部分,因為其他部分都一樣,掃描bean,配置資料來源相關的,配置事務等等。我把上面shiro部分截圖下來了:
shiro
  從這部分的配置可以看出,其實就是和上一節中在.ini檔案中配置的一樣的,只不過現在換成讓spring去管理了,也很好理解,當然這裡自定義的MyRealm還沒寫,因為MyRealm中需要有關於資料庫的操作,所以下面先把mybatis部分整合好(其實加上MyRealm,shiro部分已經整合完了~)。

3. 整合MyBatis

3.1 全域性配置檔案

  首先配置一下mybatis的全域性配置檔案mybatis-config.xml,因為資料來源啥的都交給spring管理了,所以全域性配置檔案就比較清爽了,當然還有一些設定上的東西,就不配置了,這個要看具體實際中使用的時候在配置。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 別名 -->
    <typeAliases>
        <package name="demo.entity"/>
    </typeAliases>
</configura

3.2 配置mapper對映檔案

配置一下UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="demo.dao.UserDao">
<!-- 
    <resultMap type="User" id="UserResult">
        <result property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
    </resultMap>
 -->

    <select id="getByUsername" parameterType="String" resultType="User">
        select * from t_user where username=#{username}
    </select>

    <select id="getRoles" parameterType="String" resultType="String">
        select r.rolename from t_user u,t_role r where u.role_id=r.id and u.username=#{username}
    </select>

    <select id="getPermissions" parameterType="String" resultType="String">
        select p.permissionname from t_user u,t_role r,t_permission p where u.role_id=r.id and p.role_id=r.id and u.username=#{username}
    </select>

</mapper> 

  由配置可知,我們指定的是demo.dao.UserDao這個類,掃描mapper類是交給spring管理的,所以mapper介面可以不用和mapper對映檔案同名且放到同一目錄下,如果是交給mybatis管理我測試過是需要這麼做的,在我之前mybatis的博文中也有提到這些。接下來就是寫dao介面了(其實就是mapper對映檔案對應的mapper介面)。

3.3 mapper介面

public interface UserDao {

    public User getByUsername(String username);

    public Set<String> getRoles(String username);

    public Set<String> getPermissions(String username);
}

  只需要寫介面即可,不需要寫實現,spring的配置檔案中會去掃描mapper,會自動建立一個代理物件來執行相應的方法,要注意的是這個介面中的方法名要和上面mapper對映檔案中的id號一樣的,否則是無法對映到具體的statement上面的,會報錯的。

3.4 po類

這裡就寫一個簡單的User類,資料庫中的儲存資訊可以參考上一篇博文,跟那邊一樣的。

public class User {

    private Integer id;
    private String username;
    private String password;
    //省略get set方法
}

3.5 完成service

順帶著把service層也完善了,這裡我就寫實現類了,介面就不寫了。

@Service("userService")
public class UserServiceImpl implements UserService{

    @Resource
    private UserDao userDao;

    public User getByUsername(String username) {
        return userDao.getByUsername(username);
    }

    public Set<String> getRoles(String username) {
        return userDao.getRoles(username);
    }

    public Set<String> getPermissions(String username) {
        return userDao.getPermissions(username);
    }

}

在service的實現類中,注入剛剛寫好的dao介面即可呼叫其中的方法了,使用的是spring自動建立的代理物件去執行的。

4 整合SpringMVC

4.1 配置檔案

  上面的工作做完了後,其實需要寫一個測試程式來測試一下能否正確訪問資料庫,因為整合必須步步為營。由於這個比較簡單,我就不寫測試程式了。因為前面web.xml配置中已經加入springmvc的支援了,所以這裡直接寫一下spring-mvc.xml配置檔案即可:

<?xml version="1.0" encoding="UTF-8"?>    
<beans xmlns="http://www.springframework.org/schema/beans"    
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"   
    xmlns:p="http://www.springframework.org/schema/p"  
    xmlns:aop="http://www.springframework.org/schema/aop"   
    xmlns:context="http://www.springframework.org/schema/context"  
    xmlns:jee="http://www.springframework.org/schema/jee"  
    xmlns:tx="http://www.springframework.org/schema/tx"  
    xsi:schemaLocation="    
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd  
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd  
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd  
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd  
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd">    

    <!-- 使用註解的包,包括子集 -->
    <context:component-scan base-package="demo.controller" />

    <!-- 檢視解析器 -->
    <bean id="viewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/" />
        <property name="suffix" value=".jsp"></property>
    </bean>

</beans>  

  我這裡只配置了掃描包,沒有配對映器和介面卡,也沒有寫那個<mvc:annotation-driven></mvc:annotation-driven>註解驅動,也是可以執行的,我在網上沒查到原因,也沒去看原始碼,我的猜測是springmvc中可能有預設的對映器和介面卡。如果有看過原始碼的朋友,請不吝賜教。

4.2 完成controller

配置好了後,就完成controller了,一個controller即可,因為一個controller中可以寫很多方法。

@Controller
@RequestMapping("/user")
public class UserController {

    //使用者登入
    @RequestMapping("/login")
    public String login(User user, HttpServletRequest request) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
        try{
            subject.login(token);//會跳到我們自定義的realm中
            request.getSession().setAttribute("user", user);
            return "success";
        }catch(Exception e){
            e.printStackTrace();
            request.getSession().setAttribute("user", user);
            request.setAttribute("error", "使用者名稱或密碼錯誤!");
            return "login";
        }
    }

    @RequestMapping("/logout")
    public String logout(HttpServletRequest request) {
        request.getSession().invalidate();
        return "index";
    }   

    @RequestMapping("/admin")
    public String admin(HttpServletRequest request) {
        return "success";
    }

    @RequestMapping("/student")
    public String student(HttpServletRequest request) {
        return "success";
    }   

    @RequestMapping("/teacher")
    public String teacher(HttpServletRequest request) {
        return "success";
    }   
}

4.2 完成自定義realm

  終於可以寫自己的realm了,上