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核心知識點總結!