SpringMVC+Spring+Hibernate+Mybatis+Shiro等整合及開發(3)

shiro簡介

    一 Apache Shiro 是 Java 的一個安全框架。目前,使用 Apache Shiro 的人越來越多,因爲它相當簡單,對比 Spring Security,可能沒有 Spring Security 做的功能強大,但是在實際工作時可能並不需要那麼複雜的東西,所以使用小而簡單的 Shiro 就足夠了。對於它倆到底哪個好,這個不必糾結,能更簡單的解決項目問題就好了。

    功能內部圖:



Subject:主體,可以看到主體可以是任何可以與應用交互的 “用戶”;

SecurityManager:相當於 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心臟;所有具體的交互都通過 SecurityManager 進行控制;它管理着所有 Subject、且負責進行認證和授權、及會話、緩存的管理。

Authenticator:認證器,負責主體認證的,這是一個擴展點,如果用戶覺得 Shiro 默認的不好,可以自定義實現;其需要認證策略(Authentication Strategy),即什麼情況下算用戶認證通過了;

Authrizer:授權器,或者訪問控制器,用來決定主體是否有權限進行相應的操作;即控制着用戶能訪問應用中的哪些功能;

Realm:可以有 1 個或多個 Realm,可以認爲是安全實體數據源,即用於獲取安全實體的;可以是 JDBC 實現,也可以是 LDAP 實現,或者內存實現等等;由用戶提供;注意:Shiro 不知道你的用戶 / 權限存儲在哪及以何種格式存儲;所以我們一般在應用中都需要實現自己的 Realm;

SessionManager:如果寫過 Servlet 就應該知道 Session 的概念,Session 呢需要有人去管理它的生命週期,這個組件就是 SessionManager;而 Shiro 並不僅僅可以用在 Web 環境,也可以用在如普通的 JavaSE 環境、EJB 等環境;所有呢,Shiro 就抽象了一個自己的 Session 來管理主體與應用之間交互的數據;這樣的話,比如我們在 Web 環境用,剛開始是一臺 Web 服務器;接着又上了臺 EJB 服務器;這時想把兩臺服務器的會話數據放到一個地方,這個時候就可以實現自己的分佈式會話(如把數據放到 Memcached 服務器);

SessionDAO:DAO 大家都用過,數據訪問對象,用於會話的 CRUD,比如我們想把 Session 保存到數據庫,那麼可以實現自己的 SessionDAO,通過如 JDBC 寫到數據庫;比如想把 Session 放到 Memcached 中,可以實現自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 進行緩存,以提高性能;

CacheManager:緩存控制器,來管理如用戶、角色、權限等的緩存的;因爲這些數據基本上很少去改變,放到緩存中後可以提高訪問的性能

Cryptography:密碼模塊,Shiro 提高了一些常見的加密組件用於如密碼加密 / 解密的。


二 再來看一個shiro認證流程:

    首先發出驗證請求給Subject.login(用戶名密碼),Subject委託給SecurityManager驗證,SecurityManager要拿到比較的對象,就向Realm索取,Realm是一般是自己定義的,我是從數據庫裏面取的。取出來返回回去,進行對比。



PS:上面有一部分內容摘自網上。

    經過上面的介紹知道,要實現簡單的shiro應用我們着重關注:Realm數據獲取。

