Shiro学习——认证与授权(ini文件配置与mysql方式)
Posted sadoshi
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro学习——认证与授权(ini文件配置与mysql方式)相关的知识,希望对你有一定的参考价值。
前言
安全框架最基本的两个功能是认证和授权,说俗一点就是登陆和权限管理。在系列第一篇文章《Shiro学习(一)——Shiro配置与快速开始》中,我们已经通过login验证了其认证功能。本篇主要讲讲授权功能。
另外也想讲讲通过数据库方式进行授权的管理。网上搜索以及很多例子基本都是以ini文件配置为主,即使个别使用数据库管理的,也讲得不清不楚,希望这篇文章能让大家搞明白。
ini文件配置方式
还是先以ini文件配置方式讲起,我们新建文件shiro.ini
[users]
zhang=123,role1
wang=456,role1,role2
[roles]
role1=user:create,user:update
role2=user:create,user:delete
注意ini文件中,用户信息要在[users]小节下,角色权限要在[roles]小节下。因为ini文件配置方式会调用IniRealm来处理,该类会寻找[users]和[roles]小节进行处理。
[users]小节下,以“用户名=密码,角色1,角色2,......”进行配置。因此上述为用户名“zhang”,其密码为123,其角色为role1。
[roles]小节下,以“角色=权限1,权限2,......”进行配置。权限还有一些简写的方式,这里就不讲了,需要的可以自行查询。
接下来我们写一个测试类:
package com.sadoshi.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.env.BasicIniEnvironment;
import org.apache.shiro.env.Environment;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Before;
import org.junit.Test;
import junit.framework.Assert;
public class ShiroIniTest {
private Subject subject;
@Before
public void init() {
Environment env = new BasicIniEnvironment("classpath:shiro.ini");
org.apache.shiro.mgt.SecurityManager securityManager = env.getSecurityManager();
SecurityUtils.setSecurityManager(securityManager);
subject = SecurityUtils.getSubject();
}
@Test
public void testAuthorization() {
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
subject.login(token);
System.out.println(subject.hasRole("role1"));
System.out.println(subject.hasRole("role2"));
System.out.println(subject.isPermitted("user:create"));
System.out.println(subject.isPermitted("user:update"));
} catch (AuthenticationException e) {
e.printStackTrace();
}
subject.logout();
}
}
init函数内是对subject进行初始化,testAuthorization先login进行认证,之后我们用hasRole方法校验该帐号是否为role1、role2角色,然后用isPermitted校验帐号是否具备user:create和user:update权限。
上述的例子网上随便搜一下就有,仅当做个铺垫和记录,接下来讲通过数据库配置才是重点。
通过mysql数据库配置方式
绝大多数项目,我们的帐号密码,以及角色权限,应该都是存储在数据库中的,基本没什么项目用ini文件配置账号权限。数据库配置并不难,而且即使不扩展JdbcRealm,也可以具备一定灵活性。关于灵活性后面再讲一下,先讲默认情况。
建表
首先数据库必须新建三张表
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=3 DEFAULT CHARSET=utf8
CREATE TABLE `user_roles` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`username` varchar(100) NOT NULL,
`role_name` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
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`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
这三个表的表名、字段名都不能改动(主键id除外),因为JdbcRealm的默认查询语句就是查这三个表对应的字段。当然这几个表可以添加更多的字段,但至少要有这些字段,而且字段名和表名不能动。
我们插入一些测试数据,和我们通过ini配置的情况一样:
INSERT INTO users(username, `password`) VALUES ('zhang','123');
INSERT INTO users(username, `password`) VALUES ('wang','456');
INSERT INTO user_roles(username, role_name) VALUES ('zhang','role1');
INSERT INTO user_roles(username, role_name) VALUES ('wang','role1');
INSERT INTO user_roles(username, role_name) VALUES ('wang','role2');
INSERT INTO roles_permissions(role_name, permission) VALUES ('role1', 'user:create');
INSERT INTO roles_permissions(role_name, permission) VALUES ('role1', 'user:update');
INSERT INTO roles_permissions(role_name, permission) VALUES ('role2', 'user:create');
INSERT INTO roles_permissions(role_name, permission) VALUES ('role2', 'user:delete');
配置JdbcRealm
我们有两种方式配置shiro使用JdbcRealm,通过ini方式或者通过纯java方式。读者选择其中一种即可,当然以后有多个realm需要配置,也可以混合使用。
ini文件配置方式
这里创建ini配置文件shiro-jdbc-realm.ini:
[main]
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro
dataSource.username=root
#dataSource.password=
jdbcRealm.dataSource=$dataSource
jdbcRealm.permissionsLookupEnabled=true
securityManager.realms=$jdbcRealm
这里先指定jdbcRealm的类型,然后指定dataSource的类型,之后设置dataSource的各项属性,把dataSource注入jdbcRealm中,再把jdbcRealm注入到securityManager即可。注意如果使用权限控制的话,一定要把jdbcRealm.permissionsLookupEnabled这个设置为true,因为默认是false,这种情况下jdbcRealm不会去查询roles_permissions,之前的isPermitted判断权限都会返回false。
纯java配置
java的配置差不多,如果了解spring ioc注入原理的,应该对这些配置方式不陌生:
public void init() throws Exception {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
JdbcRealm realm = new JdbcRealm();
Class<DruidDataSource> clazz = DruidDataSource.class;
DruidDataSource dataSource = clazz.newInstance();
dataSource.setUsername("root");
dataSource.setPassword("");
dataSource.setUrl("jdbc:mysql://localhost:3306/shiro");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
realm.setDataSource(dataSource);
realm.setPermissionsLookupEnabled(true);
securityManager.setRealm(realm);
}
上面这段的函数实现的就是ini文件配置一样的功能。我们前面的文章分析了securityManager默认使用DefaultSecurityManager,所以这里我们直接new一个DefaultSecurityManager出来,利用反射创建dataSource,之后设置属性,再配置到securityManager中即可。这样我们就可以使用securityManager了,后面按之前的步骤调用SecurityUtils.setSecurityManager(securityManager),再获取subject即可。
测试权限效果
之后我们编写测试例子,我们使用纯java方式:
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 com.alibaba.druid.pool.DruidDataSource;
import junit.framework.Assert;
public class ShiroJdbcTest {
private Subject subject;
@Before
public void init() throws Exception {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
JdbcRealm realm = new JdbcRealm();
Class<DruidDataSource> clazz = DruidDataSource.class;
DruidDataSource dataSource = clazz.newInstance();
dataSource.setUsername("root");
dataSource.setPassword("");
dataSource.setUrl("jdbc:mysql://localhost:3306/shiro");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
realm.setDataSource(dataSource);
realm.setPermissionsLookupEnabled(true);
securityManager.setRealm(realm);
SecurityUtils.setSecurityManager(securityManager);
subject = SecurityUtils.getSubject();
}
@Test
public void testJDBCRealm() {
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
subject.login(token);
System.out.println(subject.hasRole("role1"));
System.out.println(subject.hasRole("role2"));
System.out.println(subject.isPermitted("user:create"));
System.out.println(subject.isPermitted("user:update"));
} catch (AuthenticationException e) {
e.printStackTrace();
}
Assert.assertEquals(true, subject.isAuthenticated());
subject.logout();
}
}
认证与权限的表名和字段可以修改吗?关于JdbcRealm的探索
是可以修改的,而且JdbcRealm为我们留了足够的空间,甚至不需要对其进行扩展。我们稍微看一下源码:
public class JdbcRealm extends AuthorizingRealm {
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 = ?";
//省略
}
实际上JdbcRealm把使用的sql查询语句提取出来,第一句是用来做登录认证的,第二句是加上盐值认证,第三句是根据用户名查询角色名,第四句根据角色名查询权限。看看这些语句,现在知道为什么之前创建的表名和字段不能修改了吧,因为你改了,JdbcRealm就查不到了!
shiro作者还是比较贴心,不但把sql语句提取出来,还有专门的setter方法和getter方法,这样我们可以在配置的时候直接修改这几个语句,那即使表名和字段名改了,JdbcRealm也能找到。
举个例子,我们现在把所有表名的前面加上"t_",修改后表名变为“t_users”、“t_user_roles”、“t_roles_permissions”,另外我们把t_user_roles和t_roles_permissions的role_name字段改为role。这时候你执行上面测试的程序一定会报错。
我们需要修改一下对应的语句,这次我们使用ini文件配置JdbcRealm方式:
[main]
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/shiro
dataSource.username=root
#dataSource.password=
jdbcRealm.dataSource=$dataSource
jdbcRealm.permissionsLookupEnabled=true
jdbcRealm.authenticationQuery=select password from t_users where username = ?
jdbcRealm.userRolesQuery=select role from t_user_roles where username = ?
jdbcRealm.permissionsQuery=select permission from t_roles_permissions where role = ?
securityManager.realms=$jdbcRealm
可以看到倒数2、3、4行把修改后的sql语句设置到JdbcRealm对应的属性中,之后再尝试测试即可。
小结
本文主要讲了shiro授权方面的基本使用,也稍微探索了一下JdbcRealm。如果权限管理比较复杂,通常的做法是自定义realm,扩展JdbcRealm并重写其doGetAuthenticationInfo和doGetAuthorizationInfo方法。自定义realm的后面再讲。使用起来也不复杂,但是想要完全驾驭的话,肯定要从源码角度去理解,这个会在后面的文章中分析。
以上是关于Shiro学习——认证与授权(ini文件配置与mysql方式)的主要内容,如果未能解决你的问题,请参考以下文章