SpringBoot 與Shiro 整合系列(三)多Realm驗證和認證策略
系列(三)講述 SpringBoot整合Shiro 實現多Realm驗證以及認證策略
目錄
shiro是一個很好的登陸以及許可權管理框架,但是預設是單realm單資料表,但是很多業務需求單realm是很難實現登陸以及許可權管理的功能(比如業務中使用者分佈在不同的資料表),這個時候就需要使用多個Realm。
一、使用場景
存在這樣一種場景,我們可能會把安全資料放到不同的資料庫,比方說 mysql裡有,oracle裡也有,mysql裡面的加密演算法是MD5,oracle裡面的加密演算法是SHA1。這個時候我們進行使用者認證時,就需要同時訪問這兩個資料庫,就需要多個Realm。如果有多個Realm的話,還需要涉及到認證策略的問題。
二、多Realm驗證
多重認證,主要的類是ModularRealmAuthenticator,他有兩個需要配置的屬性,一個是Collection(用於儲存Realm),另一個是AuthenticationStrategy(用於儲存驗證的策略 )。
通過檢視原始碼可以看到 ModularRealmAuthenticator.class 中的 doAuthenticate方法:
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {//一個realm
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {//多個realm
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
從上面doAuthenticate方法中可以看到:
如果有一個Realm 使用的是:
- doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
如果有多個Realm 使用的是:
- doMultiRealmAuthentication(realms, authenticationToken);
所以我們可以配置多個Realm 給到 ModularRealmAuthenticator 這個bean,將ModularRealmAuthenticator 單獨配置為一個bean,將這個bean 配置給SecurityManager。
配置流程:
1.在原有程式碼的基礎上,新增第二個Realm SecondRealm.java
SecondRealm.java 中的 加密演算法 為 SHA1
package com.koncord.shiro;
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.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import com.koncord.model.User;
import com.koncord.service.UserService;
/**
* 自定義 Realm
* @author Administrator
*
*/
public class SecondRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
/**
* 執行授權邏輯
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("SecondRealm 執行授權邏輯");
//給資源進行授權
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//新增資源的授權字串
//info.addStringPermission("user:add");
//導資料庫查詢當前登入使用者的授權字串
//獲取當前登入使用者
Subject subject=SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
User sbUser=userService.findUserByName(user.getName());
info.addStringPermission(sbUser.getPerms());
return info;
}
/**
* 執行認證邏輯
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("SecondRealm 執行認證邏輯");
//編寫shiro 判斷邏輯,判斷使用者名稱和密碼
//1.判斷使用者名稱
UsernamePasswordToken userToken=(UsernamePasswordToken) token;
//根據使用者名稱獲取使用者資訊
User user=userService.findUserByName(userToken.getUsername());
if(user == null){
//使用者不存在
return null;//shiro底層會丟擲 UNknowAccountException
}
//2.判斷密碼 第一個引數:需要返回給 subject.login方法的資料 第二個引數:資料庫密碼 第三個引數:realm的名字
// return new SimpleAuthenticationInfo(user, user.getPassword(), "");
//根據使用者的情況,來構建AuthenticationInfo對像並返回,通常使用的實現類是SimpleAuthenticationInfo
//以下資訊是從資料庫獲取的
//1).principal:認證的實體類資訊。可以是username,也可以是資料表對應的使用者的體類物件
Object principal=user;
//2).credentials:密碼(資料庫獲取的使用者的密碼)
Object credentials=user.getPassword();
//3).realmName:當前realm物件的name,呼叫父類的getName()方法即可
String realmName=getName();
//4).鹽值
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getName());//鹽值 要唯一
return new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
}
public static void main(String[] args) {
String hashAlgorithmName="SHA1";//加密演算法(與配置檔案中的一致)
String credentials="111111";//密碼
ByteSource salt=ByteSource.Util.bytes("zhangsan");//鹽值
// ByteSource salt=null;//鹽值
int hashIterations=1024;//加密次數(與配置檔案中的一致)
System.out.println(new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations));
}
}
第一個自定義Realm的程式碼:
package com.koncord.shiro;
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.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import com.koncord.model.User;
import com.koncord.service.UserService;
/**
* 自定義 Realm
* @author Administrator
*
*/
public class UserRealm extends AuthorizingRealm{
@Autowired
private UserService userService;
/**
* 執行授權邏輯
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("UserRealm 執行授權邏輯");
//給資源進行授權
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//新增資源的授權字串
//info.addStringPermission("user:add");
//導資料庫查詢當前登入使用者的授權字串
//獲取當前登入使用者
Subject subject=SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
User sbUser=userService.findUserByName(user.getName());
info.addStringPermission(sbUser.getPerms());
return info;
}
/**
* 執行認證邏輯
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("UserRealm 執行認證邏輯");
//編寫shiro 判斷邏輯,判斷使用者名稱和密碼
//1.判斷使用者名稱
UsernamePasswordToken userToken=(UsernamePasswordToken) token;
//根據使用者名稱獲取使用者資訊
User user=userService.findUserByName(userToken.getUsername());
if(user == null){
//使用者不存在
return null;//shiro底層會丟擲 UNknowAccountException
}
//2.判斷密碼 第一個引數:需要返回給 subject.login方法的資料 第二個引數:資料庫密碼 第三個引數:realm的名字
// return new SimpleAuthenticationInfo(user, user.getPassword(), "");
//根據使用者的情況,來構建AuthenticationInfo對像並返回,通常使用的實現類是SimpleAuthenticationInfo
//以下資訊是從資料庫獲取的
//1).principal:認證的實體類資訊。可以是username,也可以是資料表對應的使用者的體類物件
Object principal=user;
//2).credentials:密碼(資料庫獲取的使用者的密碼)
Object credentials=user.getPassword();
//3).realmName:當前realm物件的name,呼叫父類的getName()方法即可
String realmName=getName();
//4).鹽值
ByteSource credentialsSalt = ByteSource.Util.bytes(user.getName());//鹽值 要唯一
return new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
}
public static void main(String[] args) {
String hashAlgorithmName="MD5";//加密演算法(與配置檔案中的一致)
String credentials="111111";//密碼
ByteSource salt=ByteSource.Util.bytes("admin");//鹽值
// ByteSource salt=null;//鹽值
int hashIterations=1024;//加密次數(與配置檔案中的一致)
System.out.println(new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations));
}
}
2.修改ShiroConfig 配置類
2.1 建立第2個Realm:
/**
* 建立第二個Realm
*/
@Bean
public SecondRealm secondRealm(){
SecondRealm secondRealm = new SecondRealm();
//設定 憑證匹配器
secondRealm.setCredentialsMatcher(hashedCredentialsMatcherSHA());
return secondRealm;
}
/**
* 配置 憑證匹配器 ,加密演算法為 SHA1
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcherSHA(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//設定加密演算法
hashedCredentialsMatcher.setHashAlgorithmName("SHA1");
//設定加密次數,比如兩次,相當於SHA1(SHA1())
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
2.2 修改securityManager方法,新增多realms:
/**
* 2.建立 DefaultWebSecurityManager
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager =new DefaultWebSecurityManager();
// //關聯realm
// securityManager.setRealm(userRealm);
//新增多realms
List<Realm> realms =new ArrayList<Realm>();
realms.add(userRealm);
realms.add(secondRealm());
securityManager.setRealms(realms);
return securityManager;
}
ShiroConfig 配置類完整程式碼:
package com.koncord.shiro;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
/**
* Shiro配置類
* @author Administrator
*
*/
@Configuration
public class ShiroConfig {
/**
* 3.建立ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean =new ShiroFilterFactoryBean();
//設定安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
//新增Shiro內建過濾器
/**
* Shiro內建過濾器,可以實現許可權相關的攔截
* 常用的過濾器:
* anon:無需認證(登入)可以訪問
* authc:必須認證才可以訪問
* user:如果使用rememberMe的功能可以直接訪問
* perms:該資源必須得到資源許可權才可以訪問
* role:該資源必須得到角色許可權才可以訪問
*/
Map<String, String> filterMap = new LinkedHashMap<String, String>();
// filterMap.put("/add", "authc");
// filterMap.put("/update", "authc");
filterMap.put("/testThymeleaf", "anon");
filterMap.put("/login", "anon");
//授權過濾器
//注意:當前授權攔截後,shiro會自動跳轉到未授權頁面
filterMap.put("/add", "perms[user:add]");
filterMap.put("/update", "perms[user:update]");
filterMap.put("/*", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
//設定登入跳轉連結
shiroFilterFactoryBean.setLoginUrl("/toLogin");
//設定未授權提示頁面
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
return shiroFilterFactoryBean;
}
/**
* 2.建立 DefaultWebSecurityManager
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager securityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager =new DefaultWebSecurityManager();
// //關聯realm
// securityManager.setRealm(userRealm);
//新增多realms
List<Realm> realms =new ArrayList<Realm>();
realms.add(userRealm);
realms.add(secondRealm());
securityManager.setRealms(realms);
return securityManager;
}
/**
* 1.建立Realm
*/
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
//設定 憑證匹配器
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
/**
* 建立第二個Realm
*/
@Bean
public SecondRealm secondRealm(){
SecondRealm secondRealm = new SecondRealm();
//設定 憑證匹配器
secondRealm.setCredentialsMatcher(hashedCredentialsMatcherSHA());
return secondRealm;
}
/**
* 配置ShiroDialect,用於thymeleaf和shiro標籤配合使用
*/
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
/**
* 配置 憑證匹配器 (由於我們的密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理了,
* 所以我們需要修改下doGetAuthenticationInfo中的程式碼;)
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//設定加密演算法
hashedCredentialsMatcher.setHashAlgorithmName("MD5");
//設定加密次數,比如兩次,相當於md5(md5())
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
/**
* 配置 憑證匹配器 ,加密演算法為 SHA1
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcherSHA(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//設定加密演算法
hashedCredentialsMatcher.setHashAlgorithmName("SHA1");
//設定加密次數,比如兩次,相當於SHA1(SHA1())
hashedCredentialsMatcher.setHashIterations(1024);
return hashedCredentialsMatcher;
}
}
這樣流程就配置完成了!
3.測試
打斷點,進行debug除錯,看下執行效果
(1)在UsernamePasswordToken.class 的
處打斷點,會看到斷點停兩次。
(2)在ModularRealmAuthenticator.class的
處,會看到 Realm的個數。
(3)由於兩種都使用的HashedCredentialsMatcher 時的兩種演算法:
測試成功!!!
三、認證策略
如果有多個Realm,怎樣才能認證成功,這就我們所謂的認證策略。
1.概念
(1)當一個應用程式配置了兩個或兩個以上的Realm 時,ModularRealmAuthenticator 依靠內部的AuthenticationStrategy 元件來判定認證的成功或失敗。
AuthenticationStrategy是一個無狀態的元件,它在身份驗證嘗試中被詢問4 次(這4 次互動所需的任何必要的狀態將被作為方法引數):
- 在任何Realm 被呼叫之前被詢問;
- 在一個單獨的Realm 的getAuthenticationInfo 方法被呼叫之前立即被詢問;
- 在一個單獨的Realm 的getAuthenticationInfo 方法被呼叫之後立即被詢問;
- 在所有的Realm 被呼叫後詢問。
(2)認證策略的另外一項工作就是聚合所有Realm的結果資訊封裝至一個AuthenticationInfo例項中,並將此資訊返回,以此作為Subject的身份資訊。
2.認證策略的使用
認證策略主要使用的是 AuthenticationStrategy 介面。
這個介面有三個實現類(認證策略的實現):
策略 | 意義 |
---|---|
AllSuccessfulStrategy | 所有Realm驗證成功才算成功,且返回所有Realm身份驗證成功的認證資訊,如果有一個失敗就失敗了 |
AtLeastOneSuccessfulStrategy(預設) | 只要有一個Realm驗證成功即可,和FirstSuccessfulStrategy 不同,將返回所有Realm身份驗證成功的認證資訊 |
FirstSuccessfulStrategy | 只要有一個 Realm 驗證成功即可,只返回第一個 Realm 身份驗證成功的認證資訊,其他的忽略; |
ModularRealmAuthenticator內建的認證策略預設實現是AtLeastOneSuccessfulStrategy 方式,因為這種方式也是被廣泛使用的一種認證策略。
2.1 預設使用
驗證下AtLeastOneSuccessfulStrategy:通過原始碼的方法驗證
①為了看效果,修改下 SecondRealm的認證訊息的返回值
SimpleAuthenticationInfo info= new SimpleAuthenticationInfo("SecondRealmName", credentials, credentialsSalt, realmName);
②debug方式啟動專案,輸入 使用者名稱和密碼,點選submit,進入如下斷點,可以看到 認證策略:
預設AtLeastOneSuccessfulStrategy策略下,有一個Realm認證成功就可以了。就算另一個Realm認證失敗也沒關係。
2.1 切換認證策略
(1)如何切換認證策略?
比如:切換成 AllSuccessfulStrategy 即所有認證策略都通過了,才算認證成功。
答:可以看出 認證策略是ModularRealmAuthenticator 類的一個屬性 authenticationStrategy,即在ShiroConfig配置類中新增配置:
/**
* 系統自帶的Realm管理,主要針對多realm
*/
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
//設定認證策略
modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
return modularRealmAuthenticator;
}
並在securityManager方法中設定這個modularRealmAuthenticator()方法:
即可。
(2)驗證AllSuccessfulStrategy:
打斷點,debug重啟專案,進行驗證:發現第二個Realm報異常
登入失敗
上一篇:SpringBoot 與Shiro 整合系列(二)實現MD5鹽值加密
延伸:
springboot整合shiro實現多realm不同資料表登陸
Spring Boot 整合Shiro的多realm配置
相關文章
- SpringBoot 整合 Shiro 密碼登入與郵件驗證碼登入(多 Realm 認證)Spring Boot密碼
- springboot整合shiro實現身份認證Spring Boot
- springboot(十四):springboot整合shiro-登入認證和許可權管理Spring Boot
- Spring整合shiro做登陸認證Spring
- springboot + shiro 驗證碼與記住登入Spring Boot
- shiro授權和認證(四)
- Shiro入門學習---使用自定義Realm完成認證|練氣中期
- SpringBoot-shiro登入攔截.使用者認證.整合MybatisSpring BootMyBatis
- 關於shiro安全框架和shiro的認證流程框架
- springboot + shiro 實現登入認證和許可權控制Spring Boot
- SpringBoot--- Shiro(攔截,認證)、Thymeleaf(模板引擎)Spring Boot
- Shiro實現Basic認證
- SpringBoot整合JWT做身份驗證Spring BootJWT
- Shiro【授權過濾器、與ehcache整合、驗證碼、記住我】過濾器
- [翻譯-Shiro]-Apache Shiro Java認證指南ApacheJava
- Spring Boot 整合 Shiro實現認證及授權管理Spring Boot
- 【認證與授權】Spring Security系列之認證流程解析Spring
- SpringBoot + Spring Security 學習筆記(三)實現圖片驗證碼認證Spring Boot筆記
- 圖解Jwt和shiro認證方式的區別圖解JWT
- Django整合OpenLDAP認證DjangoLDA
- Shiro(認證的執行流程Authentication)
- Shiro學習-認證思路分析(七)
- 【Shiro】5.多個Realm的使用和實現
- SpringBoot整合Shiro+MD5+Salt+Redis實現認證和動態許可權管理(上)----築基中期Spring BootRedis
- LDAP落地實戰(三):GitLab整合OpenLDAP認證LDAGitlab
- Spring boot 入門(四):整合 Shiro 實現登陸認證和許可權管理Spring Boot
- Spring Boot (十四): Spring Boot 整合 Shiro-登入認證和許可權管理Spring Boot
- asp.net core 3.1多種身份驗證方案,cookie和jwt混合認證授權ASP.NETCookieJWT
- SpringBoot 整合 JWT 實現 token 驗證,token 登出Spring BootJWT
- Springboot 整合ApachShiro完成登入驗證和許可權管理Spring Boot
- URL載入系統之四:認證與TLS鏈驗證TLS
- SpringBoot安全認證SecuritySpring Boot
- shiro多realm配置免密碼登陸密碼
- Oracle中兩種認證方式:OS認證與口令檔案認證Oracle
- 策略模式下表單驗證模式
- SpringBoot+Shiro學習(四):Realm授權Spring Boot
- laravel5.6 RESTful API系列之整合github登入認證!LaravelRESTAPIGithub
- 中介軟體---登陸認證授權---Shiro