Shiro總結及SSM框架通過Shiro實現登錄認證及用戶授權

權限管理,可以實現用戶訪問系統的控制,權限管理分爲認證和授權

Shiro(Apache Shiro)是Java的一個安全框架,Shiro屬於輕量級框架,可以實現認證、授權、加密、會話管理、與Web集成、緩存等

Shiro核心類示意圖
在這裏插入圖片描述
主要類:

Authentication: 身份認證

Authorization: 授權,即權限驗證,驗證某個已認證用戶是否擁有某個權限

Seesion Manager: 會話管理,每次用戶登錄都是一次會話,在沒有退出之前,他的所有信息都在會話中

Cryptography: 加密,保護數據的安全性

Web Support: Web支持,可以容易的集成到Web環境中

Catching: 緩存,用戶登錄後,其用戶信息、所擁有的角色、權限不用每次去查,可以提高效率

Concurrency: shiro支持多線程應用的併發驗證,即在一個線程中開啓另一個線程,能把權限自動傳播過去

Testing: 提供測試支持

Run As: 允許一個用戶以另一個用戶的身份進行訪問

Remember Me: 記住我,即一次登錄後,下次就不用登錄了

Shiro架構圖:
在這裏插入圖片描述

主要組件:

Subject: 主體,主體可以是用戶也可以是程序,主體要訪問系統,系統需要對主體進行認證、授權。

SecurityManager: 安全管理器,主體進行認證和授權都是通過securityManager進行

Authenticator: 認證器,主體進行認證最終通過Authenticator進行的。

Authorizer: 授權器,主體進行授權最終通過authenticator進行的。

SessionManager: 會話管理,web應用中一般是用web容器對session進行管理,Shiro也提供一套session管理的方式。

SessionDao: 通過SessionDao管理Session數據

CacheManager: 緩存管理器,主要對Session和授權數據進行緩存

Realm: 領域,相當於數據源,通過Realm存取認證、授權相關數據

Cryptography: 密碼管理,提供一套加密/解密的組件,便於開發

登錄認證

1. 整合Shiro,添加Shiro所需要的pom依賴
<dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.2</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-nop</artifactId>
      <version>1.7.24</version>
    </dependency>
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
    </dependency>
    <!--    shiro核心jar包-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.4.0</version>
    </dependency>
    <!--    shiro整合到web工程所依賴的jar包-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>1.4.0</version>
    </dependency>
     <!--    shiro緩存依賴的jar包-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.4.0</version>
    </dependency>
       <!--    shiro整合與Spring的整合-->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.4.0</version>
    </dependency>
2. 在spring當中配置shiro過濾器和安全管理器
		 <!-- 配置shiro過濾器 -->
		    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		        <!--配置登錄認證的路徑
        			如果請求是login,則做去執行認證操作(調用對應的Realm執行認證操作);
       				 如果請求不是login請求,且沒有認證,則會直接執行login請求(下面貼有login請求對應的方法)
        		-->
        		<property name="loginUrl" value="/login"/>
		        <property name="securityManager" ref="securityManager"></property>
		
		        <!-- 配置shiro過濾器pattern -->
		        <property name="filterChainDefinitions">
		            <value>
		                /static/** = anon   <!--不需要登錄驗證-->
		                /login.jsp = anon   <!--不需要登錄驗證-->
		                /**=authc     <!--除指定請求外,其它所有的請求都需要身份驗證-->
		            </value>
		        </property>
		    </bean>
		
		    <!-- 配置shiro安全管理器 -->
		    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"></bean>

login請求對應的控制器方法

		@Controller
		public class LoginController {
    		@RequestMapping("/login")
    		public String login() {
        	return "redirect:login.jsp";
   			} 
		}
3. 在web.xml當中配置過濾器攔截所有請求,進行處理
 <!-- 攔截到所有請求,使用spring一個bean(ShiroFilter)來進行處理 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <!-- 是否filter中的init和 destroy-->
        <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>
4. 配置及編寫用於認證操作的Realm

把自己寫的自定義Realm交給Spring管理

	<!--自定義realm-->
    <!-- 配置realm數據源 -->
     <!-- class爲自己編寫的realm -->
    <bean id="employeeRealm" class="com.eh.web.realm.EmployeeRealm"></bean>

並將Realm配置到安全管理器中

    <!-- 配置shiro安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="employeeRealm"/>
<!--        注入緩存-->
        <property name="cacheManager" ref="ehCache"/>
    </bean>

自定義realm編寫認證操作

public class EmployeeRealm extends AuthorizingRealm {

    @Autowired
    private EmployeeService employeeService;
    //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("來到了認證");
        //獲取表單傳來的參數(表單傳的參數已經被封裝到token中)
        String username = (String) token.getPrincipal();
        System.out.println(username);
        //到數據庫中查詢有沒有當前用戶
        Employee employee = employeeService.getEmployeeWithName(username);
        System.out.println(employee);
        if (employee == null) {
            //沒有該用戶
            return null;
        }
        //認證
        //參數: 主體(當前用戶) 正確的密碼,鹽(此處省略沒寫,下面會單獨介紹),當前realm名稱
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(employee, employee.getPassword(), this.getName());
        //將認證信息返回(成功還是失敗) 定義一個過濾器,用來監聽該認證是否成功
        return info;
    }
 }
5. 編寫用於監聽認證是否成功的過濾器

將該過濾器交給Spring管理

	<!-- 配置自定義的監聽的過濾器-->
    <bean id="myFormFilter" class="com.eh.web.filter.MyFormFilter"></bean>
    
        <!-- 配置shiro過濾器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--配置登錄認證的路徑
        	如果請求是login,則做去執行認證操作(調用對應的Realm執行認證操作);
    		 如果請求不是login請求,且沒有認證,則會直接執行login請求(下面貼有login請求對應的方法)
   		-->
        <property name="loginUrl" value="/login"/>

		<!--將監聽的過濾器配置到shiro中-->
        <property name="filters">
            <map>
                <entry key="authc" value-ref="myFormFilter"/>
            </map>
        </property>

        <!-- 配置shiro安全管理器 -->
    	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
      		  <property name="realm" ref="employeeRealm"/>
    	</bean>
        <!-- 配置shiro過濾器pattern -->
        <property name="filterChainDefinitions">
            <value>
                /static/** = anon   <!--不需要登錄驗證-->
                /login.jsp = anon   <!--不需要登錄驗證-->
                /logout = logout   <!--取消認證-->
                /**=authc     <!--除指定請求外,其它所有的請求都需要身份驗證-->
            </value>
        </property>
    </bean>
//表單認證過濾器
public class MyFormFilter extends FormAuthenticationFilter {
	//攔截成功
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("認證成功");
        AjaxRes ajaxRes = new AjaxRes();
        ajaxRes.setSuccess(true);
        ajaxRes.setMsg("登錄成功");
        //把對象轉換成json類型的字符串(前端請求使用的ajax請求)
        String JsonString = new ObjectMapper().writeValueAsString(ajaxRes);
        //設置字符集編碼
        response.setCharacterEncoding("utf-8");
        //相應給瀏覽器
        response.getWriter().print(JsonString);
        //返回false  表示不需要執行下一個攔截器
        return false;
    }
    //攔截失敗
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        System.out.println("認證失敗");
        AjaxRes ajaxRes = new AjaxRes();
        //根據不同的異常相應不同的信息
        if (e != null) {
            //獲取異常名稱
            String name = e.getClass().getName();
            if (name.equals(UnknownAccountException.class.getName())) {
                //賬號錯誤
                ajaxRes.setSuccess(false);
                ajaxRes.setMsg("賬號不存在");
            } else if (name.equals(IncorrectCredentialsException.class.getName())) {
                //密碼錯誤
                ajaxRes.setSuccess(false);
                ajaxRes.setMsg("密碼錯誤");
            } else {
                //未知異常
                ajaxRes.setSuccess(false);
                ajaxRes.setMsg("未知錯誤");
            }
        }
        //設置字符集編碼
        response.setCharacterEncoding("utf-8");
        try {
            //把對象轉換成json類型的字符串
            String JsonString = new ObjectMapper().writeValueAsString(ajaxRes);
            response.setCharacterEncoding("utf-8");
            //相應給瀏覽器
            response.getWriter().print(JsonString);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        //返回false  表示不需要執行下一個攔截器
        return false;
    }
  }

瀏覽器請求及相應轉發代碼

$.post("login", $("form").serialize(), function (data) {
                    //把json格式的字符串轉換成json數據
                    data = $.parseJSON(data)
                    if (data.success) {
                        //成功
                        window.location.href = "${pageContext.request.contextPath}/index.jsp";
                    } else {
                        alert(data.msg);
                    }
                })

用戶退出功能的實現:
前端界面:

<a style="font-size: 18px; color: white;text-decoration: none;" href="${pageContext.request.contextPath}/logout">註銷</a>

在shiro過濾器中添加logout的處理

<!-- 配置shiro過濾器pattern -->
        <property name="filterChainDefinitions">
            <value>
                /static/** = anon   <!--不需要登錄驗證-->
                /login.jsp = anon   <!--不需要登錄驗證-->
                /logout = logout   <!--取消認證-->
                /**=authc     <!--除指定請求外,其它所有的請求都需要身份驗證-->
            </value>
        </property>

這樣整個認證登錄過程就實現實現了

用戶授權

授權方法調用的時機:
  1. 有shiro標籤的使用
<shiro:hasPermission name="employee:delete">
        <a href="#" class="easyui-linkbutton" data-options="iconCls:'icon-remove',plain:true" id="delete">離職</a>
    </shiro:hasPermission>
  1. 有權限(shiro)的註解
	@RequiresPermissions("employee:index")
    @RequestMapping("/employee")
    public String employee() {
        return "employee";
    }

注:在使用shiro註解時,需要在配置文件彙總添加shiro的註解掃描:

<!--
    配置爲true即使用cglib繼承的方式,
    false爲jdk的接口動態代理   控制器沒有實現接口
    -->
    <aop:config proxy-target-class="true" ></aop:config>

    <!-- 使用第三方去掃描shiro的註解 -->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor ">
        <property name="securityManager" ref="securityManager"></property>
    </bean>

授權方法

 //授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
        System.out.println("授權");
        Employee employee = (Employee) principal.getPrimaryPrincipal();
        //查詢角色和對應的權限
        List<String> roles = new ArrayList<>();
        List<String > permissions = new ArrayList<>();

        //判斷當前用戶是否是管理員, 如果是管理員則擁有所有權限
        if (employee.getAdmin()) {
            //擁有所有權限
            permissions.add("*:*");
        } else {
            //查詢角色
            roles = employeeService.getRolesById(employee.getId());
            System.out.println("------------------" + roles);
            //查詢權限
            permissions = employeeService.getPermissionsById(employee.getId());
            System.out.println("------------------" + permissions);
        }
        //賦給授權信息
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRoles(roles);
        info.addStringPermissions(permissions);
        return info;
    }

沒有權限結果處理,當沒有權限訪問某一頁面時,會報500錯誤,這時候可以定義一個處理沒有權限的方法

 	//未授權異常
    @ExceptionHandler(AuthorizationException.class)
    public void ExceptionHandler(HandlerMethod method, HttpServletResponse response) throws IOException {//拿取發生異常的方法
        //判斷當前請求是不是Json(AJAX)請求,如果是(則在該方法中無法實現跳轉),返回json數據給瀏覽器,讓其自己做跳轉
        //獲取發生異常的方法
        //獲取方法上的註解,通過方法上是否有@ResponseBody註解來判斷
        ResponseBody responseBody = method.getMethodAnnotation(ResponseBody.class);
        if (responseBody != null) {
            //則該請求爲(Ajax)json請求
            AjaxRes ajaxRes = new AjaxRes();
            ajaxRes.setMsg("沒有權限操作");
            ajaxRes.setSuccess(false);
            String s = new ObjectMapper().writeValueAsString(ajaxRes);
            response.setCharacterEncoding("utf-8");
            response.getWriter().print(s);
        } else {
            response.sendRedirect("nopermission.jsp");
        }
    }

這樣就實現了用戶授權

補充:

Shiro還有一些比較常用但前面還沒有介紹的業務,這裏補充說明一下

Shiro緩存的使用
  1. 所需要的pom依賴
<dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.2.2</version>
</dependency>
  1. 添加shiro緩存配置
    添加shiro-ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
	<defaultCache 
		maxElementsInMemory="1000" 
		maxElementsOnDisk="10000000"
		eternal="false" 
		overflowToDisk="false" 
		diskPersistent="false"
		timeToIdleSeconds="120"
		timeToLiveSeconds="120" 
		diskExpiryThreadIntervalSeconds="120"
		memoryStoreEvictionPolicy="LRU">
	</defaultCache>
</ehcache>
  1. 將該緩存配置添加到安全管理器(SecurityManager)中
    <!-- 配置shiro安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="employeeRealm"/>
		<!--注入緩存-->
        <property name="cacheManager" ref="ehCache"/>
    </bean>
Shiro密碼加密
  1. 添加憑證匹配器
<!-- 憑證匹配器 -->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <!-- 散列算法 -->
        <property name="hashAlgorithmName" value="md5"/>
        <!-- 散列次數 -->
        <property name="hashIterations" value="2"></property>
    </bean>

並將該憑證匹配器配置到自定義Realm(數據源)中

	<!--自定義realm-->
    <!-- 配置realm數據源 -->
    <bean id="employeeRealm" class="com.eh.web.realm.EmployeeRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>
  1. 在保存用戶時, 給用戶密碼進行加密處理
	@Override
    public void saveEmployee(Employee employee) {
        //把密碼進行加密,把該用戶名當做鹽 散列次數2
        Md5Hash md5Hash = new Md5Hash(employee.getPassword(), employee.getUsername(), 2);
        employee.setPassword(md5Hash.toString());
        //保存員工
        employeeMapper.insert(employee);
        //保存關係表
        for (Role role : employee.getRoles()) {
            employeeMapper.insertEmployeeAndRoleRel(employee.getId(), role.getRid());
        }
    }

  1. 認證時,添加密碼處理
 		//參數 正確的密碼,鹽,當前realm名稱
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(employee, employee.getPassword(), ByteSource.Util.bytes(employee.getUsername()), this.getName());
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章