Realm 是什么
Realm: 域,Shiro 从 Realm 中获取用户,角色,权限信息。可以把 Relam 看成 DataSource,即安全数据源。
在前两章的认证和授权中,我们也使用到了 SimpleAccountRealm
,并通过其 addAccount(username, password, roles)
来预设用户和角色信息。
IniRealm
IniRealm 顾名思义,即通过读取 .ini
文件来获取用户,角色,权限信息。
配置用户名/密码及其角色, 格式: “用户名=密码,角色1,角色2”,如:
1 2 3
| [users] zhao = 123456, admin, user wang = 123456, user
|
配置角色及权限之间的关系, 格式: “角色=权限1, 权限2”, 如:
1 2 3
| [roles] admin = user:delete user = user:select
|
结合起来,即 :
1 2 3 4 5 6 7
| [users] zhao = 123456, admin, user wang = 123456, user
[roles] admin = user:delete user = user:select
|
然后进行测试 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.Realm; import org.apache.shiro.realm.text.IniRealm; import org.apache.shiro.subject.Subject; import org.junit.Test;
import java.util.Arrays;
public class IniRealmTest {
@Test public void testIniRealm() { DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(); Realm iniRealm = new IniRealm("classpath:shiro.ini"); defaultSecurityManager.setRealm(iniRealm); SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhao", "123456");
try { subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); System.out.println("登陆失败"); }
System.out.println("--------------------认证--------------------"); System.out.println("是否具备 admin 权限: " + subject.hasRole("admin")); System.out.println("是否具备 user 权限: " + subject.hasRole("user")); System.out.println("是否同时具备 admin 和 user 权限: " + subject.hasAllRoles(Arrays.asList("admin", "user")));
System.out.println("--------------------授权--------------------"); System.out.println("是否具备 user:delete 权限" + subject.isPermitted("user:delete")); System.out.println("是否具备 user:select 权限" + subject.isPermitted("user:select")); System.out.println("是否同时具备 user:delete 和 user:select 权限" + subject.isPermittedAll("user:delete", "user:select")); } }
|
跟前两章的代码没有什么不同,只是将 SimpleAccountRealm 换成了 IniRealm。
JdbcRelam
JdbcRelam 顾名思义,即通过通过访问数据库来获取用户,角色,权限信息。
首先需要导入 mysql
的驱动包和 druid
数据库连接池的包:
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.32</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency>
|
创建数据库表和初始化数据 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `roles_permissions`; CREATE TABLE `roles_permissions` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `role_name` varchar(100) DEFAULT NULL, `permission` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_roles_permissions` (`role_name`,`permission`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO `roles_permissions` VALUES ('1', 'admin', 'user:delete'); INSERT INTO `roles_permissions` VALUES ('2', 'user', 'user:select');
DROP TABLE IF EXISTS `users`; CREATE TABLE `users` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(100) DEFAULT NULL, `password` varchar(100) DEFAULT NULL, `password_salt` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_users_username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
INSERT INTO `users` VALUES ('1', 'zhao', '123456', null);
DROP TABLE IF EXISTS `user_roles`; CREATE TABLE `user_roles` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(100) DEFAULT NULL, `role_name` varchar(100) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `idx_user_roles` (`username`,`role_name`) ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO `user_roles` VALUES ('1', 'zhao', 'admin'); INSERT INTO `user_roles` VALUES ('2', 'zhao', 'user');
|
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| import com.alibaba.druid.pool.DruidDataSource; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.mgt.DefaultSecurityManager; import org.apache.shiro.realm.jdbc.JdbcRealm; import org.apache.shiro.subject.Subject; import org.junit.Before; import org.junit.Test;
import java.util.Arrays;
public class JdbcRealmTest { private DruidDataSource dataSource = new DruidDataSource();
@Before public void before() { dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/shiro"); dataSource.setUsername("root"); dataSource.setPassword("root"); }
@Test public void testJdbcRealm() { DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
JdbcRealm jdbcRealm = new JdbcRealm(); jdbcRealm.setDataSource(dataSource); jdbcRealm.setPermissionsLookupEnabled(true);
defaultSecurityManager.setRealm(jdbcRealm); SecurityUtils.setSecurityManager(defaultSecurityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhao", "123456");
try { subject.login(token); } catch (AuthenticationException e) { e.printStackTrace(); System.out.println("登陆失败"); }
System.out.println("--------------------认证--------------------"); System.out.println("是否具备 admin 权限: " + subject.hasRole("admin")); System.out.println("是否具备 user 权限: " + subject.hasRole("user")); System.out.println("是否同时具备 admin 和 user 权限: " + subject.hasAllRoles(Arrays.asList("admin", "user"))); System.out.println("--------------------授权--------------------"); System.out.println("是否具备 user:delete 权限" + subject.isPermitted("user:delete")); System.out.println("是否具备 user:select 权限" + subject.isPermitted("user:select")); System.out.println("是否同时具备 user:delete 和 user:select 权限" + subject.isPermittedAll("user:delete", "user:select")); } }
|
这里也只是将 Relam 修改了一下,其他代码是一样的。
细心的朋友可能会有疑问,我们这里没有写一行查询语句,那么Shiro 怎么知道你的数据库结构的,它如何来查询角色和权限信息。
其实我们点开 JdbcRelam
的源码看看就知道了,它内置了默认的查询角色和权限的 SQL 语句:
1 2 3 4
| protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?"; protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?"; protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?"; protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
|
而上面我们建立的表结构是符合这些查询语句的。
但在实际项目开发中,我们不可能完全按照 Shiro 提供的这种方式来建表,我们可以通过修改 JdbcRealm 的默认查询语句来实现:
1 2 3
| jdbcRealm.setAuthenticationQuery(String authenticationQuery); jdbcRealm.setPermissionsQuery(String permissionsQuery); jdbcRealm.setUserRolesQuery(String userRolesQuery);
|
自定义 Relam
在真实项目开发中,我们往往会使用自定义 Realm 来实现一些自定义的功能,如判断账号锁定,账号登陆次数限制等。
我们需要创建一个类来继承 AuthorizingRealm
,并实现其抽象方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| import im.zhaojun.pojo.User; import org.apache.shiro.authc.*; 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.HashSet; import java.util.Set;
public class MyCustomRealm extends AuthorizingRealm {
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String username = (String)principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> roles = selectRolesByUserName(username); Set<String> permissions = selectPermissionsByUserName(username);
authorizationInfo.setRoles(roles); authorizationInfo.setStringPermissions(permissions); return authorizationInfo; }
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
User user = selectUserByUserName((String) authenticationToken.getPrincipal());
if (user == null) { throw new UnknownAccountException("账号不存在"); }
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), super.getName()); }
private Set<String> selectPermissionsByUserName(String username) { HashSet<String> permissions = new HashSet<>(); if ("zhao".equals(username)) { permissions.add("select"); } return permissions; }
private Set<String> selectRolesByUserName(String username) { HashSet<String> roles = new HashSet<>();
if ("zhao".equals(username)) { roles.add("user"); } return roles; }
private User selectUserByUserName(String username) { User user = null;
if ("zhao".equals(username)) { user = new User("zhao", "123456"); } return user; } }
|
这里的代码也很简单,主要是实现了父类的抽象方法 :
doGetAuthenticationInfo
: 获取用户认证信息,在这里我们不需要校验密码是否正确,因为有专门的密码校验器来做这件事,我们只需要返回认证信息即可。 (认证信息在这个示例中为 SimpleAuthenticationInfo
, 即账号密码)
当然你也可以在返回认证信息前根据用户的状态,如冻结,锁定,或登陆次数来抛出相应的异常,以直接返回登陆失败,而不再进行密码校验。doGetAuthorizationInfo
: 获取用户授权信息,授权信息包括所拥有的角色和权限信息,这里的逻辑很简单,只需要根据用户信息查询出角色和权限,配置到 AuthorizationInfo
中返回即可 。
测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import im.zhaojun.realm.MyCustomRealm; 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;
public class MyCustomRealmTest {
@Test public void testCustomRealm() { DefaultSecurityManager securityManager = new DefaultSecurityManager(); MyCustomRealm realm = new MyCustomRealm(); securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhao", "123456");
subject.login(token);
System.out.println("是否具备 user 角色: " + subject.hasRole("user")); System.out.println("是否具备 select 权限: " + subject.isPermitted("select")); } }
|
本章代码地址 : https://github.com/zhaojun1998/Premission-Study/tree/master/Permission-Shiro-03/