18.SSM框架集~Shiro
本文是上一篇文章的後續,詳情點擊該鏈接
Apache Shiro 是一個強大而靈活的開源安全框架,它乾淨利落地處理身份認證,授權,企業會話管理和加密
Shiro官網Shiro 中的體系的組成
Authentication:身份的驗證-就是我們平時做的登錄
Authorization:授權:賦予角色不同的 菜單功能
SessionManagement:管理登錄用戶的信息
Cryptography:加密技術 MD5加密算法等
WebSupport:shiro 對 web 項目進行的支持
Caching:緩存 可以安全快速的操作
Concurrency:支持併發多線程的處理
Testing:測試
RunAs:可以實現在一個用戶允許的前提下,使用另一個用戶訪問
RememberMe:記住我
簡單來說,Shiro是對RBAC的一個封裝
Shiro 的環境搭建
先來段代碼感受一下
導包
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.5.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-web -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.5.3</version>
</dependency>
配置文件 shiro.ini
[users]
uid=123
pwd=root
package com.alvin.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class Test {
public static void main(String[] args) {
//解析shiro.ini文件
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//通過SecurityManager工廠獲得SecurityManager實例
SecurityManager securityManager = factory.getInstance();
//把SecurityManager對象設置到運行環境中
SecurityUtils.setSecurityManager(securityManager);
//通過SecurityManager 獲得主體對象subject
Subject subject = SecurityUtils.getSubject();
//書寫自己輸入的賬號和密碼---相當於用戶自己輸入的賬號和密碼
UsernamePasswordToken token = new UsernamePasswordToken("uid","123");
try {
//進行身份驗證
subject.login(token);
//通過方法判斷是否登錄成功
if(subject.isAuthenticated()){
System.out.println("登錄成功");
}
} catch (IncorrectCredentialsException e) {
System.out.println("登錄失敗");
}catch (UnknownAccountException e){
System.out.println("用戶名不正確");
}
}
}
Shiro異常分析
DisabledAccountException
賬戶失效異常
ConcurrentAccessException
競爭次數過多
ExcessiveAttemptsException
嘗試次數過多
UnknownAccountException
用戶名不正確
IncorrectCredentialsException
憑證(密碼)不正確
ExpiredCredentialsException
憑證過期
shiro中的JDBCRealm
import org.apache.shiro.realm.jdbc.JdbcRealm;
底層寫好的SQL語句
我們現在來新建一個數據庫操作一下
添加依賴
<!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.4</version>
</dependency>
從剛剛的源碼我們知道,我們創建的數據庫名必須和源碼裏面的一樣,而且也必須包含源碼裏面涉及的字段纔可以
新建users
新建配置文件shiro-jdbc.ini
[main]
#獲得數據源
dataSou=com.mchange.v2.c3p0.ComboPooledDataSource
dataSou.driverClass=com.mysql.cj.jdbc.Driver
dataSou.jdbcUrl=jdbc:mysql://127.0.0.1:3306/shiro?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
dataSou.user=root
dataSou.password=root
#配置了jdbcRealm
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource=$dataSou
#設置了securityManager
securityManager.realm=$jdbcRealm
package com.alvin.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class TestS {
public static void main(String[] args) {
//解析shiro.ini文件
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-jdbc.ini");
//通過SecurityManager工廠獲得SecurityManager實例
SecurityManager securityManager = factory.getInstance();
//把SecurityManager對象設置到運行環境中
SecurityUtils.setSecurityManager(securityManager);
//通過SecurityManager 獲得主體對象subject
Subject subject = SecurityUtils.getSubject();
//書寫自己輸入的賬號和密碼---相當於用戶自己輸入的賬號和密碼
UsernamePasswordToken token = new UsernamePasswordToken("黃貴根","123");
try {
//進行身份驗證
subject.login(token);
//通過方法判斷是否登錄成功
if(subject.isAuthenticated()){
System.out.println("登錄成功");
}
} catch (IncorrectCredentialsException e) {
System.out.println("登錄失敗");
}catch (UnknownAccountException e){
System.out.println("用戶名不正確");
}
}
}
認證策略
AtLeastOneSuccessfulStrategy
如果一個(或更多)Realm 驗證成功,則整體的嘗試被認爲是成功的。如果沒有一個驗證成功,則整體嘗試失敗 類似於 java 中的 &
FirstSuccessfulStrategy
只有第一個成功地驗證的 Realm 返回的信息將被使用。所有進一步的 Realm 將被忽略。如果沒有一個驗證成功,則整體嘗試失敗。類似於 java 中的 &&
AllSucessfulStrategy
爲了整體的嘗試成功,所有配置的 Realm 必須驗證成功。如果沒有一個驗 證成功,則整體嘗試失敗
[main]
#獲得數據源
dataSou=com.mchange.v2.c3p0.ComboPooledDataSource
dataSou.driverClass=com.mysql.cj.jdbc.Driver
dataSou.jdbcUrl=jdbc:mysql://127.0.0.1:3306/shiro?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
dataSou.user=root
dataSou.password=root
#配置了jdbcRealm
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource=$dataSou
#配置驗證器
authenticationStrategy=org.apache.shiro.authc.pam.FirstSuccessfulStrategy
#設置了securityManager
securityManager.realm=$jdbcRealm
securityManager.authenticator.authenticationStrategy=$authenticationStrategy
自定義realm
我們使用 JDBCRealm 的時候發現,shiro 的底層自己封裝了數據庫表的名稱和字段的名稱,這樣就造成了使用起來非常不方便
新建一個表 admin
Realm接口
ctrl + h選中點擊查看實現類
UserRealm
package com.alvin.shiroplus;
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.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.sql.*;
public class UserRealm extends AuthorizingRealm {
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai", "root", "root");
preparedStatement = connection.prepareStatement("select * from admin");
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){ //這個名字隨便定義
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(resultSet.getString("uname"),resultSet.getString("pwd"),"userRealm");
return info;
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try {
connection.close();
preparedStatement.close();
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
配置shiro-jdbcPlusPro.ini
[main]
#設置securityManager中realm
userRealm=com.alvin.shiroplus.UserRealm
securityManager.realms=$userRealm
package com.alvin.test;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class Test {
public static void main(String[] args) {
//解析shiro.ini文件
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-jdbcPlusPro.ini");
//通過SecurityManager工廠獲得SecurityManager實例
SecurityManager securityManager = factory.getInstance();
//把SecurityManager對象設置到運行環境中
SecurityUtils.setSecurityManager(securityManager);
//通過SecurityManager 獲得主體對象subject
Subject subject = SecurityUtils.getSubject();
//書寫自己輸入的賬號和密碼---相當於用戶自己輸入的賬號和密碼
UsernamePasswordToken token = new UsernamePasswordToken("黃貴根","123");
try {
//進行身份驗證
subject.login(token);
//通過方法判斷是否登錄成功
if(subject.isAuthenticated()){
System.out.println("登錄成功");
}
} catch (IncorrectCredentialsException e) {
System.out.println("登錄失敗");
}catch (UnknownAccountException e){
System.out.println("用戶名不正確");
}
}
}
加密算法
在身份認證的過程中往往都會涉及到加密,如果不加密,這個時候信息就會非常的不安全,shiro 中提供的算法比較多 如 MD5SHA 等
使用Md5加密
package com.alvin.test;
import org.apache.shiro.crypto.hash.Md5Hash;
public class Test {
public static void main(String[] args) {
//使用MD5加密
Md5Hash md5Hash = new Md5Hash("3696");
System.out.println("3696 = " + md5Hash);
//加鹽
md5Hash = new Md5Hash("3696","alvin");
System.out.println("3696 = " + md5Hash);
//迭代次數
md5Hash = new Md5Hash("3696","alvin",2);
System.out.println("3696 = " + md5Hash);
}
}
配置shiro-jdbcPlusProMax.ini
[main]
#設置securityManager中realm
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
credentialsMatcher.hashAlgorithmName=md5
credentialsMatcher.hashIterations=2
userRealm=com.alvin.test.UserRealm
userRealm.credentialsMatcher=$credentialsMatcher
securityManager.realms=$userRealm
userRealm
package com.alvin.test;
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.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import java.sql.*;
public class UserRealm extends AuthorizingRealm {
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai", "root", "root");
preparedStatement = connection.prepareStatement("select * from admin");
resultSet = preparedStatement.executeQuery();
while(resultSet.next()){ //這裏寫鹽值
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(resultSet.getString("uname"),resultSet.getString("pwd"), ByteSource.Util.bytes("alvin"),"userRealm");
return info;
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try {
connection.close();
preparedStatement.close();
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return null;
}
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
}
Test測試
package com.alvin.test;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class Test {
public static void main(String[] args) {
//解析shiro.ini文件
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-jdbcPlusProMax.ini");
//通過SecurityManager工廠獲得SecurityManager實例
SecurityManager securityManager = factory.getInstance();
//把SecurityManager對象設置到運行環境中
SecurityUtils.setSecurityManager(securityManager);
//通過SecurityManager 獲得主體對象subject
Subject subject = SecurityUtils.getSubject();
//書寫自己輸入的賬號和密碼---相當於用戶自己輸入的賬號和密碼
UsernamePasswordToken token = new UsernamePasswordToken("黃貴根","3696");
try {
//進行身份驗證
subject.login(token);
//通過方法判斷是否登錄成功
if(subject.isAuthenticated()){
System.out.println("登錄成功");
}
} catch (IncorrectCredentialsException e) {
System.out.println("登錄失敗");
}catch (UnknownAccountException e){
System.out.println("用戶名不正確");
}
}
}
授權
授權:給身份認證通過的人授予某些資源的訪問權限
權限的粒度我們一般分爲兩種,一種是粗粒度,一種是細粒度
粗粒度通常指的是表的操作,比如User 具有 CRUD 的操作
而細粒度的話,則是使用業務層代碼的實現。比如只允許查詢 id=1 的用戶
授權說白了也是CRUD操作,和RBAC差不多,所以也是粗粒度
角色則是權限的集合
代碼走起來~
shiro.ini
#指定具體的用戶
[users]
uid=123,role1,role2
pwd=root
#角色的定義
[roles]
role1=add,update,delete
role2=find
Java代碼
package com.alvin.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
//解析shiro.ini文件
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//通過SecurityManager工廠獲得SecurityManager實例
SecurityManager securityManager = factory.getInstance();
//把SecurityManager對象設置到運行環境中
SecurityUtils.setSecurityManager(securityManager);
//通過SecurityManager 獲得主體對象subject
Subject subject = SecurityUtils.getSubject();
//書寫自己輸入的賬號和密碼---相當於用戶自己輸入的賬號和密碼
UsernamePasswordToken token = new UsernamePasswordToken("uid","123");
try {
//進行身份驗證
subject.login(token);
} catch (IncorrectCredentialsException e) {
System.out.println("登錄失敗");
}
//授權的查詢
//基於角色的授權
boolean role1 = subject.hasRole("role1");
System.out.println(role1);
//判斷是否具有多個角色
boolean[] booleans = subject.hasRoles(Arrays.asList("role1", "role3"));
for(boolean flag : booleans){
System.out.println(flag);
}
//可以使用checkRole判斷指定用戶是否具有對應的角色
//如果沒有指定角色,就會拋出異常
subject.checkRole("role3"); //3是不存在的角色
}
}
//基於資源的授權
boolean permitted = subject.isPermitted("add");
System.out.println(permitted);
//判斷是否具有多個資源
boolean permittedAll = subject.isPermittedAll("add", "bob", "alice");
System.out.println(permittedAll);
//判斷用戶是否有指定資源,如果沒有就拋出異常
subject.checkPermissions("uid","hello");
自定義Realm完成授權
shiro.ini
[main]
#設置securityManager中realm
userRealm=com.alvin.test.UserRealm
securityManager.realms=$userRealm
UserRealm
package com.alvin.test;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
public class UserRealm extends AuthorizingRealm {
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro?useSSL=false&useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai", "root", "root");
PreparedStatement prepareStatement = conn.prepareStatement("select pwd from admin where uname =? ");
prepareStatement.setObject(1,authenticationToken.getPrincipal());
ResultSet rs = prepareStatement.executeQuery();
while (rs.next()){
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(authenticationToken.getPrincipal(),rs.getString("pwd"),"userRealm");
return info;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String username = principalCollection.getPrimaryPrincipal().toString();
//獲得username 然後去數據庫查詢這個用戶對應的角色,在根據角色查詢出指定角色下對應的菜單,
//返回給指定角色下的所有菜單--List集合
System.out.println("username="+username);
//模擬數據庫查的菜單
List<String> list =new ArrayList<>();
list.add("updateUser");
list.add("addUser");
list.add("deleteUser");
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
for(String l:list){
simpleAuthorizationInfo.addStringPermission(l);
}
return simpleAuthorizationInfo;
}
}
Test
package com.alvin.test;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
public class Test {
public static void main(String[] args) {
/*Realm*/
//解析shiro.ini文件
Factory<SecurityManager> factory =new IniSecurityManagerFactory("classpath:shiro.ini");
//通過SecurityManager工廠獲得SecurityManager實例
SecurityManager securityManager = factory.getInstance();
//把SecurityManager對象設置到運行環境中
SecurityUtils.setSecurityManager(securityManager);
//通過SecurityUtils獲得主體subject
Subject subject = SecurityUtils.getSubject();
//書寫自己輸入的賬號和密碼---相當於用戶自己輸入的賬號和密碼
//我們拿着自己書寫用戶名密碼去和shiro.ini 文件中的賬號密碼比較
UsernamePasswordToken token =new UsernamePasswordToken("黃貴根","3696");
try {
//進行身份的驗證
subject.login(token);
//通過方法判斷是否登錄成功
if(subject.isAuthenticated()){
System.out.println("登錄成功");
//授權的校驗
System.out.println("是否存在該菜單:"+subject.isPermitted("updateUser2123"));
}
} catch (IncorrectCredentialsException e) {
System.out.println("登錄失敗");
}catch (UnknownAccountException e){
System.out.println("用戶名不正確");
}
}
}