一.認識shiro
Apache Shiro是一個強大且易用的Java安全框架,有身份驗證、授權、密碼學和會話管理。
- 輕量級的權限框架
- shiro(輕量級,粗粒度) , Spring security(細粒度)
- RBAC:權限(登錄,授權) 用戶(n)-角色(n)-權限(n)(資源)
1.1 shiro的四大基石
- 身份認證(登錄) Authentication
- 授權(權限) Authorization
- 密碼學 Cryptography
-
過使用加密算法保持數據安全同時易於使用。
-
- 會話管理 Session Management
-
管理用戶特定的會話,即使在非 Web 或 EJB 應用程序。
-
1.2 重要的對象
- Subject:當前用戶
- SecurityManager:權限管理器(所有功能管理)
- Realm:獲取權限數據
二.Hello案例
建一個普通的maven項目
2.1 導包
<!--使用shiro需要先導包-->
<dependencies>
<!--shiro的核心包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<!--日誌包-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!--測試包-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
</dependencies>
2.2 ini文件
文件中有咱們的用戶角色權限
# ini文件裏面放的就是咱們的用戶,角色,權限,資源
# -----------------------------------------------------------------------------
# users:用戶
# root:用戶名 123456:密碼 admin:角色
# -----------------------------------------------------------------------------
[users]
root = 123456, admin
guest = guest, it
# -----------------------------------------------------------------------------
# roles:角色
# admin = * :admin這個用戶擁有所有權限
# it = employee:* :it這個角色擁有員工的所有權限
# hr = employee:save :hr這個角色擁有員工添加權限
# -----------------------------------------------------------------------------
[roles]
admin = *
it = employee:*
hr = employee:save
2.3 功能測試
主要測試登錄,權限認證;認識API
@Test
public void testHello() throws Exception{
//①.拿到權限管理對象
/**
* 讀取了shiro.ini的文件(隱藏了realm) -> 隱藏了iniRealm
* SecurityManager:權限管理器,shiro的所有功能都放在裏面
*/
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
//②.相當於把SecurityManager放到了當前上下文
/**
* 可以讓我們在當前系統的任何位置都可以拿到SecurityManager對象
*/
SecurityUtils.setSecurityManager(securityManager);
//③.拿到當前用戶(沒有登錄就是遊客)
Subject currentUser = SecurityUtils.getSubject();
System.out.println("用戶是否登錄:"+currentUser.isAuthenticated());
//④.如果沒有登錄,讓他進行登錄
if(!currentUser.isAuthenticated()){
//ctrl+alt+t :包含代碼
try {
//4.1 準備令牌(對象) 用戶名密碼令牌
UsernamePasswordToken token = new UsernamePasswordToken("guest","guest");
//4.2 進行登錄功能
currentUser.login(token);
} catch (UnknownAccountException e) {
//Unknown(未知)Account(賬號)Exception:用戶名不存在
e.printStackTrace();
System.out.println("哥,你是傻子嘛?");
}catch (IncorrectCredentialsException e){
//Incorrect(不正確)Credentials(憑證)Exception:密碼錯誤
e.printStackTrace();
System.out.println("哥,密碼錯誤了?");
}catch (AuthenticationException e){
//AuthenticationException:登錄中最大的那個異常
e.printStackTrace();
System.out.println("發生了一個神祕的錯誤!!!");
}
}
System.out.println("用戶是否登錄:"+currentUser.isAuthenticated());
System.out.println("是否是管理員角色:"+currentUser.hasRole("admin"));
System.out.println("是否是IT角色:"+currentUser.hasRole("it"));
System.out.println("是否可以操作employee:save權限:"+currentUser.isPermitted("employee:save"));
System.out.println("是否可以操作employee:index權限:"+currentUser.isPermitted("employee:index"));
System.out.println("是否可以操作department:index權限:"+currentUser.isPermitted("department:index"));
//⑤.還可以登出(註銷)
currentUser.logout();
System.out.println("用戶是否登錄:"+currentUser.isAuthenticated());
}
三.自定義Realm
剛纔咱們使用的是準備的好的一個Realm,在Shiro中,除了咱們 剛纔使用的iniRealm,還有普通SimpleAccountRealm,JDBCRealm等等.
但是這些Realm都不能滿足咱們的需求,而這種情況下,就需要我們自己定義一個Realm來實現相應的功能!
3.1 準備自定義Realm
- 寫一個Realm,繼承
AuthorizingRealm
- 提供了兩個方法,一個是授權
doGetAuthorizationInfo
,一個是身份認證doGetAuthenticationInfo
public class MyRealm extends AuthorizingRealm {
//授權認證功能就寫在這裏面
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//從數據庫中獲取角色並放且放到授權對象中
Set<String> roles = getRoles();
authorizationInfo.setRoles(roles);
//從數據庫中獲取權限並放且放到授權對象中
Set<String> perms = getPerms();
authorizationInfo.setStringPermissions(perms);
return authorizationInfo;
}
/**
* 假設這裏獲取到當前用戶的角色
*/
private Set<String> getRoles(){
Set<String> roles = new HashSet<>();
roles.add("admin");
roles.add("it");
return roles;
}
/**
* 假設這裏獲取到當前用戶的權限
*/
private Set<String> getPerms(){
Set<String> perms = new HashSet<>();
perms.add("employee:index");
return perms;
}
/**
* 記住:如果這個方法返回null,就代表是用戶名錯誤,shiro就會拋出:UnknownAccountException
*/
//身份認證(登錄)就寫在這裏面
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.拿到令牌(UsernamePasswordToken)
UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
//2.拿到用戶名,判斷這個用戶是否存在
// 2.1 拿到傳過來的用戶名
String username = token.getUsername();
// 2.2 根據用戶名從數據庫中拿到密碼(以後會拿用戶對象)
String password = this.getUsers(username);
// 2.3 如果沒有拿到密碼(沒有通過用戶名拿到相應的用戶->用戶不存在)
if(password==null){
return null;
}
//記住:我們只在正常完成這裏的功能,shiro會判斷密碼是否正確
//3.返回 AuthenticationInfo這個對象
/**
* 咱們創建對象需要傳的參數:
* Object principal:主體(可以亂寫) -> 登錄成功後,你想把哪一個對象存下來
* Object credentials:憑證(就是密碼) -> 數據庫中的密碼
* credentials(密碼)Salt:鹽值
* String realmName : realm的名稱(可以亂寫)
*/
//拿到咱們的鹽值對象(ByteSource)
ByteSource salt = ByteSource.Util.bytes("itsource");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password,salt,"myRealm");
return authenticationInfo;
}
/**
* 假設這裏是根據用戶名進行的查詢
* MD5:e10adc3949ba59abbe56e057f20f883e
* MD5+10次:4a95737b032e98a50c056c41f2fa9ec6
* MD5+10次+itsource:831d092d59f6e305ebcfa77e05135eac
*/
public String getUsers(String username){
if("admin".equals(username)){
return "831d092d59f6e305ebcfa77e05135eac";
}else if("zhang".equals(username)){
return "123";
}
return null;
}
}
3.2 測試自定義Realm
@Test
public void testMyRealm() throws Exception{
//一.創建一個SecurityManager對象
// 1.創建realm對象
MyRealm myRealm = new MyRealm();
// 2.創建SecurityManager對象
DefaultSecurityManager securityManager = new DefaultSecurityManager(myRealm);
//②.相當於把SecurityManager放到了當前上下文
SecurityUtils.setSecurityManager(securityManager);
//③.拿到當前用戶
Subject subject = SecurityUtils.getSubject();
//Hashed(哈希)Credentials(認證)Matcher(匹配器)
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//設置哈希算法
matcher.setHashAlgorithmName("MD5");
//設置迭代次數
matcher.setHashIterations(10);
//把匹配器交給shiro
myRealm.setCredentialsMatcher(matcher);
System.out.println("用戶是否登錄:"+subject.isAuthenticated());
//④.如果沒有登錄,讓他登錄
if(!subject.isAuthenticated()){
try {
UsernamePasswordToken token = new UsernamePasswordToken("admin","123456");
subject.login(token);
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用戶名錯誤");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
System.out.println("密碼錯誤");
} catch (AuthenticationException e) {
e.printStackTrace();
System.out.println("神迷錯誤");
}
}
System.out.println("用戶是否登錄:"+subject.isAuthenticated());
System.out.println("是否是admin角色:"+subject.hasRole("admin"));
System.out.println("是否是hr角色:"+subject.hasRole("hr"));
System.out.println("是否有employee:index權限:"+subject.isPermitted("employee:index"));
System.out.println("是否有employee:save權限:"+subject.isPermitted("employee:save"));
System.out.println("是否有department:index權限:"+subject.isPermitted("department:index"));
}