1. 程式人生 > >SpringBoot學習-(十七)SpringBoot整合Shiro

SpringBoot學習-(十七)SpringBoot整合Shiro

基本步驟:

  1. 新增pom檔案依賴
  2. 書寫自定義的realm
  3. 配置shiro
  4. 控制層使用

專案目錄結構:

這裡寫圖片描述

1.新增pom檔案依賴

<!-- spring整合shiro -->
<!-- maven會自動新增shiro-core,shiro-web依賴 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version
>
</dependency>

2.書寫自定義realm

package com.ahut.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 org.apache.shiro.util.ByteSource; /** * * @ClassName
: MyRealm * @Description: 自定義realm * @author cheng * @date 2017年10月9日 上午10:54:00 */
public class MyRealm extends AuthorizingRealm { /** * 用於認證 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("使用了自定義的realm,使用者認證..."); System.out.println("使用者名稱:" + ((UsernamePasswordToken) token).getUsername()); System.out.println("密碼:" + new String(((UsernamePasswordToken) token).getPassword())); // 模擬賬號不存在 // if (true) { // throw new UnknownAccountException(); // } // 獲取使用者名稱 String userName = (String) token.getPrincipal(); // 依據使用者名稱去資料庫查詢 // 模擬從資料庫中查詢出來的雜湊值密碼 String password = "36f2dfa24d0a9fa97276abbe13e596fc"; // 查詢到了資料,驗證密碼是否正確 // 密碼正確,認證通過 // 密碼錯誤,認證失敗 // 沒有查詢到資料,認證失敗 // 模擬從資料庫中獲取salt String salt = "qwerty"; // 與UsernamePasswordToken(userName, password)進行比較 // 區別UsernamePasswordToken(userName, password)中的password是使用者輸入的密碼,即沒有加密過的密碼 // SimpleAuthenticationInfo(userName, password, ByteSource.Util.bytes(salt), this.getName())中的password是資料庫中的密碼,即加密過後的密碼 return new SimpleAuthenticationInfo(userName, password, ByteSource.Util.bytes(salt), this.getName()); } /** * 用於授權 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("使用了自定義的realm,使用者授權..."); // 獲取使用者名稱 // String userName = (String) principals.getPrimaryPrincipal(); // 依據使用者名稱在資料庫中查詢許可權資訊 // 角色 List<String> roles = new ArrayList<>(); roles.add("admin"); roles.add("user"); // 許可權 List<String> permissions = new ArrayList<>(); permissions.add("admin:select"); permissions.add("admin:delete"); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addStringPermissions(permissions); simpleAuthorizationInfo.addRoles(roles); return simpleAuthorizationInfo; } }

3.配置shiro

package com.ahut.config;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.ahut.shiro.MyRealm;

/**
 * 
 * @ClassName: ShiroConfig
 * @Description: spring整合shiro配置
 * @author cheng
 * @date 2017年10月10日 上午9:46:43
 */
@Configuration
public class ShiroConfig {

    /**
     * 
     * @Title: createMyRealm
     * @Description: 自定義的realm
     * @return
     */
    @Bean
    public MyRealm createMyRealm() {
        // 加密相關
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 雜湊演算法
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 雜湊次數
        hashedCredentialsMatcher.setHashIterations(2);
        MyRealm myRealm = new MyRealm();
        myRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return myRealm;
    }

