Shiro学习——认证与授权(ini文件配置与数据库配置方式)

Posted sadoshi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro学习——认证与授权(ini文件配置与数据库配置方式)相关的知识,希望对你有一定的参考价值。

前言

安全框架最基本的两个功能是认证和授权,说俗一点就是登陆和权限管理。在系列第一篇文章《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文件配置与数据库配置方式)的主要内容,如果未能解决你的问题,请参考以下文章

shiro---Shiro认证授权案例讲解

Shiro安全框架SpringBoot版

Shiro 安全框架详解二(概念+权限案例实现)

shiro技术四大组件和运行原理是啥?

SpringBoot日记——Spring的安全配置-登录认证与授权

Shiro集成web环境[Springboot]-认证与授权