1、核心依賴
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
2、認證流程:創建SecurityManager-->主體提交認證-->SecurityMananger認證-->Authentictor認證-->Realm驗證(從subject.login(token)開始跟蹤源碼可以驗證(idea下ctrl+alt+b跟蹤源碼)),單元測試代碼如下:
package com.example.demo_mg;
import junit.framework.Assert;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoMgApplicationTests {
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
@Before
public void before() {
simpleAccountRealm.addAccount("wzs", "123456");
}
@Test
public void contextLoads() {
}
@Test
public void test() {
//構建SecurityManager環境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm);
//主體提交認證請求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
subject.login(token);
Assert.assertEquals(subject.isAuthenticated(), true);
subject.logout();
Assert.assertEquals(subject.isAuthenticated(), true);
}
}
賬號錯誤UnknownAccountException,密碼錯誤IncorrectCredentialsException。
從subject.login(token);點擊ctrl+alt+b跟蹤源碼到DelegatingSubject的login方法,調用Subject subject = this.securityManager.login(this, token);,繼續跟蹤到DefaultSecurityManager的login方法,調用info = this.authenticate(token);,繼續跟蹤到AuthenticatingSecurityManager的authenticate方法,調用this.authenticator.authenticate(token);,繼續跟蹤到AbstractAuthenticator的authenticate方法,調用info = this.doAuthenticate(token);,繼續跟蹤到ModularRealmAuthenticator的doAuthenticate方法,可以看到如下認證代碼,通過realm實現認證:
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
this.assertRealmsConfigured();
Collection<Realm> realms = this.getRealms();
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
3、授權流程:創建SecurityManager-->主體授權-->SecurityManager授權-->Authorizer授權-->Realm獲取角色權限數據(數據庫|緩存等):
package com.example.demo_mg;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.SimpleAccountRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoMgApplicationTests {
SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
@Before
public void before() {
simpleAccountRealm.addAccount("wzs", "123456", "admin", "user");
}
@Test
public void contextLoads() {
}
@Test
public void test() {
//構建SecurityManager環境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(simpleAccountRealm);
//主體提交認證請求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
subject.login(token);
//授權
subject.checkRole("admin");
subject.checkRoles("admin", "user");
subject.logout();
}
}
角色不存在UnauthorizedException
從subject.checkRoles("admin", "user");點擊ctrl+alt+b跟蹤源碼到DelegatingSubject的checkRoles方法,調用this.securityManager.checkRoles(this.getPrincipals(), roleIdentifiers);,繼續跟蹤到AuthorizingSecurityManager的checkRoles方法,調用this.authorizer.checkRoles(principals, roles);,繼續跟蹤到ModularRealmAuthorizer的checkRoles方法,遍歷roles調用this.checkRole(principals, role);,繼續跟蹤到checkRole方法,調用this.hasRole(principals, role),繼續跟蹤到hasRole方法,可見如下代碼,通過realm授權:
public boolean hasRole(PrincipalCollection principals, String roleIdentifier) {
this.assertRealmsConfigured();
Iterator var3 = this.getRealms().iterator();
Realm realm;
do {
if (!var3.hasNext()) {
return false;
}
realm = (Realm)var3.next();
} while(!(realm instanceof Authorizer) || !((Authorizer)realm).hasRole(principals, roleIdentifier));
return true;
}
4、測試內置的IniRealm:
在src/test下新建Direcotry,名稱resources,右鍵Mark Direcotry As-->Test Sources Root,在下面新建user.ini文件,內容:
[users]
wzs=123456,admin
[roles]
admin=user:delete,user:update
單元測試代碼:
package com.example.demo_mg;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoMgApplicationTests {
@Test
public void contextLoads() {
}
@Test
public void test() {
IniRealm iniRealm = new IniRealm("classpath:user.ini");
//構建SecurityManager環境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(iniRealm);
//主體提交認證請求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
subject.login(token);
//授權
subject.checkRole("admin");
subject.checkPermissions("user:delete", "user:update");
subject.checkPermission("user:insert");
subject.logout();
}
}
權限不存在UnauthorizedException
5、測試內置的JdbcRealm:
1)安裝mysql8.0
2)按照JdbcRealm的源碼的SQL建表:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`password_salt` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
CREATE TABLE `user_roles` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`role_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
CREATE TABLE `roles_permissions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_name` varchar(255) DEFAULT NULL,
`permission` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
3)添加測試數據:
insert into users (username,password) values ('wzs','123456');
insert into user_roles (username,role_name) values ('wzs','admin');
insert into roles_permissions (role_name,permission) values ('admin','user:delete');
4)依賴
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
5)單元測試代碼:
package com.example.demo_mg;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoMgApplicationTests {
DruidDataSource dataSource = new DruidDataSource();
{
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://129.204.58.30:3306/shiro");
dataSource.setUsername("user");
dataSource.setPassword("*********");
}
@Test
public void contextLoads() {
}
@Test
public void test() {
JdbcRealm jdbcRealm = new JdbcRealm();
jdbcRealm.setDataSource(dataSource);
jdbcRealm.setPermissionsLookupEnabled(true);//為了查詢權限表,要開啟權限查詢
//構建SecurityManager環境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(jdbcRealm);
//主體提交認證請求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
subject.login(token);
//授權
System.out.println(subject.isAuthenticated());
System.out.println(subject.hasRole("admin"));
System.out.println(subject.isPermitted("user:delete"));
subject.logout();
}
}
6)若自定義用戶、角色、權限三張表,需要自定義查詢用戶、角色、權限的SQL:
參考JdbcRealm的查詢語句:
protected String authenticationQuery = "select password from users where username = ?";
protected String userRolesQuery = "select role_name from user_roles where username = ?";
protected String permissionsQuery = "select permission from roles_permissions where role_name = ?";
設置自定義查詢語句到jdbcRealm對象:
jdbcRealm.setAuthenticationQuery("自定義查詢用戶sql");
jdbcRealm.setUserRolesQuery("自定義查詢角色sql");
jdbcRealm.setPermissionsQuery("自定義查詢權限sql");
6、自定義Realm,參考JdbcRealm繼承抽象的AuthorizingRealm類並實現其抽象方法,認證doGetAuthenticationInfo,授權doGetAuthenticationInfo,然后單元測試授權和認證:
package com.example.demo_mg.realm;
import org.apache.commons.collections.map.HashedMap;
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.util.*;
public class TestRealm extends AuthorizingRealm {
//模擬users、user_roles、roles_permissions三張表的查詢,實際應用需要查詢數據庫或緩存
Map<String, String> users = new HashMap<>();
Map<String, Set<String>> user_roles = new HashedMap();
Map<String, Set<String>> roles_permissions = new HashedMap();
{
users.put("wzs", "123456");
user_roles.put("wzs", new HashSet<>(Arrays.asList("admin", "test")));
roles_permissions.put("admin", new HashSet<>(Arrays.asList("user:delete", "user:update")));
super.setName("TestRealm"); //設置Realm名稱,可選
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//從認證信息獲取用戶名
String username = (String)principalCollection.getPrimaryPrincipal();
//從數據庫或緩存中獲取角色、權限數據
Set<String> roles = user_roles.get(username);
Set<String> permissions = new HashSet<>();
for (String role : roles) {
Set<String> set;
if((set = roles_permissions.get(role)) != null) {
permissions.addAll(set);
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//從主題傳過來的認證信息中,獲得用戶名
String username = (String)authenticationToken.getPrincipal();
//通過用戶名從數據庫中獲取憑證
String password = users.get(username);
if(password != null) {
return new SimpleAuthenticationInfo(username, password, super.getName());
}
return null;
}
}
單元測試:
package com.example.demo_mg;
import com.example.demo_mg.realm.TestRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoMgApplicationTests {
@Test
public void contextLoads() {
}
@Test
public void test() {
TestRealm testRealm = new TestRealm();
//構建SecurityManager環境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(testRealm);
//主體提交認證請求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
subject.login(token);
//授權
System.out.println(subject.isAuthenticated());
System.out.println(subject.hasRole("admin"));
System.out.println(subject.isPermitted("user:delete"));
subject.logout();
}
}
7、密碼加密及加鹽:
在6的基礎上調整代碼:
package com.example.demo_mg.realm;
import org.apache.commons.collections.map.HashedMap;
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.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import java.util.*;
public class TestRealm extends AuthorizingRealm {
//模擬users、user_roles、roles_permissions三張表的查詢,實際應用需要查詢數據庫或緩存
Map<String, String> users = new HashMap<>();
Map<String, Set<String>> user_roles = new HashedMap();
Map<String, Set<String>> roles_permissions = new HashedMap();
String salt = UUID.randomUUID().toString().replaceAll("-","");
{
//不加鹽(與認證對應)
// users.put("wzs", new Md5Hash("123456",null, 2).toString());
//加鹽
users.put("wzs", new Md5Hash("123456",salt, 2).toString());
user_roles.put("wzs", new HashSet<>(Arrays.asList("admin", "test")));
roles_permissions.put("admin", new HashSet<>(Arrays.asList("user:delete", "user:update")));
super.setName("TestRealm"); //設置Realm名稱,可選
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//從認證信息獲取用戶名
String username = (String)principalCollection.getPrimaryPrincipal();
//從數據庫或緩存中獲取角色、權限數據
Set<String> roles = user_roles.get(username);
Set<String> permissions = new HashSet<>();
for (String role : roles) {
Set<String> set;
if((set = roles_permissions.get(role)) != null) {
permissions.addAll(set);
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//從主題傳過來的認證信息中,獲得用戶名
String username = (String)authenticationToken.getPrincipal();
//通過用戶名從數據庫中獲取憑證
String password = users.get(username);
if(password != null) {
//不加鹽
// return new SimpleAuthenticationInfo(username, password, super.getName());
//加鹽
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, super.getName());
simpleAuthenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(salt));
return simpleAuthenticationInfo;
}
return null;
}
}
單元測試:
package com.example.demo_mg;
import com.example.demo_mg.realm.TestRealm;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoMgApplicationTests {
@Test
public void contextLoads() {
}
@Test
public void test() {
TestRealm testRealm = new TestRealm();
//設置加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
matcher.setHashAlgorithmName("md5"); //加密算法
matcher.setHashIterations(2); //加密次數,與new Md5Hash("123456",null, 2).toString();的hashIterations參數一致,否則IncorrectCredentialsException
testRealm.setCredentialsMatcher(matcher);
//構建SecurityManager環境
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(testRealm);
//主體提交認證請求
SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("wzs", "123456");
subject.login(token);
//授權
System.out.println(subject.isAuthenticated());
System.out.println(subject.hasRole("admin"));
System.out.println(subject.isPermitted("user:delete"));
subject.logout();
}
}
MD5加密不可逆,但不加鹽存在風險,注冊的時候隨機生成鹽,保存到數據庫,這里返回的認證對象帶的鹽從庫里查出來。