    /**
     * 
     * @Title: securityManager
     * @Description: 注入自定義的realm
     * @Description: 注意方法返回值SecurityManager為org.apache.shiro.mgt.SecurityManager
     *               ,不要導錯包
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(createMyRealm());
        return securityManager;
    }

    /**
     * 
     * @Title: shirFilter
     * @Description: Shiro 的Web過濾器
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/userLogin");
        // 登入成功後要跳轉的連結,建議不配置,shiro認證成功自動到上一個請求路徑
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 未授權介面,指定沒有許可權操作時跳轉頁面
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        // 過濾器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不會被過濾的連結 順序判斷
        // 過慮器鏈定義,從上向下順序執行,一般將/**放在最下邊
        // 對靜態資源設定匿名訪問
        // anon:所有url都都可以匿名訪問
        filterChainDefinitionMap.put("/static/**", "anon");
        // 配置退出 過濾器,其中的具體的退出程式碼Shiro已經替我們實現了
        filterChainDefinitionMap.put("/logout", "logout");
        // authc:所有url都必須認證通過才可以訪問
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

}

程式碼說明:
1.自定義的realm和注入自定義的realm相當於以前的shiro.ini檔案

#自定義的realm
myRealm=com.ahut.test.MyRealm
# 注入自定義的realm
securityManager.realms=$myRealm

2.ShiroFilterFactoryBean相當於以下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" />
    <!-- 自定義filter配置 -->
    <property name="filters">
        <map>
            <!-- 將自定義 的FormAuthenticationFilter注入shiroFilter中-->
            <entry key="authc" value-ref="formAuthenticationFilter" />
        </map>
    </property>

    <!-- 過慮器鏈定義,從上向下順序執行,一般將/**放在最下邊 -->
    <property name="filterChainDefinitions">
        <value>
            <!-- 對靜態資源設定匿名訪問 -->
            /images/** = anon
            /js/** = anon
            /styles/** = anon
            <!-- 驗證碼,可匿名訪問 -->
            /validatecode.jsp = anon

            <!-- 請求 logout.action地址,shiro去清除session-->
            /logout.action = logout
            <!--商品查詢需要商品查詢許可權 ,取消url攔截配置,使用註解授權方式 -->
            <!-- /items/queryItems.action = perms[item:query]
            /items/editItems.action = perms[item:edit] -->
            <!-- 配置記住我或認證通過可以訪問的地址 -->
            /index.jsp  = user
            /first.action = user
            /welcome.jsp = user
            <!-- /** = authc 所有url都必須認證通過才可以訪問-->
            /** = authc
            <!-- /** = anon所有url都可以匿名訪問 -->

        </value>
    </property>
</bean>

<!-- securityManager安全管理器 -->
<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" />
    <!-- 記住我 -->
    <property name="rememberMeManager" ref="rememberMeManager"/>    
</bean>

<!-- realm -->
<bean id="customRealm" class="cn.itcast.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="formAuthenticationFilter" 
    class="cn.itcast.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天 -->
    <property name="maxAge" value="2592000" />
</bean>

4.控制層使用

package com.ahut.action;

import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.ExcessiveAttemptsException;
import org.apache.shiro.authc.ExpiredCredentialsException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 
 * @ClassName: ShiroAction
 * @Description: shiro控制層
 * @author cheng
 * @date 2017年10月10日 上午10:18:21
 */
@RestController
public class ShiroAction {

    /**
     * 
     * @Title: userLogin
     * @Description: 使用者登入
     * @return
     */
    @RequestMapping(value = "/userLogin")
    public String userLogin(String username, String password) {

        // 以下部分在配置階段就已經完成,可以直接使用
        // 讀取配置檔案
        // Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        // 獲取SecurityManager的例項
        // SecurityManager securityManager = factory.getInstance();
        // 把 securityManager 的例項繫結到 SecurityUtils 上
        // SecurityUtils.setSecurityManager(securityManager);

        System.out.println(username + ":" + password);
        Subject subject = SecurityUtils.getSubject();
        // 自己建立一個令牌,輸入使用者名稱和密碼
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
        try {
            subject.login(usernamePasswordToken);
            System.out.println("身份認證成功!");

        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("賬號不存在!");

        } catch (LockedAccountException e) {
            e.printStackTrace();
            System.out.println("賬號被鎖定!");

        } catch (DisabledAccountException e) {
            e.printStackTrace();
            System.out.println("賬號被禁用!");

        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("憑證/密碼錯誤!");

        } catch (ExpiredCredentialsException e) {
            e.printStackTrace();
            System.out.println("憑證/密碼過期!");

        } catch (ExcessiveAttemptsException e) {
            e.printStackTrace();
            System.out.println("登入失敗次數過多!");

        }

        // 是否認證通過
        boolean isAuthenticated1 = subject.isAuthenticated();
        System.out.println("登入後,是否認證通過:" + isAuthenticated1);

        // 退出
        subject.logout();

        // 是否認證通過
        boolean isAuthenticated2 = subject.isAuthenticated();
        System.out.println("退出登入後,是否認證通過:" + isAuthenticated2);

        return "處理登入";
    }

}

可能出現的問題

頁面載入css或者js時,出現以下錯誤:Resource interpreted as Stylesheet but transferred with MIME type text/html
原因:shiro配置原因,對需要認證的資源進行了攔截操作

// authc:所有url都必須認證通過才可以訪問
filterChainDefinitionMap.put("/**", "authc");

修改以上程式碼為:
filterChainDefinitionMap.put("/**", "anon");

完整程式碼:

    /**
     * @description: shiro web過濾器
     * @author cheng
     * @dateTime 2018/4/18 15:50
     */
    @Bean
    public ShiroFilterFactoryBean createShiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/v1/toLoginPage");

        // 過濾器
        Map<String, String> filterChainDefinitionMap = new HashMap<>();
        // 配置不會被過濾的連結 順序判斷
        // 過慮器鏈定義,從上向下順序執行,一般將/**放在最下邊
        // 使用者註冊匿名訪問
        filterChainDefinitionMap.put("/v1/users/", "anon");
        // 管理員登入頁面
        filterChainDefinitionMap.put("/v1/toLoginPage", "anon");
        // 管理員登入
        filterChainDefinitionMap.put("/v1/login", "anon");
        // 對靜態資源設定匿名訪問
        // anon:所有url都都可以匿名訪問
        filterChainDefinitionMap.put("/static/**", "anon");

        // authc:所有url都必須認證通過才可以訪問
        filterChainDefinitionMap.put("/**", "anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }