1. 程式人生 > >SpringMVC框架和Shiro框架的整合與簡單搭建

SpringMVC框架和Shiro框架的整合與簡單搭建

這篇部落格中沒有什麼高深的技術,只是一個簡單的Shiro框架的搭建...因為之前一直沒找到正確的搭建方法、或是因為報錯搭建失敗...所以我寫下了這篇。希望可以給有需要的人幫助。

我用的編輯工具是Idea。

首先,我們從三個方面來認識Shiro。

1:什麼是Shiro?

Shiro是Apache旗下的一個開源的安全框架,我認為他的主要功能就是2點:認證、授權。

認證:從字面意思上來看也就是那樣,認證、辨別你的身份。

授權:也從字面意思上開看的話是授予、賦予許可權。

(- -...我感覺我在說廢話...)

2:Shiro有什麼用?

上面也說了,主要是為了認證、授權,當然,僅僅是個人初步的理解(撓頭)...歡迎指教。

3:Shiro的元件?

雖然百度一下就有一大堆,但是我還是寫出來吧...至少我又寫了一遍,加深了一點印象...

Subject:主體,可以把它看做是任何可以與應用互動的物件;

SecurityManager:安全管理器,是Shiro的核心,管理所有的Subject;

Authenticator:認證器,負責主體的認證;

Authrizer:授權器,可以給認證完的主體賦予所擁有的相應的許可權;

Realm:資料來源,使用期間認證和授權需要從這裡獲取資料;

SessionManager:Shiro自己抽象出來的一個Session,與Tomcat自帶是不一樣的,用來管理應用和主體之間互動的資料;

SessionDAO:用來做(Shiro的)Session的增刪改查(CRUD)。

CacheManager:快取管理器,可以用來儲存使用者的角色、許可權等資訊。

Cryptography:密碼模組,Shiro自己提供了一些加密碼元件;

(好吧,我承認這一段是簡述了 開濤的部落格-跟我學Shiro ,嘿嘿嘿...)

4:Shiro框架的簡單搭建。

好了,現在要開始主體了!

第一步:

匯入相關的Shiro的Jar包,我是導了這五個包:

    <!-- shiro框架 -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.2.3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.2.3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.2.3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.2.3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-quartz</artifactId>
      <version>1.2.3</version>
    </dependency>
第二步:

a:建立一個spring-shiro.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
    <!-- web.xml中shiro的filter對應的bean -->
    <!-- Shiro 的Web過濾器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--注入安全管理器-->
        <property name="securityManager" ref="securityManager" />
        <!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 -->
        <property name="loginUrl" value="/login/login.do" />
        <!--shiro登入成功後自己跳轉的網頁-->
        <property name="successUrl" value="/homePage.jsp" />
        <!-- 自定義filter配置 自定義認證和授權功能 -->
        <property name="filters">
            <map>
                <!-- 將自定義 的FormAuthenticationFilter注入shiroFilter中
                     authc 所有需要許可權認證的 都會走此filter
                -->
                <entry key="authc" value-ref="formAuthenticationFilter" />
            </map>
        </property>
        <!-- 過慮器鏈定義,從上向下順序執行,一般將/**放在最下邊 -->
        <property name="filterChainDefinitions">
            <value>
                <!--anon 為全部放過-->
                <!--authc 為全部攔截,注意,這裡的authc是上面配置的那個map中的authc-->
                <!-- 放過靜態資源 -->
                /images/** = anon
                /js/** = anon
                /styles/** = anon
                <!-- 登入頁面放過 -->
                /login.jsp=anon
                <!-- 登入方法攔截,最好和上面的loginUrl配置為一致的,不需要寫方法 -->
                /login/login.do = authc
                <!-- 請求 logout.action地址,shiro去清除session,想要登出的話直接訪問logout.do這個路徑就可以進行登出,不需要去寫方法-->
                /logout.do = logout
                <!--對所有的資訊進行攔截,一般配置在最下面-->
                /** = authc
            </value>
        </property>
    </bean>
    <!-- securityManager安全管理器,引入realm -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="shiroRealm" />
    </bean>
    <!-- 自己的realm -->
    <bean id="shiroRealm" class="com.jk.shiro.ShiroRealm">
    </bean>
    <!-- 自定義form認證過慮器 -->
    <!-- 基於Form表單的身份驗證過濾器,不配置將也會註冊此過慮器,表單中的使用者賬號、密碼及loginurl將採用預設值,建議配置 -->
    <bean id="formAuthenticationFilter" class="com.jk.filter.formAuthenticationInfoFilter">
        <!-- 表單中賬號的input名稱 -->
        <property name="usernameParam" value="username" />
        <!-- 表單中密碼的input名稱 -->
        <property name="passwordParam" value="password" />
        <!-- 記住我input的名稱 -->
        <property name="rememberMeParam" value="rememberMe"/>
    </bean>
</beans>
b:在web.xml中新增spring-shiro.xml的宣告
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring*.xml</param-value>
  </context-param>
  <!-- shiro的filter -->
  <!-- shiro過慮器,DelegatingFilterProxy通過代理模式將spring容器中的bean和filter關聯起來 -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <!-- 設定true由servlet容器控制filter的生命週期 -->
    <init-param>
      <param-name>targetFilterLifecycle</param-name>
      <param-value>true</param-value>
    </init-param>
    <!-- 設定spring容器filter的bean id,如果不設定則找與filter-name一致的bean-->
    <init-param>
      <param-name>targetBeanName</param-name>
      <param-value>shiroFilter</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
c:建立一個類繼承FormAuthenticationFilter,重寫裡面的onLoginSuccess方法
@Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,ServletResponse response) throws Exception {
        WebUtils.getAndClearSavedRequest(request);//清除原先的地址 shiro許可權框架在認證時認證通過之後 就會跳轉到上一次訪問的路徑
        //之前有個bug,若上一次請求為登出請求 則會無限死迴圈 認證成功會立即傳送登出請求
        WebUtils.redirectToSavedRequest(request, response, "/homePage.jsp");
        return false;
    }
d:建立一個類繼承AuthorizingRealm,實現裡面的setName、doGetAuthenticationInfo和doGetAuthorizationInfo方法。
public class ShiroRealm  extends AuthorizingRealm {
	//使用註解注入service,所以說,要掃描到service層
	@Autowired
	private LoginService loginService;
	//設定setName,下面要用
	@Override
	public void setName(String name) {
		super.setName("shiroRealm");
	}//注意,這個括號裡寫的是你在spring-shiro.xml中的realm的bean中的Id名
	/**doGetAuthenticationInfo	這個方法為認證方法*/
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		//從token中獲取使用者名稱
		String username = (String) token.getPrincipal();
		//根據使用者名稱去資料控中查詢使用者
		User user = loginService.selUserByUserAccount(username);
		//如果使用者資訊為空則返回找不到賬號異常
		if(user == null){
			throw new UnknownAccountException();
		}
		//設定鹽,也可以不設定。鹽通常為登入使用者名稱+時間戳,新增到資料庫當中。
		String salt = "FaQ";
		//一個簡單的認證器,如果不設定鹽的話就不能新增第三個引數;
		// 第一個引數可以放查詢出來的物件、也可以放獲取的登入使用者名稱,第二個引數為查詢出來的密碼,第三個引數為鹽,第四個引數為上面設定的setName
		SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user,user.getUserpwd(), ByteSource.Util.bytes(salt),this.getName());
		return simpleAuthenticationInfo;
	}
	/**doGetAuthorizationInfo	這個方法為授權方法*/
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
		//獲取已經登入的使用者的資訊
		User user = (User) principal.getPrimaryPrincipal();
		//根據已經獲取的使用者的資訊查詢當前使用者所擁有的所有的許可權
		List<String> permissionList = loginService.getUserPermissionByUserName(user.getUseraccount());
		//New一個簡單的授權器
		SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
		//把查詢到的使用者的許可權的資訊放入到授權器當中
		simpleAuthorizationInfo.addStringPermissions(permissionList);
		return simpleAuthorizationInfo;
	}
}
第三步:

emmmmmm,到這裡配置檔案差不多就配置完了,執行流程的話我簡單說兩句,並沒有太深入只是簡單的使用,專案啟動載入web.xml,載入spring的各種配置檔案,然後進入登入頁面,在進入的時候會優先去訪問一遍spring-shiro.xml中配置的logunUrl中的地址,在那裡面會先通過request.getAttribute()方法獲取Shiro的異常類全限定名判斷下是否為空,如果為空則繼續進入登入頁面,如果不為空則去判斷檢視是使用者名稱、密碼或者驗證碼中的那個錯誤。

