秒極啊!手把手帶你進行shiro授權攔截器的重寫,學到了學到了

前程有光發表於2020-12-03

shiro整合前後端分離的springboots,Vue專案真的是有很多大坑啊。

今天我的主題是:如何設定shiro過濾器。

遇到問題:我的專案是前後端分離的,shiro裡面有一個shiroFilterFactoryBean.setUnauthorizedUrl(“你自己的url”);
函式

這是什麼意思呢:這表示如果你訪問了一個需要許可權的url,但是目前你登陸的角色沒有許可權,那麼頁面預設跳轉的地址。
看著似乎是沒啥毛病。
但是!!!
如果是前後端分離怎麼辦呢?後端shiro讓專案跳向前端某個頁面,這裡是前後端分離式的啊!當前端的使用者(沒許可權)傳送了一個請求被shiro直接攔截了,前端一臉懵不知道發生什麼問題了,所以傻傻的在控制檯返回一段紅色的跨域錯誤資訊。
那麼我們應該怎麼解決呢?
你肯定會想那就讓後端讓前端跳轉啊,這裡我要說,既然是前後端分離的話,後端只能給前端傳送資訊讓前端根據返回的資訊自行處理。
但是問題又來了,shiro這玩意直接把前端請求攔截了啥都不返回,啥都不說一聲,搞的前端還誤解是跨域問題。

那麼我們就應該去改寫shiro的攔截器,讓它處理方式更人性化。

不廢話了,開始主題

開始改寫攔截器:

//package com.igeekhome.ccs.tool.config;  //寫自己的當前目錄

import lombok.SneakyThrows;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class MyPermsFilter extends AuthorizationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
            throws Exception {
        System.out.println("isAccessDenied");
        Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;
        System.out.println("subject:"+subject.getPrincipal());
        if (rolesArray == null || rolesArray.length == 0) {
            //no roles specified, so nothing to check - allow access.
            return true;
        }

        for(int i=0;i<rolesArray.length;i++){
            if(subject.isPermitted(rolesArray[i])){
                System.out.println("rolealist:"+rolesArray[i]);
                return true;
            }
        }
        return false;
    }

    @SneakyThrows
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        System.out.println("onAccessDenied");
        Subject subject = getSubject(request, response);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        // If the subject isn't identified, redirect to login URL
        if (subject.getPrincipal() == null) {
//            saveRequestAndRedirectToLogin(request, response);     //沒登陸就進入重新登陸介面,這裡前後端分離不需要
            System.out.println("沒登陸");
            String objectStr= "{'name':'沒登陸'}";
            JSONObject jsonObject=new JSONObject(objectStr);
            PrintWriter wirte = null;
            wirte = response.getWriter();
            wirte.print(jsonObject);
        } else {
            System.out.println("沒許可權");
            String objectStr= "{'name':'沒許可權'}";
            JSONObject jsonObject=new JSONObject(objectStr);
            PrintWriter wirte = null;
            wirte = response.getWriter();
            wirte.print(jsonObject);
//            JSONObject json = new JSONObject();
//            json.put("state","403");
//            json.put("msg","登入已失效,請重新登入!");
//            out.println(json);
//            out.flush();
//            out.close();
        }

        return false;
    }
}

首先看到

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)

也就是第一個函式
解釋一下:這個函式每次shiro啟動都會執行,看到引數ServletRequest request, ServletResponse response這兩個引數是為當前使用者身份確認起作用的,再看到Object mappedValue,它裡面存在你之前shiro配置檔案裡存入的許可權要求:

我這裡存了一個:

filterChainDefinitionMap.put("/detail/getclass","perms[user:teacher]");

所以mappedValue裡存的應該是user:teacher,當然前提是把它轉化為String

再看到程式碼:

這是看看你之前有沒有存許可權,如果沒存的話(列表大小為0或列表為空)返回true,shiro就不攔你了。
再往下看:

這裡subject是什麼?你往上看找到:

這個就是獲取你當前登陸的使用者。
然後注意了!

 if(subject.isPermitted(rolesArray[i])){
                System.out.println("rolealist:"+rolesArray[i]);
                return true;
            }

這是幹嘛,這就是拿當前使用者判斷isPermitted(),當前使用者有沒有perms的內容,也就是判斷subject裡面有沒有”user:teacher”許可權,然後這裡有個大坑,網上基本上所有教程程式碼是這樣的:

他們是hasRole,其實也沒毛病,但是我之前設定的是

perms所以要用isPermitted,你要發清楚你加的是什麼許可權。
接著下一步:
如果找到了返回true,沒找到從for迴圈出來,直接返回false。
false出現了,怎麼辦呢?這時候第二個函式

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) 

知道你返回是false的話它就會執行,看紅色框內

這代表什麼意思,當然是獲取當前使用者的username了也就是標識碼了,這裡不懂得說明shiro你還是沒弄懂()因為之前的自定義Realm類用過了。
如果subject.getPrincipal() == null說明沒有使用者名稱,意味著你還沒有登陸呢,以前程式碼這裡是一個跳轉函式(讓前端跳到登陸頁面,但是我是前後端分離所以沒用!),這裡我改成了

			String objectStr= "{'name':'沒登陸'}";
            JSONObject jsonObject=new JSONObject(objectStr);
            PrintWriter wirte = null;
            wirte = response.getWriter();
            wirte.print(jsonObject);

這是把自定義的objectStr 字串轉化為jsob格式,然後用PrintWriter返回給前端,這樣一旦出錯那麼shiro除了攔截請求外還會給前端發一個 "{‘name’:‘沒登陸’}“的json資訊,這些前端就不懵了就不會說什麼跨域問題了。而是得到了json資料。然後下面的else部分內容就不說了,和上面幾乎一樣給前端傳送一個”{‘name’:‘沒許可權’}"的資訊,這意味著你許可權出錯了,並且使用者也登陸了,那麼就只有一個可能導致第一個函式返回false,那就是你本來就沒有許可權!

好了好了說了一大堆。

老規矩,給那些趕時間的朋友們:

快餐:

總結:

三個點:
1.為什麼要寫攔截器,並且如何去重寫攔截器,上面說了哦。
2.攔截器檔案第一個函式裡是用subject.isPermitted(rolesArray[i])還是subject.hasRole(rolesArray[i])根據自己在之前設定資訊判斷。

我用的perms所以用subject.isPermitted(rolesArray[i])

3.就是這麼向前端返回資料資訊

然後貼出四分程式碼:
shiroConfig:

package //自己的包;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
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 javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class shiroConfig {
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/index/login");
        shiroFilterFactoryBean.setSuccessUrl("/Station/noauth");
//        shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
         shiroFilterFactoryBean.setUnauthorizedUrl("/Station/noauth");
//        shiroFilter.setLoginUrl("");//身份認證失敗,則跳轉到登入頁面的配置 沒有登入的使用者請求需要登入的頁面時自動跳轉到登入頁面,不是必須的屬性,不輸入地址的話會自動尋找專案web專案的根目錄下的”/login.jsp”頁面。
//        shiroFilter.setSuccessUrl("");//登入成功預設跳轉頁面,不配置則跳轉至”/”。如果登陸前點選的一個需要登入的頁面,則在登入自動跳轉到那個需要登入的頁面。不跳轉到此。
//        shiroFilter.setUnauthorizedUrl("");//沒有許可權預設跳轉的頁面
//        shiroFilter.setFilterChainDefinitions("");//filterChainDefinitions的配置順序為自上而下,以最上面的為準
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // <!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->
        filterChainDefinitionMap.put("/Station/**", "anon");
        filterChainDefinitionMap.put("/index/**", "anon");
//        filterChainDefinitionMap.put("/detail/**", "anon");
//        filterChainDefinitionMap.put("/detail/**", "anon");
//        filterChainDefinitionMap.put("/Goods/**", "authc");
        //主要這行程式碼必須放在所有許可權設定的最後,不然會導致所有 url 都被攔截 剩餘的都需要認證
//        filterChainDefinitionMap.put("/**", "authc");
        filterChainDefinitionMap.put("/detail/getclass","perms[user:teacher]");

        // 1、建立過濾器Map,用來裝自定義過濾器
        LinkedHashMap<String, Filter> map = new LinkedHashMap<>();

        // 2、將自定義過濾器放入map中,如果實現了自定義授權過濾器,那就必須在這裡註冊,否則Shiro不會使用自定義的授權過濾器
        map.put("perms", new MyPermsFilter());

        // 3、將過濾器Ma繫結到shiroFilterFactoryBean上
        shiroFilterFactoryBean.setFilters(map);

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    @Bean
    public SecurityManager securityManager(CustomRealm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(realm);
        return  defaultWebSecurityManager;
    }
    @Bean
    public CustomRealm customRealm() {
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customRealm;
    }
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 使用md5 演算法進行加密
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 設定雜湊次數: 意為加密幾次
        hashedCredentialsMatcher.setHashIterations(2);

        return hashedCredentialsMatcher;
    }
}

CustomRealm:

package //自己的包;

import com.igeekhome.ccs.biz.IndexBiz;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
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;
import org.apache.tomcat.websocket.AuthenticationException;
import org.springframework.beans.factory.annotation.Autowired;

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

public class CustomRealm extends AuthorizingRealm {
//    @Autowired
//    private LoginService loginService;
    @Autowired
    IndexBiz indexBiz;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<String> stringSet = new HashSet<>();
        if (indexBiz.getsatte(username).equals("老師")){
            System.out.println("老師");
            stringSet.add("user:teacher");
        }else {
            System.out.println("學生");
            stringSet.add("user:student");
        }
        info.setStringPermissions(stringSet);
        return info;
    }

    /**
     * 這裡可以注入userService,為了方便演示,我就寫死了帳號了密碼
     * private UserService userService;
     * <p>
     * 獲取即將需要認證的資訊
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
        System.out.println("-------身份認證方法--------");
        String userName = (String) authenticationToken.getPrincipal();
        String userPwd = new String((char[]) authenticationToken.getCredentials());
        //根據使用者名稱從資料庫獲取密碼
        String password = indexBiz.getpassword(userName);

        if (indexBiz.getsatte(userName)==null) {
            throw new AccountException("使用者名稱錯誤");
        }
//        else if (!userPwd.equals(password)) {
//            throw new AccountException("密碼不正確");
//        }
        String dbPwd = indexBiz.getpassword(userName);
        return new SimpleAuthenticationInfo(userName,dbPwd, ByteSource.Util.bytes(userName + "salt"),getName());
    }

}

MyPermsFilter:

package //自己的包;

import lombok.SneakyThrows;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class MyPermsFilter extends AuthorizationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
            throws Exception {
        System.out.println("isAccessDenied");
        Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;
        System.out.println("subject:"+subject.getPrincipal());
        if (rolesArray == null || rolesArray.length == 0) {
            //no roles specified, so nothing to check - allow access.
            return true;
        }

        for(int i=0;i<rolesArray.length;i++){
            if(subject.isPermitted(rolesArray[i])){//subject.hasRole(rolesArray[i])
                System.out.println("rolealist:"+rolesArray[i]);
                return true;
            }
        }
        return false;
    }

    @SneakyThrows
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        System.out.println("onAccessDenied");
        Subject subject = getSubject(request, response);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        // If the subject isn't identified, redirect to login URL
        if (subject.getPrincipal() == null) {
//            saveRequestAndRedirectToLogin(request, response);     //沒登陸就進入重新登陸介面,這裡前後端分離不需要
            System.out.println("沒登陸");
            String objectStr= "{'name':'沒登陸'}";
            JSONObject jsonObject=new JSONObject(objectStr);
            PrintWriter wirte = null;
            wirte = response.getWriter();
            wirte.print(jsonObject);
        } else {
            System.out.println("沒許可權");
            String objectStr= "{'name':'沒許可權'}";
            JSONObject jsonObject=new JSONObject(objectStr);
            PrintWriter wirte = null;
            wirte = response.getWriter();
            wirte.print(jsonObject);
//            JSONObject json = new JSONObject();
//            json.put("state","403");
//            json.put("msg","登入已失效,請重新登入!");
//            out.println(json);
//            out.flush();
//            out.close();
        }

        return false;
    }

}

最後

文章的最後給大家安利一個福利,關注公眾號:前程有光,領取一線大廠Java面試題總結+各知識點學習思維導+一份300頁pdf文件的Java核心知識點總結!

相關文章