shiro配置


在web.xml中配置shiro的filter

在web系統中,shiro也通過filter進行攔截,filter攔截器后將操作權交給Spring中配置的filterChain(過濾器鏈),shiro提供很多filter。要使用代理filter類DelegatingFilterProxy

<!-- shiro的filter -->
    <!-- shiro過濾器,DelegatingFilterProxy通過代理模式將Spring容器中的bean和filter關聯起來 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <!-- 設置targetFilterLifecycle為true 由servlet控制filter生命周期 -->
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
        <!-- 設置Spring容器filter的bean id,如果不設置則在Spring注冊的bean中查找與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>

applicationContext-shiro.xml

<!-- 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.action"/>
        <!-- 認證成功統一跳轉到first.action,建議不配置,默認情況下,shiro認證成功后自動跳轉上一個請求路徑 -->
        <property name="successUrl" value="/first.action"/>
        <!-- 通過unauthorizedUrl 指定沒有權限操作時的跳轉頁面 -->
        <property name="unauthorizedUrl" value="/refuse.jsp"/>
        <!-- 過濾器鏈定義,從上向下執行,一般將/**放在最下邊 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- 退出攔截,請求logout.action執行退出操作 shiro自動清除Session-->
                /logout.action = logout
                <!-- 無權訪問頁面 anon表示可以匿名訪問 -->
                /refuse.jsp = anon
                <!-- 驗證碼可以匿名訪問 -->
                /validatecode.jsp = anon
                <!-- perms[xx] 表示有xx權限才可以訪問 -->
                /item/queryItem.action = perms[item:query]
                /item/editItem.action = perms[item:edit]
                <!-- 對靜態資源設置匿名訪問 -->
                /js/** = anon
                /images/** = anon
                /styles/** = anon
                
                <!-- /** = authc 表示所有的URL都必須認真通過才可以進行訪問-->
                /** = authc    
            </value>        
        </property>
    </bean>
    
    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm" />
    </bean>
    
    <!-- 自定義Realm -->
    <bean id="customRealm" class="liuxun.ssm.shiro.CustomRealm"/>

第二中加入cas配置------applicationContext-shiro.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:context="http://www.springframework.org/schema/context"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="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.xsd
                               http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
                               http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd

                               http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                               http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd"
       default-lazy-init="false">
    <description>Shiro公共配置</description>
    
    <bean id="shiroCasRealm" class="com.dousnl.framework.security.ShiroRealm">
        <property name="cachingEnabled" value="true" />
        <property name="authenticationCachingEnabled" value="true" />
        <property name="authenticationCacheName" value="authenticationCache" />
        <!-- cas  integration start-->
        <!-- 配置cas服務器地址 -->
        <property name="casServerUrlPrefix" value="http://${sso.server.url}/cas" />
        <!-- 客戶端的回調地址設置,必須和上面的shiro-cas過濾器casFilter攔截的地址一致 -->
        <property name="casService" value="http://${shxm.server.url}/index/success" />
        <!-- cas integration end-->
        <!-- <property name="userService" ref="userService" />
        <property name="userAuthService" ref="userAuthService" /> -->
    </bean>
    <bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" />
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="rememberMe" />
        <property name="httpOnly" value="true" />
        <property name="maxAge" value="604800" /><!-- 保存7天 --><!-- 單位為秒 --><!-- 最小為30分鍾 -->
    </bean>
    <!-- rememberMe管理器 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cipherKey"
                  value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}" />
        <property name="cookie" ref="rememberMeCookie" />
    </bean>
    <!-- 會話管理器 -->
    <bean id="sessionManager"
          class="org.apache.shiro.web.session.mgt.ServletContainerSessionManager">
    </bean>
    <!-- 用戶授權信息Cache, 采用EhCache -->
    <!-- 緩存管理器 使用Ehcache實現 -->  
    <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache/ehcache-shiro.xml"/>
    </bean>
    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="shiroCasRealm" />
        <!-- 解決sessionid問題  本地化 -->
        <property name="sessionMode" value="native"/>
        
        <property name="cacheManager" ref="shiroEhcacheManager" />
        <property name="sessionManager" ref="sessionManager" />
        <property name="rememberMeManager" ref="rememberMeManager" />
        <property name="subjectFactory" ref="casSubjectFactory" />
    </bean>
    
    <!-- shiro-cas登錄過濾器 -->
    <bean id="casFilter" class="org.apache.shiro.cas.CasFilter">
        <!-- 配置驗證錯誤時的失敗頁面 ,這里配置為登錄頁面 -->
        <property name="failureUrl" value="http://${sso.server.url}/cas/login?service=http://${shxm.server.url}/login" />
    </bean>
    <!-- 退出登錄過濾器 -->
    <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter">
        <property name="redirectUrl" value="http://${sso.server.url}/cas/logout?service=http://${shxm.server.url}/login" />
    </bean>
    
    <!-- Shiro生命周期處理器 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
    
    <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
        <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" />
        <property name="arguments" ref="securityManager" />
    </bean>
    
    <!-- 后台配置管理 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <!-- cas  integration start-->
        <!-- 設定角色的登錄鏈接,這里為cas登錄頁面的鏈接可配置回調地址 -->
        <property name="loginUrl" value="http://${sso.server.url}/cas/login?service=http://${shxm.server.url}/index/success" />
        <!-- cas integration end-->
        <property name="successUrl" value="/index/success" />
        <!-- <property name="unauthorizedUrl" value="/admin/unauthorized" /> -->
        <property name="filters">
            <util:map>
                <!-- <entry key="authc" value-ref="captchaAuthenticationFilter" />
                <entry key="logout" value-ref="adminLogoutFilter" />
                <entry key="session" value-ref="onlineSessionFilter" /> -->
                <entry key="casFilter" value-ref="casFilter" />
                <entry key="logout" value-ref="logoutFilter" />
            </util:map>
        </property>
        <property name="filterChainDefinitions">
            <value>
                /index/success = casFilter
                /static/** = anon
                /logout = logout
                /** = user
                <!-- /admin/** =session,user
                /shiro-cas = user
                /fieldDef/** = user
                /mouldClass/** = user
                /mouldConfig/** = user
                /mouldDatil/** = user
                /mouldDef/** = user
                /check/** = user
                /expenseAppForm/** = user
                /reportForm/** = user
                /bzCheckController/** = user -->
            </value>
        </property>
        <!-- spel語言 -->
        <!-- <property name="filterChainDefinitionMap"
                  value="#{filterChainDefinitionService.loadFilterChains()}" /> -->
    </bean>
</beans>
 
securityManager:這個屬性是必須的
loginUrl:沒有登錄認證的用戶請求將跳轉到此地址進行認證,不是必須的屬性,不輸入地址的話會自動尋找web項目的根目錄下的"login.jsp" 頁面
 

自定義Realm模擬測試

此Realm先不從數據庫查詢權限數據,當前需要先將shiro整合完成。
 
package liuxun.ssm.shiro;
 
import java.util.ArrayList;
import java.util.List;
 
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
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 liuxun.ssm.po.ActiveUser;
import liuxun.ssm.po.SysPermission;
 
public class CustomRealm extends AuthorizingRealm {
 
    // 設置Realm名稱
    @Override
    public void setName(String name) {
        super.setName("CustomRealm");
    }
 
    // 支持UsernamePasswordToken
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }
 
    // 用於認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 從token中獲取用戶身份信息
        String username = (String) token.getPrincipal();
        // 拿着username從數據庫中進行查詢
        // ....
        // 如果查詢不到返回null
        if (!username.equals("zhangsan")) {
            return null;
        }
 
        // 獲取從數據庫查詢出來的用戶密碼
        String password = "123"; // 這里使用靜態數據進行測試
 
        // 根據用戶id從數據庫中取出菜單
        // ...先使用靜態數據
        List<SysPermission> menus = new ArrayList<SysPermission>();
        SysPermission sysPermission_1 = new SysPermission();
        sysPermission_1.setName("商品管理");
        sysPermission_1.setUrl("/item/queryItem.action");
        SysPermission sysPermission_2 = new SysPermission();
        sysPermission_2.setName("用戶管理");
        sysPermission_2.setUrl("/user/query.action");
 
        menus.add(sysPermission_1);
        menus.add(sysPermission_2);
 
        // 構建用戶身份信息
        ActiveUser activeUser = new ActiveUser();
        activeUser.setUserid(username);
        activeUser.setUsername(username);
        activeUser.setUsercode(username);
        activeUser.setMenus(menus);
 
        // 返回認證信息由父類AuthenticationRealm進行認證
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password,
                this.getName());
 
        return simpleAuthenticationInfo;
    }
 
    // 用於授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //獲取身份信息
        ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
        //用戶id
        String userid = activeUser.getUserid();
        // 根據用戶id從數據庫中查詢權限數據
        // ...這里使用靜態數據模擬
        List<String> permissions = new ArrayList<String>();
        permissions.add("item:query");
        permissions.add("item:update");
        
        //將權限信息封裝為AuthorizationInfo
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //基於資源權限的訪問控制
        for (String permission : permissions) {
            simpleAuthorizationInfo.addStringPermission(permission);
        }
        // 如果基於角色進行訪問控制
        // for (String role : roles) {
        // simpleAuthorizationInfo.addRole(role);
        // }
        
        return simpleAuthorizationInfo;
    }
 
}

登錄

(1) 登錄原理
使用FormAuthenticationFilter過濾器實現,原理如下:
當用戶沒有認證時,請求loginUrl進行認證,用戶身份和用戶密碼提交數據到loginUrl,FormAuthorizationFilter攔截去除request中的username和password(兩個參數名稱是可以配置的),FormAuthorizationFilter調用Realm傳入一個token(username和password),realm認證時根據username查詢用戶信息(在ActiveUser中存儲,包括userid、usercode、username、menus) 如果查詢不到,Realm返回null,FormAuthorizationFilter向request域中填充了一個參數"shiroLoginFailure" 記錄了異常信息。
(2) 登錄頁面
由於FormAuthorizationFilter的用戶身份和密碼的input的默認值(username和password),修改頁面中賬號和密碼的input的name屬性為username和password。
(3)控制器登錄代碼的實現,如下:
---------------------

//用戶登錄提交方法
    @RequestMapping("/login")
    public String login(HttpServletRequest request) throws Exception{
       
        //shiro在認證通過后出現錯誤后將異常類路徑通過request返回
        //如果登陸失敗從request中獲取認證異常信息,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 {
                throw new Exception(); //最終在設置的異常處理器中生成未知錯誤
            }
        }
        //此方法不處理登錄成功(認證成功)的情況
        //如果登錄失敗還到login頁面
        return "login";
    }

首頁

1.認證成功后用戶菜單在首頁顯示(從activeUser獲取)
2.認證后用戶的信息在頁頭顯示(從activeUser獲取)
由於session由shiro管理,需要修改首頁的controller方法,將session中的數據通過model傳到頁面
//系統首頁
    @RequestMapping("/first")
    public String first(Model model)throws Exception{
        //主體
        Subject subject = SecurityUtils.getSubject();
        //身份
        ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
        model.addAttribute("activeUser", activeUser);
        return "/first";
    }

 

退出

由於使用shiro的sessionManager,不用開發退出功能,使用shiro的logout攔截器即可。也就是說不用我們去實現退出,只要去訪問一個退出的url(該 url是可以不存在),由LogoutFilter攔截住,清除session。所以可以屏蔽退出相關的控制器代碼。
<!-- 退出攔截,請求logout.action執行退出操作 -->
/logout.action = logout
無權限頁面refuse.jsp

當用戶無操作權限,shiro將跳轉到refuse.jsp頁面。

<!-- 通過unauthorizedUrl 指定沒有權限操作時的跳轉頁面 -->  
<property name="unauthorizedUrl" value="/refuse.jsp"/>
授權過濾器測試

使用PermissionsAuthorizationFilter
在applicationContext-shiro.xml中配置url所對應的權限。
測試流程:
1、在applicationContext-shiro.xml中配置filter規則
<!--商品查詢需要商品查詢權限  -->
/item/queryItem.action = perms[item:query]
2、用戶在認證通過后,請求/items/queryItems.action
3、被PermissionsAuthorizationFilter攔截,發現需要“item:query”權限
4、PermissionsAuthorizationFilter調用realm中的doGetAuthorizationInfo獲取數據庫中正確的權限
5、PermissionsAuthorizationFilter對item:query 和從realm中獲取權限進行對比,如果“item:query”在realm返回的權限列表中,授權通過。如果授權失敗,跳轉到refuse.jsp
頁面如下:
---------------------

問題總結

1、在applicationContext-shiro.xml中配置過慮器鏈接,需要將全部的url和權限對應起來進行配置,比較發麻不方便使用。
2、每次授權都需要調用realm查詢數據庫,對於系統性能有很大影響,可以通過shiro緩存來解決。
shiro過濾器總結

過濾器簡稱 對應的java類
anon            org.apache.shiro.web.filter.authc.AnonymousFilter
authc           org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasic  org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
perms          org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
port              org.apache.shiro.web.filter.authz.PortFilter
rest              org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
roles            org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
ssl                org.apache.shiro.web.filter.authz.SslFilter
user             org.apache.shiro.web.filter.authc.UserFilter
logout          org.apache.shiro.web.filter.authc.LogoutFilter
過濾器配置示例詳解:
anon:例如/admins/**=anon  沒有參數,表示可以匿名訪問。
authc:例如/admins/user/**=authc 表示需要認證(登錄)才能使用,FormAuthenticationFilter是表單認證,沒有參數。
roles:例如/admins/user/**=roles[admin],參數可以寫多個,多個時必須加引號,並且參數之間用逗號隔開,,當有多個參數時,例如/admin/user/**=roles["admin,geust"],每個參數通過才算通過,相當於hasAllRoles()方法。
perms:例如/admins/user/**=perms[user:add:*],參數可以寫多個,多個時必須加引號,並且參數之間用逗號分隔,例如/admins/user/**=perms["user:add:*,user:modiffy:*"],當有多個參數時必須每個參數都通過才算通過,相當於isPermittedAll()方法。
rest:例如/admins/user/**=rest[user],根據請求的方法,相當於/admins/user/**=perms[user:method],其中method為post,get,put,delete等 rest 即restful,就是用於對restful風格的設計進行權限管理。
port:例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置里port的端口,queryString是你訪問的url里的?后面的參數。
authcBasic:例如/admins/user/**=authcBasic沒有參數表示httpBasic認證
ssl:例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議為https
user:例如/admins/user/**=user沒有參數表示必須存在用戶, 身份認證通過或通過記住我認證通過的可以訪問,當登入操作時不做檢查
注意:anon、authcBasic、authc、user是認證過濾器,perms、roles、ssl、rest、port是授權過濾器
URL表達式說明:
(1)URL目錄是基於HttpServletRequest.getContextPath()此目錄設置。
(2)URL可以使用通配符,** 代表任意子目錄
(3)shiro驗證URL時,URL匹配成功便不再不再繼續匹配查找。所以要注意配置文件中的URL順序,尤其是在使用通配符時。
過濾器鏈定義說明:
(1)一個URL可以配置多個Filter,使用逗號隔開。
(2)當設置多個過濾器時,全部驗證通過,才視為通過。
(3)部分過濾器可以指定參數,如perms、roles

認證

需求

修改realm的doGetAuthenticationInfo方法,從數據庫中查詢用戶,realm返回的用戶信息中包括(MD5加密后的串和salt),實現讓shiro進行散列串的校驗。
添加憑證匹配器

添加憑證匹配器實現MD5加密校驗,修改applicationContext-shiro.xml 修改內容如下:

<!-- 自定義Realm -->
    <bean id="customRealm" class="liuxun.ssm.shiro.CustomRealm">
        <!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 -->
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>
    
    <!-- 憑證匹配器 -->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="md5"/>
        <property name="hashIterations" value="1"/>
    </bean>

修改realm的認證方法

修改realm代碼從數據庫中查詢用戶身份信息,將sysService注入realm。

public class CustomRealm extends AuthorizingRealm {
    @Autowired
    private SysService sysService;
 
    // 設置Realm名稱
    @Override
    public void setName(String name) {
        super.setName("CustomRealm");
    }
 
    // 支持UsernamePasswordToken
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }
 
    // 用於認證(從數據庫中查詢用戶信息)
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 從token中獲取用戶身份信息
        String userCode = (String) token.getPrincipal();
        
        SysUser sysUser = null;
        try {
            sysUser = sysService.findSysUserByUserCode(userCode);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        //如果賬號不存在則返回null
        if (sysUser == null) {
            return null;
        }
        
        //根據用戶id取出菜單
        List<SysPermission> menus = null;
        try {
            menus = sysService.findMenuListByUserId(sysUser.getId());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //用戶密碼
        String password = sysUser.getPassword();
        //
        String salt = sysUser.getSalt();
        
        //構建用戶身份信息
        ActiveUser activeUser = new ActiveUser();
        activeUser.setUserid(sysUser.getId());
        activeUser.setUsername(sysUser.getUsername());
        activeUser.setUsercode(sysUser.getUsercode());
        activeUser.setMenus(menus);
        
        //將activeUser設置simpleAuthenticationInfo
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password,
                ByteSource.Util.bytes(salt), this.getName());
        return simpleAuthenticationInfo;
    }
        ......
 
}

授權

修改realm授權方法

修改realm代碼中的doGetAuthorizationInfo方法代碼從數據庫中查詢權限信息(已經注入sysService)
 
// 用於授權(從數據庫中查詢授權信息)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //獲取身份信息
        ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
        //用戶id
        String userid = activeUser.getUserid();
        //獲取用戶權限
        List<SysPermission> permissionsList = null;
        try {
            permissionsList = sysService.findPermissionListByUserId(userid);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //構建shiro授權信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //單獨定義一個集合
        List<String> permissions = new ArrayList<String>();
        for (SysPermission sysPermission : permissionsList) {
            //將數據庫中的權限標簽放入集合
            permissions.add(sysPermission.getPercode());
        }
        simpleAuthorizationInfo.addStringPermissions(permissions);
        
        return simpleAuthorizationInfo;
    }

對controller開啟AOP

在springmvc.xml中配置shiro注解支持,可在controller方法中使用shiro注解配置權限
 
<!-- 開啟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>

權限注解控制

商品查詢的controller方法添加權限(item:query)

// 查詢商品列表信息
    @RequestMapping("/queryItem")
    @RequiresPermissions("item:query")
    public ModelAndView queryItems(HttpServletRequest request) throws Exception {
上邊代碼中@RequiresPermissions("item:query")表示必須具有"item:query"權限方可執行。
同理,商品修改controller方法添加權限(item:update)
    // 方法返回字符串,字符串就是邏輯視圖名,Model作用就是將數據填充到request域,在頁面展示
      @RequestMapping(value="/editItem",method={RequestMethod.GET}) 
      @RequiresPermissions("item:update")
      public String editItems(Model model,Integer id) throws Exception{

商品修改提交

// 商品修改提交
    // itemsQueryVo是包裝類型的pojo
     @RequestMapping("/editItemSubmit")
     @RequiresPermissions("item:update")
         //注意:每個校驗pojo的前邊必須加@Validated, 每個校驗的pojo后邊必須加BindingResult接收錯誤信息
    public String editItemSubmit(Model model,Integer id, 
            @Validated(value={ValidGroup1.class}) @ModelAttribute(value="item")ItemsCustom itemsCustom,
            BindingResult bindingResult,
            // 上傳圖片
            MultipartFile pictureFile
            ) throws Exception {

JSP標簽控制

shiro標簽介紹

Jsp頁面添加:
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>
標簽名稱 標簽條件(均是顯示標簽內容)
<shiro:authenticated>          登錄之后
<shiro:notAuthenticated>    不在登錄狀態時
<shiro:guest>                       用戶在沒有RememberMe時
<shiro:user>                         用戶在RememberMe時
<shiro:hasAnyRoles name="abc,123" >    在有abc或者123角色時
<shiro:hasRole name="abc">                    擁有角色abc
<shiro:lacksRole name="abc">                  沒有角色abc
<shiro:hasPermission name="abc">          擁有權限資源abc
<shiro:lacksPermission name="abc">        沒有abc權限資源
<shiro:principal>                                         顯示用戶身份名稱
<shiro:principal property="username"/>     顯示用戶身份中的屬性值
jsp頁面添加標簽

如果有商品修改權限 頁面顯示"修改鏈接"

<td>
       <!-- 有item:update權限才顯示修改鏈接,沒有權限則不顯示相當於if(hasPermission(item:update)) -->
       <shiro:hasPermission name="item:update">
       <a href="${pageContext.request.contextPath }/item/editItem.action?id=${item.id}">修改</a>
       </shiro:hasPermission>
    </td>

授權測試

(1) 當調用controller的一個方法,由於該 方法加了@RequiresPermissions("item:query") ,shiro調用realm獲取數據庫中的權限信息,看"item:query"是否在權限數據中存在,如果不存在就拒絕訪問,如果存在就授權通過。
(2) 當展示一個jsp頁面時,頁面中如果遇到<shiro:hasPermission name="item:update">,shiro調用realm獲取數據庫中的權限信息,看item:update是否在權限數據中存在,如果不存在就拒絕訪問,如果存在就授權通過。
問題:只要遇到注解或jsp標簽的授權,都會調用realm方法查詢數據庫,需要使用緩存解決此問題。

shiro緩存

shiro每次授權都會通過realm獲取權限信息,為了提高訪問速度需要添加緩存,第一次從realm中讀取權限數據,之后不再讀取,這里Shiro和Ehcache整合。

添加Ehcache的jar包

配置cacheManager

在applicationContext-shiro.xml中配置緩存管理器。
    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm" />
        <!-- 注入緩存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>
    </bean>
        
    <!-- 緩存管理器 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/>
    </bean>

配置shiro-ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!--diskStore:緩存數據持久化的目錄 地址  -->
    <diskStore path="/Users/liuxun/Desktop/ehcache" />
    <defaultCache 
        maxElementsInMemory="1000" 
        maxElementsOnDisk="10000000"
        eternal="false" 
        overflowToDisk="false" 
        diskPersistent="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120" 
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

清空緩存

如果用戶正常退出,緩存自動清空。如果用戶非正常退出,緩存自動清空。
如果修改了用戶的權限,而用戶不退出系統,舊的權限數據緩存在服務器,讀取仍先從緩存獲取權限數據,修改的權限無法立即生效。
需要手動進行編程實現:
在權限修改后調用realm的clearCache方法清除緩存。
下邊的代碼正常開發時要放在service中調用。
在service中,權限修改后調用realm的方法。
在自定義的Realm中定義clearCached方法

//清空緩存
    public void clearCached(){
        //清空所有用戶的身份緩存信息
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
        super.clearCache(principals);
    }

在權限修改后調用realm中的方法,realm已經由spring管理,所以從spring中獲取realm實例,調用clearCached方法。
測試清除緩存controller方法

@Controller
public class ClearShiroCache {
 
    //注入realm
    @Autowired
    private CustomRealm customRealm;
    
    @RequestMapping("/clearShiroCache")
    public String clearShiroCache(){
        //清除緩存,如果按照標准寫法是在Service中調用customRealm.clearCached();
        customRealm.clearCached();
        return "success";
    }
}

session管理

在applicationContext-shiro.xml中配置sessionManager,修改的關鍵代碼如下:
        <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm" />
        <!-- 注入緩存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>
        <!-- 注入Session管理器 -->
        <property name="sessionManager" ref="sessionManager"/>
    </bean>
    <!-- 會話管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- Session的失效時長,單位:毫秒 -->
        <property name="globalSessionTimeout" value="600000"/>
        <!-- 刪除失效的Session -->
        <property name="deleteInvalidSessions" value="true"/>
    </bean>

驗證碼

思路

shiro使用FormAuthenticationFilter進行表單認證,驗證校驗的功能應該加在FormAuthenticationFilter中,在認證之前進行驗證碼校驗。
需要寫FormAuthenticationFilter的子類,繼承FormAuthenticationFilter,改寫它的認證方法,在認證之前進行驗證碼校驗。
自定義FormAuthenticationFilter

需要在驗證賬號和名稱之前校驗驗證碼

public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
 
    //原FormAuthenticationFilter的認證方法
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //在這里進行驗證碼的校驗
        
        //從Session中獲取正確的驗證碼
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpSession session = httpServletRequest.getSession();
        //取出Session中的驗證碼(正確的驗證碼)
        String validateCode = (String) session.getAttribute("validateCode");
        
        //取出頁面的驗證碼
        //輸入的驗證和session中的驗證進行對比 
        String randomcode = httpServletRequest.getParameter("randomcode");
        if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
            //如果校驗失敗,將驗證碼錯誤失敗信息,通過shiroLoginFailure設置到request中
            httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
            //拒絕訪問,不再校驗賬號和密碼 
            return true; 
        }
        return super.onAccessDenied(request, response);
    }
 
}

 

配置FormAuthenticationFilter

修改applicationContext-shiro.xml中對FormAuthenticationFilter的配置
(1) 在shiroFilter 中添加filters屬性
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 -->
        <property name="loginUrl" value="/login.action"/>
        <!-- 認證成功統一跳轉到first.action,建議不配置,默認情況下,shiro認證成功后自動跳轉上一個請求路徑 -->
        <property name="successUrl" value="/first.action"/>
        <!-- 通過unauthorizedUrl 指定沒有權限操作時的跳轉頁面 -->
        <property name="unauthorizedUrl" value="/refuse.jsp"/>
        <!-- 自定義filter配置 -->
        <property name="filters">
            <map>
                <!-- 將自定義的FormAuthenticationFilter注入shiroFilter -->
                <entry key="authc" value-ref="authenticationFilter"/>
            </map>
        </property>
                ......

(2)formAuthenticationFilter定義

<!-- 自定義form認證過濾器 -->
    <!-- 基於Form表單的身份認證過濾器,即使不配置也會注冊此過濾器,表單中的用戶賬號、密碼及loginurl將采用默認值,建議配置 -->
    <bean id="authenticationFilter" class="liuxun.ssm.shiro.CustomFormAuthenticationFilter">
        <!-- 表單中賬號的input名稱 -->
        <property name="usernameParam" value="username"/>
        <!-- 表單中密碼的input名稱 -->
        <property name="passwordParam" value="password"/>
    </bean>

登錄頁面添加驗證碼

<TR>
    <TD>驗證碼:</TD>
    <TD><input id="randomcode" name="randomcode" size="8" /> <img
        id="randomcode_img" src="${baseurl}validatecode.jsp" alt=""
        width="56" height="20" align='absMiddle' /> <a
        href=javascript:randomcode_refresh()>刷新</a>
    </TD>
</TR>

配置validatecode.jsp匿名訪問

在login.action對錯誤信息進行解析

記住我rememberme

用戶登錄選擇"自動登錄" 本次登錄成功后會向cookie寫身份信息,下次登錄從cookie中取出身份信息實現自動登錄。

用戶身份實現Serializable序列化接口

向cookie記錄身份信息需要將用戶身份信息對象實現序列化接口,如下:
 

所以還需要將SySPermission類實現序列化接口

配置rememberMeManager

<!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm" />
        <!-- 注入緩存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>
        <!-- 注入Session管理器 -->
        <property name="sessionManager" ref="sessionManager"/>
        <!-- 注入rememberMe管理器 -->
        <property name="rememberMeManager" ref="rememberMeManager"/>
    </bean>
<!-- rememberMeManager管理器,寫cookie,取出cookie生成用戶信息 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cookie" ref="rememberMeCookie"/>
    </bean>
    <!-- 記住我cookie -->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!-- rememberMe是cookie的名稱 -->
        <constructor-arg value="rememberMe"/>
        <!-- 記住我cookie的有效時間是30天 30*24*60*60 單位是秒 -->
        <property name="maxAge" value="2592000"></property>
    </bean>

FormAuthenticationFilter配置rememberMe

修改formAuthenticationFitler添加頁面中“記住我checkbox”的input名稱

<!-- 自定義form認證過濾器 -->
    <!-- 基於Form表單的身份認證過濾器,即使不配置也會注冊此過濾器,表單中的用戶賬號、密碼及loginurl將采用默認值,建議配置 -->
    <bean id="authenticationFilter" class="liuxun.ssm.shiro.CustomFormAuthenticationFilter">
        <!-- 表單中賬號的input名稱 -->
        <property name="usernameParam" value="username"/>
        <!-- 表單中密碼的input名稱 -->
        <property name="passwordParam" value="password"/>
        <!-- 記住我input的名稱 -->
        <property name="rememberMeParam" value="rememberMe"/>
    </bean>

登錄頁面加rememberMe組件

在login.jsp中添加"記住我"checkbox

<tr>
    <TD></TD>
    <td><input type="checkbox" name="rememberMe" />自動登陸</td>
</tr>

使用UserFilter

如果設置記住我,下次訪問某些URL時可以不用登陸,將記住我即可訪問的地址配置讓UserFilter攔截。(因為用戶身份信息已經存儲在名稱為rememberMe的cookie中,凡是需要身份認證才能訪問的url都可以添加user配置)
注意:
user和authc過濾器的區別
user可以說是針對rememberMe來使用的,當為非匿名訪問的某地址配置了user過濾器,那么通過rememberMe不通過登錄認證也可直接訪問,凡是沒有配置user過濾器的URL,即使配置了rememberMe功能也必須通過登錄認證才能訪問,當然可以匿名訪問的除外。/**=user 表示所有的地址都可通過rememberMe功能進行訪問。
authc 表示通過身份信息認證的地址(包括rememberMe或登錄實現認證)都可進行訪問,范圍更廣。

測試:
登錄時選中記住我選擇框 認證成功后 退出瀏覽器,重新打開瀏覽器 直接訪問主頁

點擊修改后效果如下:

可以發現沒有配置user過濾器的URL(非匿名的) 是不能通過rememberMe直接訪問的
修改配置為所有商品(item)的訪問配置user過濾器(實現可通過rememberMe訪問)

再重新關閉瀏覽器 重新打開 可以發現商品的所有鏈接都可以通過rememberMe進行訪問了

項目代碼

此項目已上傳至GitHub ( https://github.com/LX1993728/permission_web_shiro)
工程目錄結構如下:
其關鍵代碼如下:
web.xml
<?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" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    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">
    <display-name>springmvc_mybatis_1</display-name>
 
    <!-- 配置Spring容器監聽器 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/applicationContext-*.xml</param-value> 
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
 
    <!-- 前端控制器 -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 加載springmvc配置 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- 配置文件的地址 如果不配置contextConfigLocation,
             默認查找的配置文件名稱classpath下的:servlet名稱+"-serlvet.xml"
             即:springmvc-serlvet.xml 
             -->
            <param-value>classpath:spring/springmvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- 可以配置/ ,此工程 所有請求全部由springmvc解析,此種方式可以實現 RESTful方式,需要特殊處理對靜態文件的解析不能由springmvc解析 
            可以配置*.do或*.action,所有請求的url擴展名為.do或.action由springmvc解析,此種方法常用 不可以/*,如果配置/*,返回jsp也由springmvc解析,這是不對的。 -->
        <url-pattern>*.action</url-pattern>
    </servlet-mapping>
    
    <!-- shiro的filter -->
    <!-- shiro過濾器,DelegatingFilterProxy通過代理模式將Spring容器中的bean和filter關聯起來 -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <!-- 設置targetFilterLifecycle為true 由servlet控制filter生命周期 -->
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
        <!-- 設置Spring容器filter的bean id,如果不設置則在Spring注冊的bean中查找與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>
    
    <!-- post亂碼處理 -->
    <filter>
        <filter-name>CharacterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CharacterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
 
 
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
    </welcome-file-list>
</web-app>

applicationContext-shiro.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: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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
        http://www.springframework.org/schema/mvc 
        http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd 
        http://www.springframework.org/schema/context 
        http://www.springframework.org/schema/context/spring-context-3.2.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx-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.action"/>
        <!-- 認證成功統一跳轉到first.action,建議不配置,默認情況下,shiro認證成功后自動跳轉上一個請求路徑 -->
        <property name="successUrl" value="/first.action"/>
        <!-- 通過unauthorizedUrl 指定沒有權限操作時的跳轉頁面 -->
        <property name="unauthorizedUrl" value="/refuse.jsp"/>
        <!-- 自定義filter配置 -->
        <property name="filters">
            <map>
                <!-- 將自定義的FormAuthenticationFilter注入shiroFilter -->
                <entry key="authc" value-ref="authenticationFilter"/>
            </map>
        </property>
        <!-- 過濾器鏈定義,從上向下執行,一般將/**放在最下邊 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- 退出攔截,請求logout.action執行退出操作 shiro自動清除Session-->
                /logout.action = logout
                <!-- 測試清空緩存 -->
                /clearShiroCache.action = anon
                <!-- 無權訪問頁面 anon表示可以匿名訪問 -->
                /refuse.jsp = anon
                <!-- 驗證碼可以匿名訪問 -->
                /validatecode.jsp = anon
                <!-- perms[xx] 表示有xx權限才可以訪問 一般此類使用注解替代配置 -->
                <!-- /item/queryItem.action = perms[item:query] -->
                <!-- /item/editItem.action = perms[item:edit] -->
                <!-- 配置記住我或認證通過可以訪問的地址 -->
                /index.jsp = user
                /first.action = user
                /welcome.jsp = user
                <!-- /item/queryItem.action = user -->
                /item/** = user
                <!-- 對靜態資源設置匿名訪問 -->
                /js/** = anon
                /images/** = anon
                /styles/** = anon
                <!-- /** = authc 表示所有的URL都必須認真通過才可以進行訪問-->
                /** = authc    
            </value>        
        </property>
    </bean>
    
    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm" />
        <!-- 注入緩存管理器 -->
        <property name="cacheManager" ref="cacheManager"/>
        <!-- 注入Session管理器 -->
        <property name="sessionManager" ref="sessionManager"/>
        <!-- 注入rememberMe管理器 -->
        <property name="rememberMeManager" ref="rememberMeManager"/>
    </bean>
    
    <!-- 自定義Realm -->
    <bean id="customRealm" class="liuxun.ssm.shiro.CustomRealm">
        <!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 -->
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </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:shiro-ehcache.xml"/>
    </bean>
    
    <!-- 會話管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- Session的失效時長,單位:毫秒 -->
        <property name="globalSessionTimeout" value="600000"/>
        <!-- 刪除失效的Session -->
        <property name="deleteInvalidSessions" value="true"/>
    </bean>
    
    <!-- 自定義form認證過濾器 -->
    <!-- 基於Form表單的身份認證過濾器,即使不配置也會注冊此過濾器,表單中的用戶賬號、密碼及loginurl將采用默認值,建議配置 -->
    <bean id="authenticationFilter" class="liuxun.ssm.shiro.CustomFormAuthenticationFilter">
        <!-- 表單中賬號的input名稱 -->
        <property name="usernameParam" value="username"/>
        <!-- 表單中密碼的input名稱 -->
        <property name="passwordParam" value="password"/>
        <!-- 記住我input的名稱 -->
        <property name="rememberMeParam" value="rememberMe"/>
    </bean>
    
    <!-- rememberMeManager管理器,寫cookie,取出cookie生成用戶信息 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cookie" ref="rememberMeCookie"/>
    </bean>
    <!-- 記住我cookie -->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <!-- rememberMe是cookie的名稱 -->
        <constructor-arg value="rememberMe"/>
        <!-- 記住我cookie的有效時間是30天 30*24*60*60 單位是秒 -->
        <property name="maxAge" value="2592000"></property>
    </bean>
</beans>

springmvc.xml中設計shiro注解的配置如下:

    <!-- 開啟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-ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    <!--diskStore:緩存數據持久化的目錄 地址  -->
    <diskStore path="/Users/liuxun/Desktop/ehcache" />
    <defaultCache 
        maxElementsInMemory="1000" 
        maxElementsOnDisk="10000000"
        eternal="false" 
        overflowToDisk="true" 
        diskPersistent="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120" 
        diskExpiryThreadIntervalSeconds="120"
        memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>

CustomRealm.java

package liuxun.ssm.shiro;
 
import java.util.ArrayList;
import java.util.List;
 
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
 
import liuxun.ssm.po.ActiveUser;
import liuxun.ssm.po.SysPermission;
import liuxun.ssm.po.SysUser;
import liuxun.ssm.service.SysService;
 
public class CustomRealm extends AuthorizingRealm {
    @Autowired
    private SysService sysService;
    
    // 設置Realm名稱
    @Override
    public void setName(String name) {
        super.setName("CustomRealm");
    }
 
    // 支持UsernamePasswordToken
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }
 
    // 用於認證(使用靜態數據模擬測試)
    /**@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 從token中獲取用戶身份信息
        String username = (String) token.getPrincipal();
        // 拿着username從數據庫中進行查詢
        // ....
        // 如果查詢不到返回null
        if (!username.equals("zhangsan")) {
            return null;
        }
        // 獲取從數據庫查詢出來的用戶密碼
        String password = "123"; // 這里使用靜態數據進行測試
        // 根據用戶id從數據庫中取出菜單
        // ...先使用靜態數據
        List<SysPermission> menus = new ArrayList<SysPermission>();
        SysPermission sysPermission_1 = new SysPermission();
        sysPermission_1.setName("商品管理");
        sysPermission_1.setUrl("/item/queryItem.action");
        SysPermission sysPermission_2 = new SysPermission();
        sysPermission_2.setName("用戶管理");
        sysPermission_2.setUrl("/user/query.action");
        menus.add(sysPermission_1);
        menus.add(sysPermission_2);
        // 構建用戶身份信息
        ActiveUser activeUser = new ActiveUser();
        activeUser.setUserid(username);
        activeUser.setUsername(username);
        activeUser.setUsercode(username);
        activeUser.setMenus(menus);
        // 返回認證信息由父類AuthenticationRealm進行認證
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password,
                this.getName());
        return simpleAuthenticationInfo;
    }
    // 用於授權(使用靜態數據進行測試)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //獲取身份信息
        ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
        //用戶id
        String userid = activeUser.getUserid();
        // 根據用戶id從數據庫中查詢權限數據
        // ...這里使用靜態數據模擬
        List<String> permissions = new ArrayList<String>();
        permissions.add("item:query");
        permissions.add("item:update");
        
        //將權限信息封裝為AuthorizationInfo
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //基於資源權限的訪問控制
        for (String permission : permissions) {
            simpleAuthorizationInfo.addStringPermission(permission);
        }
        // 如果基於角色進行訪問控制
        // for (String role : roles) {
        // simpleAuthorizationInfo.addRole(role);
        // }
        
        return simpleAuthorizationInfo;
    }
    **/
    // 用於認證(從數據庫中查詢用戶信息)
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 從token中獲取用戶身份信息
        String userCode = (String) token.getPrincipal();
        
        SysUser sysUser = null;
        try {
            sysUser = sysService.findSysUserByUserCode(userCode);
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        //如果賬號不存在則返回null
        if (sysUser == null) {
            return null;
        }
        
        //根據用戶id取出菜單
        List<SysPermission> menus = null;
        try {
            menus = sysService.findMenuListByUserId(sysUser.getId());
        } catch (Exception e) {
            e.printStackTrace();
        }
        //用戶密碼
        String password = sysUser.getPassword();
        //
        String salt = sysUser.getSalt();
        
        //構建用戶身份信息
        ActiveUser activeUser = new ActiveUser();
        activeUser.setUserid(sysUser.getId());
        activeUser.setUsername(sysUser.getUsername());
        activeUser.setUsercode(sysUser.getUsercode());
        activeUser.setMenus(menus);
        
        //將activeUser設置simpleAuthenticationInfo
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password,
                ByteSource.Util.bytes(salt), this.getName());
        return simpleAuthenticationInfo;
    }
    
    // 用於授權(從數據庫中查詢授權信息)
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //獲取身份信息
        ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal();
        //用戶id
        String userid = activeUser.getUserid();
        //獲取用戶權限
        List<SysPermission> permissionsList = null;
        try {
            permissionsList = sysService.findPermissionListByUserId(userid);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //構建shiro授權信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //單獨定義一個集合
        List<String> permissions = new ArrayList<String>();
        for (SysPermission sysPermission : permissionsList) {
            //將數據庫中的權限標簽放入集合
            permissions.add(sysPermission.getPercode());
        }
        simpleAuthorizationInfo.addStringPermissions(permissions);
        
        return simpleAuthorizationInfo;
    }
    
    //清除用戶的授權信息
    
    
    //清空緩
    public void clearCached(){
        //清空所有用戶的身份緩存信息
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
        super.clearCache(principals);
    }
 
}

CustomFormAuthenticationFilter.java

package liuxun.ssm.shiro;
 
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
 
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
/**
 * 認證之前實現驗證碼校驗
 * @author liuxun
 *
 */
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter {
 
    //原FormAuthenticationFilter的認證方法
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //在這里進行驗證碼的校驗
        
        //從Session中獲取正確的驗證碼
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpSession session = httpServletRequest.getSession();
        //取出Session中的驗證碼(正確的驗證碼)
        String validateCode = (String) session.getAttribute("validateCode");
        
        //取出頁面的驗證碼
        //輸入的驗證和session中的驗證進行對比 
        String randomcode = httpServletRequest.getParameter("randomcode");
        if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){
            //如果校驗失敗,將驗證碼錯誤失敗信息,通過shiroLoginFailure設置到request中
            httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError");
            //拒絕訪問,不再校驗賬號和密碼 
            return true; 
        }
        return super.onAccessDenied(request, response);
    }
 
}

 

LoginController.java

package liuxun.ssm.controller;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
 
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
 
import liuxun.ssm.exception.CustomException;
import liuxun.ssm.po.ActiveUser;
import liuxun.ssm.service.SysService;
 
/**
 * 登錄和退出
 * @author liuxun
 *
 */
@Controller
public class LoginController {
    @Autowired
    private SysService sysService;
    
