今天花了時間學了下shiro整合springboot,學得很淺,這裏記一下筆記。
maven配置和spring的配置就不寫在這了。
直奔主題。
在spingboot裏面整合shiro主要有兩個類:
一是:自定義的Realm類,咱的授權邏輯和認證邏輯代碼都寫在這裏面。
二是:Shiro的配置類,這裏面做的主要是下面三點。
Shiro的核心API
Subject: 用戶主體(把操作交給SecurityManager)
SecurityManager:安全管理器(關聯Realm)
Realm:Shiro連接數據的橋樑
這裏給上這兩個類的基本代碼:
Realm類:
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
* 自定義Realm
* @author lenovo
*
*/
public class UserRealm extends AuthorizingRealm{
/**
* 執行授權邏輯
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
System.out.println("執行授權邏輯");
return null;
}
/**
* 執行認證邏輯
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
System.out.println("執行認證邏輯");
return null;
}
}
doGetAuthorizationInfo
: 權限認證,即登錄過後,每個身份不一定,對應的所能看的頁面也不一樣。doGetAuthenticationInfo
:身份認證。即登錄通過賬號和密碼驗證登陸人的身份信息。
比如身份認證裏面,就可以寫好對賬號密碼的判斷,通過參數裏面的Token拿出來,判斷賬號是否存在,比如下面所示。
然後我們在寫業務代碼的時候調用Subject.login()就可以判斷賬號是非存在了。
(下面的salt是用來解碼的,因爲咱在存密碼的時候進行了加密操作,後邊會提到。)
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = token.getPrincipal().toString();
User user=userService.getByName(userName);
String passwordInDB=user.getPassword();
String salt = user.getSalt();
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName, passwordInDB, ByteSource.Util.bytes(salt),
getName());
return authenticationInfo;
}
shiro配置類:
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;
/**
* Shiro的配置類
* @author lenovo
*
*/
@Configuration
public class ShiroConfig {
/**
* 創建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//設置安全管理器
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
/**
* 創建DefaultWebSecurityManager
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//關聯realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 創建Realm
*/
@Bean(name="userRealm")
public UserRealm getRealm(){
return new UserRealm();
}
}
這裏面是最基本的三個方法,必要的,
Shiro內置過濾器實現頁面攔截,如下:
import java.util.LinkedHashMap;
import java.util.Map;
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;
/**
* Shiro的配置類
* @author lenovo
*
*/
@Configuration
public class ShiroConfig {
/**
* 創建ShiroFilterFactoryBean
*/
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@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("/*", "authc");
//修改調整的登錄頁面
shiroFilterFactoryBean.setLoginUrl("/toLogin");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
return shiroFilterFactoryBean;
}
/**
* 創建DefaultWebSecurityManager
*/
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//關聯realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 創建Realm
*/
@Bean(name="userRealm")
public UserRealm getRealm(){
return new UserRealm();
}
}
下面代碼是Realm裏面的授權邏輯部分,處理上面進行了授權攔截的訪問,首先在數據庫設置一個字段Perms,表示該user的權限,然後給info加上這個授權字符串,
比如一個user: name=java Perms=user:add,那麼在訪問時被config裏面的代碼攔截了,但是攔截的訪問都會運行到Realm裏面的,在這裏面又給他進行了授權,那就能正常訪問了。(我自己寫的項目並沒有涉及權限,所以這一塊理解不深)
然後還有密碼採用加密,在config裏面加上下面代碼:
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher=new HashedCredentialsMatcher();
// 散列算法:這裏使用MD5算法;
hashedCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次數,比如散列兩次,相當於 md5(md5(""));
hashedCredentialsMatcher.setHashIterations(2);
return hashedCredentialsMatcher;
}
然後在獲取Realm部分需要改變:
@Bean(name="userRealm")
public UserRealm getRealm(){
UserRealm myShiroRealm=new UserRealm();
UserRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return UserRealm;
}
}
流程是這樣的,用戶註冊的時候,程序將明文通過加密方式加密,存到數據庫的是密文,登錄時將密文取出來,再通過shiro將用戶輸入的密碼進行加密對比,一樣則成功,不一樣則失敗。
我們可以看到這裏的加密採用的是MD5
,而且是加密兩次(MD5(MD5)
)。
shiro提供了SimpleHash類幫助我們快速加密
列子:
註冊:
@PostMapping("/foreregister")
public Object register(@RequestBody User user) {
String name = user.getName();
String password = user.getPassword();
name = HtmlUtils.htmlEscape(name);
user.setName(name);
boolean exist = userService.isExist(name);
if(exist){
String message ="用戶名已經被使用,不能使用";
return Result.fail(message);
}
String salt =new SecureRandomNumberGenerator().nextBytes().toString();
int times= 2;
String algorithmName="md5";
String encodedPassword=new SimpleHash(algorithmName,password,salt,times).toString();
user.setSalt(salt);
user.setPassword(encodedPassword);
userService.add(user);
return Result.success();
}
這是構造salt和加密密碼存入數據庫,然後我們在登錄認證的時候進行解碼比較:
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = token.getPrincipal().toString();
User user=userService.getByName(userName);
String passwordInDB=user.getPassword();
String salt = user.getSalt();
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName, passwordInDB, ByteSource.Util.bytes(salt),
getName());
return authenticationInfo;
}
這就實現了加密操作。