Shiro 整合 spring hello world

shiro 是一個Java安全框架可以進行認證、授權、加密和會話管理。Shiro的易於理解的API適用於保護任何應用程序——從最小的移動應用程序到最大的Web和企業應用程序。

【官方解釋:

Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro's easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.】

接下來是一個應用shiro 的使用步驟,由於習慣直接寫註釋到代碼中,因此步驟文字就簡寫啦

一、導入所需jar包

        

二、配置web.xml, 包括配置spring、spring-mvc以及shiro的過濾器

<?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_2_5.xsd" 
	id="WebApp_ID" version="2.5">

<!-- spring 環境配置:start -->
    <!-- 配置加載 Spring 文件的監聽器,加載 applicationContext.xml 文件 -->  
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:applicationContext.xml</param-value>
	</context-param>
	<!-- Spring監聽器 --> 
	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
<!-- spring 環境配置:end -->


<!-- 配置 spring-mvc:start -->
	<!-- The front controller of this Spring Web application, responsible for handling all application requests -->
	<!-- spring 默認會去 WEB-INF 下找名字爲 <servlet-name>-servlet.xml的配置文件, 或可以設置<init-param> 指定配置文件位置 -->
	<servlet>
		<servlet-name>spring</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<!-- Map all requests to the DispatcherServlet for handling -->
	<servlet-mapping>
		<servlet-name>spring</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
<!-- 配置 spring-mvc:end -->



<!-- 1. 配置 shiro 的 shiroFilter -->
	<!-- ==================================================================
         Filters
         ================================================================== -->
    <!-- Shiro Filter is defined in the spring application context: -->
    <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>

    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
<!--  配置 shiro 的 shiroFilter: end -->

	<welcome-file-list>
		<welcome-file>login.jsp</welcome-file>
	</welcome-file-list>
</web-app>

三、配置spring-mvc的配置文件(spring-servlet.xml),由於web.xml中使用的是默認方式,該文件需要放於WEB-INF目錄下

【不是用默認位置的話可以通過在spring-mvc配置的servlet中增加如下標籤來指定: 

      <init-param>  
            <param-name>contextConfigLocation</param-name>  
            <param-value>classpath:spring-mvc.xml</param-value>  
        </init-param>  】

<?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:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">

	<!-- 掃描包 -->
	<context:component-scan base-package="com.shiro"></context:component-scan>

    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">  
		<!-- 這裏的配置我的理解是自動給後面action的方法return的字符串加上前綴和後綴,變成一個 可用的url地址  -->
		<property name="prefix" value="/" />  
        <property name="suffix" value=".jsp" />  
    </bean>  
    
    <mvc:annotation-driven></mvc:annotation-driven>
    <mvc:default-servlet-handler/>

</beans>

四、配置spring主配置文件applicationContext.xml, shiro 的配置也整合於此:

(note: 此處配置了兩個realm, 即兩重驗證,不需要的可直接幹掉一個)

<?xml version="1.0" encoding="UTF-8"?>
<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.xsd">


<!-- 1. 配置 SecurityManager. 核心 -->
    <!-- =========================================================
         Shiro Core Components - Not Spring Specific
         ========================================================= -->
    <!-- Shiro's main business-tier object for web-enabled applications
         (use DefaultSecurityManager instead when there is no web environment)-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="authenticator" ref="authenticator"></property>
        <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
        <!-- 配置 session 的管理方式  <property name="sessionMode" value="native"/>  -->
        <property name="realms">
        	<list>
        		<ref bean="jdbcRealm" />
        		<ref bean="secondRealm" />
        	</list>
        </property> 
        <!-- <property name="realm" ref="jdbcRealm"></property> -->
        
    </bean>
<!-- 1. 配置 SecurityManager : end -->

<!-- 2. 配置 cacheManager.   -->
	<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one
             will be creaed with a default config:
             <property name="cacheManager" ref="ehCacheManager"/> -->
        <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want
             a specific Ehcache configuration to be used, specify that here.  If you don't, a default
             will be used.:
             -->
        <!-- 2.1 加入encache 的 jar包及 cache 的配置文件 -->
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> 
    </bean>
<!-- 2. 配置 cacheManager : end  -->


<!--  配置多 realm 及驗證策略  -->
	<bean id="authenticator" class="org.apache.shiro.authc.pam.ModularRealmAuthenticator">
		<!--
		<property name="realms">
			 list 驗證的時候有順序 
			<list>
				<ref bean="jdbcRealm"/>
				另一個realm 的bean  
				<ref bean="secondRealm" />
			</list>
		</property> 
		-->
		<!-- 配置認證策略  -->
		<property name="authenticationStrategy">
			<!-- AllSuccessfulStrategy 全部驗證成功才成功;或(默認)FirstSuccessfulStrategy/AtLeastOneSuccessfulStrategy -->
			<bean class="org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy" ></bean>
		</property>
	</bean>
<!--  配置多 realm 及驗證策略  :end -->

<!-- 3. 配置 Realm
		3.1 直接配置實現接口 Realm 的bean
 -->
    <!-- Used by the SecurityManager to access security data (users, roles, etc).
         Many other realm implementations can be used too (PropertiesRealm,
         LdapRealm, etc. -->
    <bean id="jdbcRealm" class="com.shiro.Realms.ShiroRealm">
        <property name="credentialsMatcher">
        	<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher" >
        		<!--  加密的算法  -->
        		<property name="hashAlgorithmName" value="MD5"></property>
        		<!-- hashIterations 加密的次數  -->
        		<property name="hashIterations" value="1024"></property>
        	</bean>
        </property> 
    </bean>
    
    <!-- 第二個 Realm -->
    <bean id="secondRealm" class="com.shiro.Realms.SecondRealm">
        <property name="credentialsMatcher">
        	<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher" >
        		 <!-- 加密的算法   -->
        		<property name="hashAlgorithmName" value="SHA1"></property>
        		 <!-- hashIterations 加密的次數  -->
        		<property name="hashIterations" value="1024"></property>
        	</bean>
        </property>
    </bean>
<!-- 3. 配置 Realm :end -->


<!-- 4. 配置 LifecycleBeanPostProcessor, 可以自動的來調用配置在 spring IOC 容器中的shiro bean 的聲明週期的方法 -->
	<!-- =========================================================
         Shiro Spring-specific integration
         ========================================================= -->
    <!-- Post processor that automatically invokes init() and destroy() methods
         for Spring-configured Shiro objects so you don't have to
         1) specify an init-method and destroy-method attributes for every bean
            definition and
         2) even know which Shiro objects require these methods to be
            called. -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 4. 配置 LifecycleBeanPostProcessor : end -->

<!-- 5. 啓用IOC 容器中使用 shiro 的註解,但必須在配置了 LifecycleBeanPostProcessor 之後使用 -->
	<!-- 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>
<!-- 5. 啓用IOC 容器中使用 shiro 的註解 : end -->

<!-- 6. 配置ShiroFilter(重點) -->
<!-- 	6.1 id 必須和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致. -->
	<!-- Define the Shiro Filter here (as a FactoryBean) instead of directly in web.xml -
         web.xml uses the DelegatingFilterProxy to access this bean.  This allows us
         to wire things with more control as well utilize nice Spring things such as
         PropertiesPlaceholderConfigurer and abstract beans or anything else we might need: -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!-- 登錄頁面  -->
        <property name="loginUrl" value="/login.jsp"/>
        <!-- 登錄成功頁面  -->
        <property name="successUrl" value="/list.jsp"/>
        <!-- 沒有權限的頁面 -->
        <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 overrides or parent/child consolidated configuration
             here if you like: -->
        <!-- <property name="filters">
            <util:map>
                <entry key="aName" value-ref="someFilterPojo"/>
            </util:map>
        </property> -->
        <!-- 
        	配置哪些頁面需要受保護
        	以及訪問這些頁面需要的權限
        	1) anon 可以被匿名訪問
        	2) authc 必須認證(登錄)後才能夠訪問
        	3) logout 登出
        	第一次匹配優先
         -->
        <property name="filterChainDefinitions">
            <value>
                /login.jsp = anon
                /shiro/login = anon
                /shiro/logout = logout
                
                # 角色權限配置
                /user.jsp = roles[user]
                /admin.jsp = roles[admin]
               
                # everything else requires authentication:
                /** = authc
            </value>
        </property>
    </bean>
<!-- 6. 配置ShiroFilter(重點) : end -->

</beans>

五、導入必要的配置文件 ehcache.xml 和 log4j.properties(ehcache.xml 可在hibernate 的包中找到,log4j.properties 也比較常見就不貼了)

六、寫Realm類,繼承於AuthorizingRealm

package com.shiro.Realms;

import java.util.HashSet;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;


public class ShiroRealm extends AuthorizingRealm {

	/**
	 * 認證
	 * 
	 * 使用 鹽值加密
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
			throws AuthenticationException {
		System.out.println("===> FirstRealm: ");
		System.out.println("---> doGetAuthenticationInfo: " + token.hashCode());
		
		// 1. 把 AuthenticationToken 轉換爲 UsernamePasswordToken
		UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
		
		// 2. 從 UsernamePasswordToken 中獲取 username
		String username = usernamePasswordToken.getUsername();
		
		// 3. 調用數據庫的方法,從數據庫查詢 username對應的用戶記錄
		System.out.println("---> 從數據庫中獲取username: " + username + "所對應的信息");
		
		// 4. 若用戶不存在,則可以拋出 UnKnownAccountException 異常
		if("unknown".equals(username)) {
			throw new UnknownAccountException("---> 用戶不存在!");
		}
		
		// 5. 根據用戶信息的情況,決定是否需要拋出其他異常 AuthenticationException
		if("monster".equals(username)) {
			throw new LockedAccountException("---> 用戶被鎖定!");
		}
		
		// 6. 根據用戶信息的情況,來構建 AuthenticationInfo 對象,並返回
		//
		// 以下信息是從數據庫中獲取的
		// 1) principal: 認證的實體信息,可以是 username 也可以是數據庫對應用戶的實體對象
		// 2) credentials: 密碼(來自數據庫)
		// 3) realmName: 當前 realm 對象的name, 調用父類的 getName() 方法即可.
		 
		Object principal = username;
		Object credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";	// 加密後的密碼; 原始密碼爲 123456,鹽爲 "admin"
		String realmName = getName();

		// 用戶名作爲鹽值
		ByteSource credentialsSalt = ByteSource.Util.bytes(username);
		
		SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
		
		return info;
	}

	public static void main(String[] args) {
		String algorithmName = "MD5";
		Object source = "123456";	// 原始密碼
		Object salt = "admin";		// 鹽值
		int hashIterations = 1024;	// 加密的次數
		
		Object result = new SimpleHash(algorithmName, source, salt, hashIterations);// hashIterations 加密的次數
		
		System.out.println("鹽值加密後的密碼:" + result);
	}

	/**
	 * 授權
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		// 1. 從 PrincipalCollection 中獲取登陸用戶的信息
		Object principal = principals.getPrimaryPrincipal();
		
		// 2. 利用登陸的用戶信息來授權給當前用戶的角色或者權限(可能需要查詢數據庫)
		Set<String> roles = new HashSet<>();
		roles.add("user");
		if ("admin".equals(principal)) {
			roles.add("admin");
		}
		
		// 3. 創建 SimpleAuthorizationInfo, 並設置其 role 屬性
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
		
		// 4. 返回 對象
		return info;
	}
}

第二個Realm類與 ShiroRealm只是加密算法改爲sha1而已。不過需要注意的是由於沒有使用數據庫,因此密碼都是加密後寫死的,使用時需據實修改。

六、處理web請求的handler類

package com.shiro.handlers;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("/shiro")
public class ShiroHandler {

	@RequestMapping("/login")
	public String login(@RequestParam("username") String username, 
			@RequestParam("password") String password) {
		Subject currentUser = SecurityUtils.getSubject();
		
		if(!currentUser.isAuthenticated()) {
			UsernamePasswordToken token = new UsernamePasswordToken(username, password);
			// 記住我
			token.setRememberMe(true);
			try {
				System.out.println("---> 1. " + token.hashCode());
				// 執行登錄
				currentUser.login(token);
			} catch (AuthenticationException ae) {
				System.out.println("登錄失敗: " + ae.getMessage());
			}
		}
		
		return "redirect:/list.jsp";
	}
	
}

七、最後添加個登陸和幾個可區分情況跳轉的頁面,至此hello world也就寫好了



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章