但是我想實現的是基於url的權限控制;又自定義了鑑權filter。

    三.spring和shiro整合並自定義鑑權和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"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
		http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
		http://www.springframework.org/schema/mvc
		http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop
		http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/tx
		http://www.springframework.org/schema/tx/spring-tx-4.3.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- Shiro生命週期處理器-->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />

    <!--shiro web過濾bean-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 -->
        <property name="loginUrl" value="/login.do" />
        <!-- 認證成功統一跳轉到first.action,建議不配置,shiro認證成功自動到上一個請求路徑 -->
        <!-- <property name="successUrl" value="/first.action"/> -->
        <!-- 通過unauthorizedUrl指定沒有權限操作時跳轉頁面-->
        <property name="unauthorizedUrl" value="/refuse.jsp" />

        <property name="filters">
            <util:map>
                <!--權限控制自定義filter-->
                <entry key="perms" value-ref="urlAuthorizationFilter" />
            </util:map>
        </property>

        <!-- 過慮器鏈定義,從上向下順序執行,一般將/**放在最下邊 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- 對靜態資源設置訪問,不然都攔截了 -->
                /images/** = anon
                /js/** = anon
                /css/** = anon
                /dologin* = anon

                <!-- 請求logout.action地址,shiro清除session -->
                /logout.do=logout
                <!-- /** = authc所有url都可以認證通過才能訪問 -->
                /a.do = perms
                /** = authc
                <!-- /** = anon所有url都可以匿名訪問 -->
                <!--/** = anon-->
            </value>
        </property>
    </bean>

    <!--自定義權限控制filter-->
    <bean id="urlAuthorizationFilter" class="com.yanghs.shiro.filter.UrlAuthorizationFilter"/>

    <!-- securityManager安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!--自定義用戶信息獲取器-->
        <property name="realm" ref="userRealm" />
        <!--session管理器-->
        <property name="sessionManager" ref="sessionManager"/>
        <!-- 注入緩存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>

    </bean>

    <!-- 自定義的realm -->
    <bean id="userRealm" class="com.yanghs.shiro.realm.UserRealm">
        <!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 -->
        <!--<property name="credentialsMatcher" ref="credentialsMatcher"/>-->
        <!--開啓用戶緩存-->
        <property name="authenticationCachingEnabled" value="false"/>
        <!--用戶緩存-->
        <!--<property name="authenticationCacheName" value=""/>-->
        <!--開啓權限緩存-->
        <property name="authorizationCachingEnabled" value="true"/>
        <!--權限緩存-->
        <property name="authorizationCacheName" value="authenticationCache"/>
    </bean>

    <!-- 散列加鹽憑證匹配器 -->
    <!--<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="md5" />
        <property name="hashIterations" value="1" />
    </bean>-->

    <!-- 緩存管理器 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
    </bean>

    <!--session管理器-->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!--多久檢查session 的有效性 1800000毫秒 半小時-->
        <property name="sessionValidationInterval" value="1800000"/>
        <!--session 有效時間-->
        <property name="globalSessionTimeout" value="1800000"/>

        <!--session 監聽器 可以設置多個-->
        <property name="sessionListeners">
            <util:list>
                <ref bean="sessionListener"/>
            </util:list>
        </property>
        <!-- 間隔多少時間檢查,不配置是60分鐘 -->
        <!--<property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>-->
        <!-- 是否開啓 檢測,默認開啓 -->
        <property name="sessionValidationSchedulerEnabled" value="true"/>
        <!-- 是否刪除無效的,默認也是開啓 -->
        <property name="deleteInvalidSessions" value="true"/>
        <!-- 會話Cookie模板 -->
        <property name="sessionIdCookie" ref="simpleCookie"/>

    </bean>



    <!--session監聽器-->
    <bean id="sessionListener" class="com.yanghs.shiro.listener.MySessionListener"></bean>

    <!--會話session id 生成器-->
    <bean id="sessionIdGenerator" class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>

    <!--會話Cookie模板-->
    <bean id="simpleCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!--cookie name-->
        <constructor-arg value="JSESSIONID"/>
        <!--如果設置爲 true,則客戶端不會暴露給客戶端腳本代碼,使用 HttpOnly cookie
            有助於減少某些類型的跨站點腳本攻擊;
            此特性需要實現了 Servlet 2.5 MR6 及以上版本的規範的 Servlet 容器支持;-->
        <property name="httpOnly" value="true"/>
        <!--cookie的最大時間 關閉瀏覽器失效 設置時間的話爲秒-->
        <property name="maxAge" value="-1"/>
    </bean>


</beans>

自定義Realm

package com.yanghs.shiro.realm;

import com.yanghs.common.entity.hbm.*;
import com.yanghs.common.service.IRoleService;
import com.yanghs.common.service.IUserService;
import com.yanghs.shiro.token.UserToken;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.Iterator;

/**
 * @author yanghs
 * @Description:自定義數據獲取
 * @date 2018/3/1 13:57
 */
public class UserRealm extends AuthorizingRealm {
    @Resource(name = "userService")
    IUserService userService;
    @Resource(name = "roleService")
    IRoleService roleService;
    /**
     * 授權邏輯獲取
     * @param principalCollection
     * @return
     */
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Userinfo userinfo = (Userinfo) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo sa = new SimpleAuthorizationInfo();
        try {
            Collection<Authority> authorityCollection = roleService.getRoleByUser(userinfo);
            Iterator<Authority> authorityIterator = authorityCollection.iterator();
            while (authorityIterator.hasNext()){
                Authority authority = authorityIterator.next();
                sa.addStringPermission(authority.getUrl());
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return sa;
    }

    /**
     * 用戶登錄邏輯實現
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UserToken token = (UserToken) authenticationToken;
        Userinfo userInfo = null;
        try {
            userInfo =  userService.getUser(new Userinfo(null,token.getUsername(),null,null));
        } catch (Exception e) {
            e.printStackTrace();
        }
        if(userInfo == null){
            throw new AccountException("用戶不存在");
        }
        if(!userInfo.getPassword().equals(token.getPwd())){
            throw new AccountException("用戶密碼錯誤");
        }
        AuthenticationInfo authcinfo = new SimpleAuthenticationInfo(userInfo,userInfo.getPassword(),this.getName());
        return  authcinfo;

    }

    /**
     * 清除認證緩存
     */
    public void clearCachedAuthenticationInfo() {
        super.clearCachedAuthenticationInfo(SecurityUtils.getSubject().getPrincipals());
    }

    /**
     * 清除權限緩存
     */
    public void clearCachedAuthorizationInfo() {
        super.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
    }
}

鑑權filter

package com.yanghs.shiro.filter;

import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * @author yanghs
 * @Description:重寫權限控制filter讓其支持url權限控制
 * @date 2018/3/5 14:06
 */
public class UrlAuthorizationFilter extends PermissionsAuthorizationFilter {
    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String path =  httpServletRequest.getContextPath();
        String uri = httpServletRequest.getRequestURI();
        uri =  uri.substring(path.length(),uri.length());

        String[] perms = new String[1];
        perms[0] = uri;
        return super.isAccessAllowed(request, response, perms);
    }
}





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