loginUrl的配置:(loginUrl的那個路徑就是訪問Controller中的指定方法的路徑)

@RequestMapping("login")
    public String login(HttpServletRequest request) throws Exception {
        //如果登入失敗從requst中獲取認證異常資訊,shiroLoginFailure為shiro 的異常類全限定名。該方法不處理認證成功,只有在認證失敗的時候才會進入。
        String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
        if(exceptionClassName!=null){
            if(UnknownAccountException.class.getName().equals(exceptionClassName)){
                throw new CustomException("賬號不存在");
            }else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)){
                throw new CustomException("使用者名稱/密碼不存在");
            }else if("randomCodeError".equals(exceptionClassName)){
                throw new CustomException("驗證碼錯誤");
            }else{
                throw new Exception();
            }
        }
        return "../../login";
    }

在輸入賬號密碼(賬號密碼的name值分別為:username和password,在spring-shiro.xml中引入了一個自定義form認證過濾器,他繼承了shiro的form認證過濾器,在裡面配置了username和password,他會轉成相應的格式去shiro的認證過濾器裡面進行儲存資料),點選登入的時候,會驗證一下登入路徑是否是在spring-shiro.xml中配置的過濾器鏈中攔截的方法路徑,如果不是的話會繼續進入loginUrl地址,然後繼續返回登入頁面。如果路徑正確,則會進入realm中,在realm中通過token獲取到登入的使用者名稱,然後拿使用者名稱去資料庫進行查詢,接著判斷一下,如果查詢返回的物件為空則證明使用者名稱錯誤,throw new UnknownAccountExcecption()丟擲一個使用者名稱錯誤的異常,如果不為空則繼續往下走,新new 一個SimpleAuthenticationInfo物件,在物件中放入查詢出來的物件或者使用者名稱,查詢出來的密碼,鹽(加密處理),this.getName(獲取上面set的Name),Shiro會比較登入的賬號密碼和資料庫的賬號密碼是否一致,不一致走loginurl中的路徑,一致說明認證通過,走loginUrl下面的successUrl路徑,successUrl路徑配置的是認證通過後要跳轉的頁面。

在賬號密碼認證成功之後會進入realm下面的授權器的方法(doGetAuthorizationInfo),使用形式引數物件的PrincipalCollection物件中的getPrimaryPrincipal方法獲取到在認證器裡面的SimpleAuthenticationInfo裡面的第一個引數,拿該引數去資料庫獲取該使用者所擁有的許可權放入一個List<String>的集合當中,然後在new SimpleAuthorizationInfo,使用simpleAuthorizationInfo物件的addStringPermission方法把獲取到的許可權的集合放進去。然後返回該物件。

上述一切都是Ok的之後會進入spring-shiro.xml中的successUrl所指向的頁面,在這個頁面可以使用Shiro的各種Jsp標籤,當然前提是需要匯入Shiro的標籤庫。

判斷該使用者是否有許可權訪問某一個方法的時候需要在方法的上面加入@RequiresPermission("user:add")之類的註解,括號裡放的是許可權表中的許可權欄位,有許可權則能訪問,無許可權就報一個異常。

要判斷是否可以使用方法的話就需要開啟對Shiro註解的支援了,我是在spring-mvc.xml中加入了

<!-- 開啟aop,對類代理 -->
	<aop:config proxy-target-class="true"></aop:config>
	<!-- 開啟shiro註解支援 -->
	<bean class="
		org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
		<property name="securityManager" ref="securityManager" />
	</bean>
還有無許可權訪問的話可以在加上
<!-- 使用shiro註解  由於原始碼問題需要自己捕獲異常 並且跳轉頁面
	   若當前使用者無許可權 則跳轉到無許可權頁面
	-->
	<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
		<property name="exceptionMappings">
			<props>
				<prop key="org.apache.shiro.authz.UnauthorizedException">exception</prop>
			</props>
		</property>
	</bean>
就可以報異常自動跳到exception這個異常頁面了。

Ok,一切大功告成!