    //用戶登錄提交方法
    /*@RequestMapping("/login")
    public String login(HttpSession session,String randomcode,String usercode,String password) throws Exception{
        // 校驗驗證碼,防止惡性攻擊
        // 從Session中獲取正確的驗證碼
        String validateCode = (String) session.getAttribute("validateCode");
        
        //輸入的驗證碼和Session中的驗證碼進行對比
        if (!randomcode.equalsIgnoreCase(validateCode)) {
            //拋出異常
            throw new CustomException("驗證碼輸入錯誤");
        }
        
        //調用Service校驗用戶賬號和密碼的正確性
        ActiveUser activeUser = sysService.authenticat(usercode, password);
        
        //如果Service校驗通過,將用戶身份記錄到Session
        session.setAttribute("activeUser", activeUser);
        //重定向到商品查詢頁面
        return "redirect:/first.action";
    } */
    //用戶登錄提交方法
    @RequestMapping("/login")
    public String login(HttpServletRequest request) throws Exception{
       
        //shiro在認證通過后出現錯誤后將異常類路徑通過request返回
        //如果登陸失敗從request中獲取認證異常信息,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(); //最終在設置的異常處理器中生成未知錯誤
            }
        }
        //此方法不處理登錄成功(認證成功)的情況
        //如果登錄失敗還到login頁面
        return "login";
    }
    
    //用戶退出
    /*
    @RequestMapping("/logout")
    public String logout(HttpSession session) throws Exception{
        //session失效
        session.invalidate();
        //重定向到商品查詢頁面
        return "redirect:/first.action";
    }
    */
}

 

FirstAction.java

package liuxun.ssm.controller;
 
import java.util.List;
 
import javax.servlet.http.HttpSession;
 
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
 
import liuxun.ssm.po.ActiveUser;
 
 
@Controller
public class FirstAction {
    //系統首頁
    @RequestMapping("/first")
    public String first(Model model)throws Exception{
        //主體
        Subject subject = SecurityUtils.getSubject();
        //身份
        ActiveUser activeUser = (ActiveUser) subject.getPrincipal();
        model.addAttribute("activeUser", activeUser);
        return "/first";
    }
    
    //歡迎頁面
    @RequestMapping("/welcome")
    public String welcome(Model model)throws Exception{
        
        return "/welcome";
        
    }
}

其中基於原始的URL攔截(不使用shiro)的代碼是屏蔽的代碼 方便對比總結 所以沒有刪除

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM