Shiro learning (four) - authentication and authorization (ini file configuration and database configuration method)

foreword

The two most basic functions of the security framework are authentication and authorization. To put it bluntly, it is login and authority management. In the first article of the series " Shiro Learning (1) - Shiro Configuration and Quick Start ", we have verified its authentication function through login. This article mainly talks about the authorization function.

In addition, I also want to talk about the management of authorization through the database. Internet search and many examples are basically based on ini file configuration. Even if some database management is used, it is not clear. I hope this article can make everyone understand.

ini file configuration method

Let’s start with the configuration of the ini file first, let’s create a new file shiro.ini

[users]
zhang=123,role1
wang=456,role1,role2
[roles]
role1=user:create,user:update
role2=user:create,user:delete

Note that in the ini file, user information should be under the [users] section, and role permissions should be under the [roles] section. Because the ini file configuration method will call IniRealm to process, this class will look for the [users] and [roles] sections for processing.

Under the [users] section, configure with "username=password, role 1, role 2,...". Therefore, the above is the user name "zhang", its password is 123, and its role is role1.

Under the [roles] section, configure with "role = authority 1, authority 2,...". There are also some abbreviations for permissions, which I won’t talk about here, and you can check them yourself if you need them.

Next we write a test class:

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();
	}
}

The init function initializes the subject, testAuthorization first logs in for authentication, then we use the hasRole method to verify whether the account is role1 or role2, and then use isPermitted to verify whether the account has user:create and user:update permissions.

The above examples can be easily found on the Internet, just as a foreshadowing and record, and then the key point is to pass the database configuration.

Through the mysql database configuration method

For most projects, our account passwords and role permissions should be stored in the database, and almost no projects use ini files to configure account permissions. Database configuration is not difficult, and even without extending JdbcRealm, you can have some flexibility. Let’s talk about flexibility later, let’s talk about the default situation first.

build table

First, the database must create three new tables

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

The table names and field names of these three tables cannot be changed (except for the primary key id), because the default query statement of JdbcRealm is to check the corresponding fields of these three tables. Of course, more fields can be added to these tables, but at least these fields must be present, and the field names and table names cannot be changed.

We insert some test data, the same as we configured through 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');

Configure JdbcRealm

We have two ways to configure shiro to use JdbcRealm, through ini way or through pure java way. Readers can choose one of them. Of course, there will be multiple realms that need to be configured in the future, and they can also be used in combination.

ini file configuration method

Create the ini configuration file shiro-jdbc-realm.ini here:

[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

Here, first specify the type of jdbcRealm, then specify the type of dataSource, then set the properties of dataSource, inject dataSource into jdbcRealm, and then inject jdbcRealm into securityManager. Note that if you use permission control, you must set jdbcRealm.permissionsLookupEnabled to true, because the default is false. In this case, jdbcRealm will not query roles_permissions, and the previous isPermitted judgment permission will return false.

pure java configuration

The configuration of java is similar. If you understand the principle of spring ioc injection, you should be familiar with these configuration methods:

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);
}

The function in the above paragraph implements the same function as the ini file configuration. Our previous article analyzed that securityManager uses DefaultSecurityManager by default, so here we directly create a new DefaultSecurityManager, use reflection to create dataSource, then set properties, and then configure it in securityManager. In this way, we can use the securityManager, and then call SecurityUtils.setSecurityManager(securityManager) according to the previous steps, and then get the subject.

Test permission effect

Then we write the test example, we use the pure java way:

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();
    }
}

Can the table names and fields of authentication and permissions be modified? Exploration about JdbcRealm

is modifiable, and JdbcRealm leaves enough room for us that we don't even need to expand it. Let's take a look at the source code:

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 = ?";

//省略
}

In fact, JdbcRealm extracts the sql query statement used. The first sentence is used for login authentication, the second sentence is added salt value authentication, the third sentence is to query the role name according to the user name, and the fourth sentence is based on the role name. Query permissions. Look at these statements, now you know why the table name and fields created before cannot be modified, because if you change it, JdbcRealm will not be able to find it!

The author of shiro is quite considerate, not only extracting the sql statement, but also a special setter method and getter method, so that we can directly modify these statements during configuration, so that even if the table name and field name are changed, JdbcRealm can also find them .

For example, we now add "t_" to the front of all table names, and the modified table names become "t_users", "t_user_roles", "t_roles_permissions", and we change the role_name field of t_user_roles and t_roles_permissions to role. At this time, the program you execute the above test will definitely report an error.

We need to modify the corresponding statement, this time we use the ini file to configure the JdbcRealm method:

[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

You can see that the last 2, 3, and 4 lines set the modified sql statement to the corresponding attribute of JdbcRealm, and then try to test again.

summary

This article mainly talks about the basic use of shiro authorization, and also explores JdbcRealm a little bit. If the authority management is more complicated, the usual way is to customize realm, extend JdbcRealm and rewrite its doGetAuthenticationInfo and doGetAuthorizationInfo methods. We will talk about custom realm later. It is not complicated to use, but if you want to fully control it, you must understand it from the perspective of source code, which will be analyzed in a later article.

Guess you like

Origin blog.csdn.net/sadoshi/article/details/120059995