網上有很多講Shiro的概念的博客,大家自行補腦,我這邊就講一個案例:Shiro的認證,授權,自定義Realm。
Apache Shiro與Spring Security區別簡述
總結了一句話:Shiro 比 Security 配置簡單很多,安全性上雖然沒有 Security強大,但對於一般的項目已經足夠了。
Shiro 的工作流程
百度找了一個比較權威的AV畫質的架構圖,你肯定也見過這個圖,被嚇到了的話我帶着你讀懂這個圖,請問作者你話是不是多,爲什麼講案例要這個圖?這是Shiro的架構,代碼也是靠這個實現的。
第一排的編程語言(Shiro 爲了突出他們支持很多語言)的方框上都有一個 Subject (主體),主體相當於‘用戶對象’與 中間的那層 Security Manager 打交道,Security Manager是 Shiro 的核心。這個裏面有很多的組件:
1、Authenticator(認證器):管理登入登出;
2、Authorizer(授權器):給主體授權;
3、Session Manager(Session 管理器):Shiro自己實現了一套Session機制,可以藉助於任何外部容器的情況下使用Session;
4、Session DAO:提供了對Session的一些操作如crud;
5、Cache Manager(緩存管理器):主要用戶緩存角色數據和權限數據等;
6、Realm:與數據庫(如MySql)打交道,Realm主要用於獲取數據庫的數據表裏邊的角色信息(roles),權限信息(permissions)等。
還有最右邊的Cryptographic ,字面意思加密用途。
Shiro的認證,授權,自定義Realm編碼實現
代碼是按照這個流程實現的。
POM
<!-- Shiro 相關 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
CustomRealm.java
public class CustomRealm extends AuthorizingRealm {
private Map<String,String > userMap = new HashMap<>();
{
userMap.put("admin","123456");
super.setName("CustomRealm");
}
/**
* 登錄身份驗證
* @param authenticationToken 賬號密碼生成的token
* @return 驗證結果
* @throws AuthenticationException 驗證失敗
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
// 1.獲取主體中傳過來的認證信息中的用戶名
String username = authenticationToken.getPrincipal().toString();
// 2.通過用戶名到數據庫中的憑證
String pwd = getPasswordByUsername(username);
if (pwd == null){
return null;
}
SimpleAuthenticationInfo AuthenticationInfo = new
SimpleAuthenticationInfo("admin","123456","CustomRealm");
return AuthenticationInfo;
}
/**
* 權限驗證
* @param principalCollection principalCollection
* @return 驗證結果
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
// 1、從認證信息中獲取用戶
String username = (String) principalCollection.getPrimaryPrincipal();
Set<String> roles = getRolesByUsername(username);
Set<String> permissions = getPermissionsByUsername(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
return authorizationInfo;
}
/**
* 下面三個是模擬從數據中獲取數據的
*/
private Set<String> getPermissionsByUsername(String username) {
Set<String> permissionSet = new HashSet<>();
permissionSet.add("user:delete");
permissionSet.add("user:update");
return permissionSet;
}
private String getPasswordByUsername(String username) {
return userMap.get(username);
}
private Set<String> getRolesByUsername(String username) {
Set<String> roleSet = new HashSet<>();
roleSet.add("admin");
roleSet.add("user");
return roleSet;
}
}
CustomRealmTest.java
public class CustomRealmTest {
public static void main(String[] args) {
CustomRealm customRealm = new CustomRealm();
// 1、構建SecurityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(customRealm);
//2、主體提交認證
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("admin","123456");
subject.login(token);
System.out.println("=====\n"+token+"\n======");
System.out.println("isAuthenticated: "+subject.isAuthenticated());
subject.checkRole("admin");
subject.checkPermission("user:delete");
subject.checkPermission("user:update");
subject.checkPermission("user:add");
}
}
另外在送一個知識點,加鹽加密:
在上面的 CustomRealmTest.java 構建完了SecurityManager的代碼之後加入以下代碼,就相當於是客戶端加密之後上傳加密後的密碼到服務器:
// 加鹽加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5");
matcher.setHashIterations(1);
customRealm.setCredentialsMatcher(matcher);
服務器端加密在Realm 把比對的“鹽”設置一下即可:
在 AuthenticationInfo 裏的 SimpleAuthenticationInfo 下面
AuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes("hello"));
記得把模擬數據也換成密文。