1. Introduction to Shiro

1.1 What is Shiro

Apache Shiro is a powerful and easy-to-use Java security framework that performs authentication, authorization, password and session management, Web integration, caching, and more. More and more people are using Apache Shiro these days because it’s fairly simple, probably not as powerful as Spring Security, but it probably doesn’t need anything that complicated to actually work with. So using a small and simple Shiro is enough. As for which one is better, it is not necessary to struggle, it is easier to solve the project problem.

Shiro’s official website

Shiro official documentation

1.2 Shiro’s functions

Shiro’s API is also very simple; Its basic function points are shown in the figure below:

  • Authentication: Authentication/login to verify that the user has the appropriate identity;
  • Authorization: indicates Authorization. Authorization is used to verify whether an authenticated user has a certain permission. That is, it determines whether a user can do something. For example, it verifies whether a user has a role. Or fine-grained verification of whether a user has a certain permission on a certain resource;
  • Session Management: Session Management, that is, after a user logs in to a Session, all its information is in the Session before the user logs out. The session can be in a normal JavaSE environment or in a Web environment.
  • Cryptography: To protect the security of data. For example, passwords are encrypted and stored in a database instead of being stored in plain text.
  • Web Support, which can be easily integrated into Web environment;
  • Caching: indicates the cache. For example, after a user logs in, the user information and roles and permissions do not need to be checked every time. This improves efficiency.
  • Concurrency: Shiro supports Concurrency validation for multi-threaded applications. Let’s say you start another thread in one thread and automatically propagate permissions to the other.
  • Testing: Provide Testing support;
  • Run As: Allows a user to pretend to be another user if they allow access;
  • Remember Me: This is a very common function, that is, once logged in, the next time you do not have to log in.

== Note: Shiro does not maintain users and privileges; These need to be designed/provided by us; Then inject it into Shiro through the corresponding interface. = =

1.3 Shiro external framework

Shiro’s external framework has a very simple and easy to use API with an explicit API contract. The following figure shows how to use Shiro to get things done from an application perspective:

As you can see, the object that the application code interacts with directly is Subject, which means that Shiro’s core external API is Subject==. Meaning of each OF its apis:

  • Subject: the Subject represents the current “user”. This user is not necessarily a specific person. Anything that interacts with the current application is a Subject, such as web crawlers, robots, etc. It’s an abstract concept; All subjects are bound to the SecurityManager, and all interactions with the Subject are delegated to the SecurityManager; You can think of a Subject as a facade; The SecurityManager is the actual enforcer;

  • SecurityManager: SecurityManager; That is, all security-related operations interact with the SecurityManager; And it manages all subjects; As you can see, the SecurityManager is the core of Shiro. It is responsible for interacting with the other components described below. If you have studied SpringMVC, you can think of it as a DispatcherServlet front-end controller.

  • Realm: realms. Shiro obtains security data (such as users, roles, and permissions) from a Realm. This means that SecurityManager needs to authenticate a user, then it needs to obtain the corresponding user from a Realm and compare it to determine if the user’s identity is valid. You also need to get the user’s roles/permissions from the Realm to verify that the user can perform operations; A Realm can be thought of as a DataSource, a secure DataSource.

  • That said, for us, the simplest application of Shiro is:

    1. Application code authenticates and authorizes through a Subject, which delegates to the SecurityManager;

    2. We need to inject a Realm into Shiro’s SecurityManager so that the SecurityManager can get legitimate users and their permissions to determine.

As you can see from the above, Shiro does not provide maintenance users/permissions, but rather allows developers to inject themselves through a Realm.

1.4 Shiro internal framework

Shiro’s internal framework is an extensible architecture, that is, it is very easy to plug in custom implementations because no framework can meet all requirements.

  • Subject: the principal, as you can see, can be any “user” that can interact with the application;
  • SecurityManager: Equivalent to DispatcherServlet in SpringMVC or FilterDispatcher in Struts2; It’s Shiro’s heart; All concrete interactions are controlled by the SecurityManager; It manages all subjects and is responsible for authentication and authorization, session and cache management.
  • Authenticator: The Authenticator, responsible for principal authentication, is an extension point that users can customize if they feel Shiro’s default is not good; The Authentication Strategy is required, that is, when the user passes the Authentication;
  • Authrizer: An authorizer, or access controller, that determines whether the principal has permission to perform the corresponding operation; That is, it controls what functionality the user can access in the application;
  • Realm: There can be one or more realms that can be considered security entity data sources, i.e. used to retrieve security entities; It can be a JDBC implementation, it can be an LDAP implementation, it can be a memory implementation, etc. Provided by the user; Note: Shiro does not know where your users/permissions are stored and in what format; == So we usually need to implement our own Realm in our application; = =
  • SessionManager: If you’ve ever written a Servlet, you know the concept of a Session. A Session needs someone to manage its lifecycle. This component is the SessionManager. Shiro can be used not only in Web environment, but also in ordinary Java ASE environment, EJB environment and so on. So Shiro abstracts its own Session to manage the data that the principal interacts with the application; So in this case, for example, if we’re using a Web environment, we’re starting with a Web server; Then I went to an EJB server; If you want to put session data from both servers in one place, you can implement your own distributed session (such as Memcached).
  • If you want to save your Session to a database, you can implement your own SessionDAO. If you want to save your Session to a database, you can write to the database via JDBC. If you want to put your Session in Memcached, you can implement your own Memcached SessionDAO; In addition, SessionDAO can be cached using Cache to improve performance.
  • CacheManager: cache controller, which manages the caches of users, roles, and permissions. Since this data is rarely changed, putting it in the cache can improve access performance
  • Cryptography: The Cryptography module, Shiro provides some common Cryptography components for use such as Cryptography/decryption.

2. Shiro’s quick start

2.1 Environment construction

Create a new Maven project and import dependencies:

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.7.1</version>
    </dependency>

    <! -- configure logging -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.26</version>
        
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.26</version>

    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
</dependencies>
Copy the code

Log4j. Properties:

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
Copy the code

Shiro configuration file shiro.ini:

[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;) ), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz

# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
# Roles with assigned permissions
#
# Each line conforms to the format defined in the
# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
Copy the code

2.2. Test class

The test class tests some API methods, mainly the use of Subject.

import com.sun.org.omg.CORBA.InitializerSeqHelper;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class Quickstart {

    private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);

    public static void main(String[] args) {


        The simplest way to create Shiro SecurityManager using configuration: * by the ini file (shiro ini) loading areas, users, roles, and permissions configuration * IniSecurityManagerFactory by reading. Ini configuration file, return to * /
        // Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        // SecurityManager securityManager = factory.getInstance();
        SecurityManager securityManager = new IniSecurityManagerFactory("classpath:shiro.ini").getInstance();

        // For a quick start with this simple example, make SecurityManager accessible as a JVM singleton.
        // Most applications do not do this and instead rely on their container configuration or webapps for web.xml.
        // This is beyond the scope of this simple quick start, so we'll just do the bare minimum so you can continue to feel things.
        SecurityUtils.setSecurityManager(securityManager);


        // Now that we have set up a simple Shiro environment, let's see what we can do:
        // Retrieve the currently executing user Subject:
        Subject currentUser = SecurityUtils.getSubject();

        // Get session (shiro session) from current user
        Session session = currentUser.getSession();
        // Session stores the value and value
        session.setAttribute("someKey"."A long journey.");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("A long journey.")) {
            log.info("The Subject -" the session [" + value + "]");//Subject-- "session"
        }

        // Check whether the current user is authenticated
        if(! currentUser.isAuthenticated()) {// Verify the user name and password (compared with the user information in the.ini configuration file), and generate a token for the user if the authentication is successful.
            UsernamePasswordToken token = new UsernamePasswordToken("root"."secret");
            token.setRememberMe(true);// Enable the Remember me function

            try {
                currentUser.login(token);// Perform the login operation
            } catch (UnknownAccountException uae) {
                log.info("There is no user with username of " + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
                log.info("Password for account " + token.getPrincipal() + " was incorrect!");
            } catch (LockedAccountException lae) {
                log.info("The account for username " + token.getPrincipal() + " is locked. " +
                        "Please contact your administrator to unlock it.");
            }
            // Total exception
            catch (AuthenticationException ae) {
            }
        }
        // Print the current user name
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        // Test the user's role
        if (currentUser.hasRole("admin")) {// If the current user has the admin role
            log.info("May the admin be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        // Test role permissions (non-instance level), coarse granularity
        if (currentUser.isPermitted("lightsaber:wield")) {
            log.info("You may use a lightsaber ring. Use it wisely.");
        } else {
            log.info("Sorry, lightsaber rings are for schwartz masters only.");
        }

        // A (very powerful) instance-level permission, fine grained
        if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
                    "Here are the keys - have fun!");
        } else {
            log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
        }

        / / logout
        currentUser.logout();

        / / end
        System.exit(0); }}Copy the code

Start a test and view the log output:

The above is just a simple test of console output. Next, let’s integrate Shiro with Springboot.

Get the test code

3. Springboot integrates Shiro

3.1. Build the environment

Create a new SpringBoot project and import the dependencies:

<dependencies>

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <! -- Import dependencies for Tomcat parsing JSPS -->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
        </dependency>

        <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.7.1</version>
        </dependency>
</dependencies>
Copy the code

Custom Realm

package com.cheng.shiro.realm;

import jdk.nashorn.internal.ir.CallNode;
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class CustomerRealm extends AuthorizingRealm {

    / / authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    / / certification
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {     
        return null; }}Copy the code

Write Shiro configuration classes

package com.cheng.config;

import com.cheng.shiro.realm.CustomerRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;


@Configuration
public class ShiroConfig {

    //1. Create a ShiroFilter to intercept all requests
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
      
        return bean;
    }

    / / 2. Create the SecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        securityManager.setRealm(realm);

        return securityManager;
    }
    //3. Create a custom Realm
    @Bean(name = "realm")
    public Realm getRealm(a){
        CustomerRealm customerRealm = new CustomerRealm();
        returncustomerRealm; }}Copy the code

3.2 Implement login interception

The login page

<%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<! doctypehtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="Width =device-width, user-Scalable =no, initial scale=1.0, maximum-scale=1.0, Minimum-scale =1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>

<h1>The login page</h1>
<form action="" method="post">The user name<input type="text" name="username"><br>password<input type="text" name="password"><br>
    <input type="submit" value="Login">

</form>

</body>
</html>
Copy the code

User home page

<%@page contentType="text/ HTML; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %><! doctypehtml>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="Width =device-width, user-Scalable =no, initial scale=1.0, maximum-scale=1.0, Minimum-scale =1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>

<h1 style="color: red">User home page V1.0</h1>
    <ul>
        <li><a href="">User management</a></li>
        <li><a href="">Commodity management</a></li>
        <li><a href="">The order management</a></li>
        <li><a href="">The worker management</a></li>
    </ul>

</body>
</html>
Copy the code

controller

@Controller
@RequestMapping("/user")
public class UserController {}Copy the code

To implement the filter function, add the corresponding filter in the configuration class.

package com.cheng.config;

import com.cheng.shiro.realm.CustomerRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;

@Configuration
public class ShiroConfig {

    //1. Create a ShiroFilter to intercept all requests
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
      
        bean.setSecurityManager(securityManager);

        /* Add filters * anon: access without authentication * authc: Must be authenticated to access * user: Must have remember me function to use * perms: have permission to access a resource * role: have permission to access a role * */
        // Create a map that determines which resources are limited and which resources are public
        HashMap<String, String> map = new HashMap<String, String>();

        // Authentication is required to access index.jsp
        map.put("/index.jsp"."authc");

        // Put the blocked request into a filter
        bean.setFilterChainDefinitionMap(map);

        // Default authentication page. If the login fails, the system automatically switches to this page for authentication
        bean.setLoginUrl("/login.jsp");

        return bean;
    }

    / / 2. Create the SecurityManager
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        securityManager.setRealm(realm);

        return securityManager;
    }
    //3. Create a custom Realm
    @Bean(name = "realm")
    public Realm getRealm(a){
        CustomerRealm customerRealm = new CustomerRealm();
        returncustomerRealm; }}Copy the code

Start the program test, when we try to access the home page, jump to the login interface, intercept success!

3.3. Implement user authentication and logout

User authentication requires the use of our custom Realm

First, the user submits the user information on the login interface, and then receives it with the Controller.

The Controller receives the

    @RequestMapping("/login")
    public String login(String username,String password){

        Subject subject = SecurityUtils.getSubject();

        // Encapsulate the user information submitted by the front end
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            subject.login(token);// Execute the login method. If the execution succeeds, jump to the main page
            return "redirect:/index.jsp";
            // Catch possible exceptions
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("Incorrect user name");
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("Password error");
        }
        return "redirect:/login.jsp";
    }
Copy the code

Login screen

The < %@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false"% > <! doctype html> <html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="Width =device-width, user-Scalable =no, initial scale=1.0, maximum-scale=1.0, Minimum-scale =1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <h1> Login page </h1> <form action="${pageContext.request.contextPath}/user/login" method="post"User name <input type="text" name="username"><br> Password <input type="text" name="password"><br>
    <input type="submit" value="Login">

</form>
</body>
</html>
Copy the code

After receiving the information submitted by the user, it encapsulates the token, and then compares the token with the user information stored in the custom Realm. If the match succeeds, the login operation is performed, and if the match fails, an exception is thrown.

CustomerRealm.java

package com.cheng.shiro.realm;

import jdk.nashorn.internal.ir.CallNode;
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class CustomerRealm extends AuthorizingRealm {

    / / authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }

    / / certification
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("==== has entered the authentication method ====");

        // Obtain identity information
        String principal = (String) token.getPrincipal();
        // Virtual user data for testing,
        if ("wanli".equals(principal)){// If the identity information is successfully authenticated
            return new SimpleAuthenticationInfo(principal,"123".this.getName());
        }

        return null; }}Copy the code

Start the program for testing:

The user name authentication function is tested after an incorrect user name is entered on the login page

Then enter the wrong password to test the password authentication function

Finally, enter the correct user name and password to log in and successfully enter the home page!

The user logged off

Add to the home page

<a href="${pageContext.request.contextPath}/user/logout"> Log out </a>Copy the code

And then write the controller

@RequestMapping("/logout")
public String logout(a){
    Subject subject = SecurityUtils.getSubject();
    subject.logout();
    return "redirect:/login.jsp";
}
Copy the code

3.4 Connect to the database to achieve the registration function based on MD5 salt value encryption

In order to prevent the user password is available, when the user registration, we should put the user submitted before plaintext password saved to the database, using shiro provides us with the MD5 algorithm to plaintext encrypted passwords, and add salt after processing, we can save to the database, at the time of certification in the future, we will be able to read encrypted passwords in the database.

Connecting to the database

Create a Shiro database and create a user table

CREATE DATABASE `shiro`CHARACTER SET utf8 COLLATE utf8_general_ci; 

CREATE TABLE `shiro`.`t_user
( `id` INT(6) NOT NULL AUTO_INCREMENT, 
`username` VARCHAR(60), `password` VARCHAR(60), 
`salt` VARCHAR(30), 
 PRIMARY KEY (`id`) 
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci; 
Copy the code

Import dependence

<! --mybatis-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>
<! -- import mysql dependencies -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>
<! --druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.6</version>
</dependency>
Copy the code

Write database configuration files

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://3306/shiro? characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=19990802

mybatis.type-aliases-package=pojo
mybatis.mapper-locations=classpath:mapper/*.xml
Copy the code

Write a utility class that generates random salt

package com.cheng.utils;

import java.util.Random;

// Generate random salt tool class
public class SaltUtils {
    public static String getSalt(int n){
        // Define an array from which the random salt will be generated
        char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789! @ # $% ^ & * () - = +.?".toCharArray();

        /* Difference between StringBuffer and StringBuilder *StringBuffer string variable (thread safe) multi-threaded operation string * StringBuilder string variable (non-thread safe single-threaded operation string * */
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            // Return a number randomly in the range of chars n times, with the interval being [a,b]
            char aChar = chars[new Random().nextInt(chars.length)];
            sb.append(aChar);
        }
        return sb.toString();
    }
    // Test
    public static void main(String[] args) {
        System.out.println(getSalt(6));//.8mv+#}}Copy the code

Implementing the Registration Service

Dao layer interface

@Mapper
@Repository
public interface UserDao {

    // User registration
   public void save(User user);

}
Copy the code

The service interface layer

public interface UserService {
   public void register(User user);
}
Copy the code

Service layer implementation class, implementation of specific business

package com.cheng.service;

import com.cheng.dao.UserDao;
import com.cheng.pojo.User;
import com.cheng.utils.SaltUtils;
import lombok.experimental.Accessors;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional// Declarative transaction management, enable the normal commit transaction, abnormal rollback transaction
public class UserServiceImpl implements UserService{

    // The service calls the DAO layer
    @Autowired
    private UserDao userDao;

    @Override
    public void register(User user) {
        // Handle business
        //1. Generate random salt 8 bits
        String salt = SaltUtils.getSalt(8);
        //2. Save the random salt to the database
        user.setSalt(salt);
        //3. Encrypt the plaintext password using MD5 + SALT + hash. The number of hashes is 1024
        Md5Hash md5Hash = new Md5Hash(user.getPassword(), salt, 1024);
        // Save the encrypted password to the database
        user.setPassword(md5Hash.toHex());//toHex() Converts a string to a hexadecimal codeuserDao.save(user); }}Copy the code

Start the program to achieve user registration, user information saved to the database, registered successfully!

3.5 Connect to the database to implement the authentication function based on MD5 salt value encryption

To implement the authentication function, we need to query whether there is this user in the database through the user name submitted by the user, so we need to write a method to query the user according to the user name:

Dao layer

// Query the user by user name
User queryUserByName(String username);
Copy the code

The service layer

// Query the user by user name
User queryUserByName(String username);
Copy the code

The Service layer implements classes

@Override
public User queryUserByName(String username) {
    return userDao.queryUserByName(username);
}
Copy the code

Because we encrypted the plaintext password with MD5 salt above, when shrio matches the password asymmetrical, we will change the password verification matcher in the configuration class

//3. Create a custom Realm
@Bean(name = "realm")
public CustomerRealm getRealm(a){
    CustomerRealm customerRealm = new CustomerRealm();
    // Change the password certificate verification matcher
    HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    // Set the encryption algorithm to MD5
    credentialsMatcher.setHashAlgorithmName("MD5");
    // Set the number of hashes
    credentialsMatcher.setHashIterations(1024);
    customerRealm.setCredentialsMatcher(credentialsMatcher);
    return customerRealm;
}
Copy the code

Implementation of user authentication in a custom realm

    / / certification
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        // Obtain identity information
        String principal = (String) token.getPrincipal();

        User user = userService.queryUserByName(principal);

        if(! ObjectUtils.isEmpty(user)){//ByteSource provides an internal method to convert a string to the corresponding salt value information
            return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()),this.getName());
        }

        return null;
    }
Copy the code

Start program test, certification successful!

3.6 Basic Use of authorization

Authorization, also called access control, controls who can access what resources in an application (e.g., access pages/edit data/page operations, etc.).

You need to know the following key objects in authorization: Subject, Resource, Permission, and Role:

  • The principal, the user accessing the application, is represented by Subject in Shiro. Users are allowed to access resources only after being authorized.
  • Resources, urls that can be accessed by users in the application, such as accessing A JSP page, viewing/editing some data, accessing a business method, printing text, and so on.
  • Permission: indicates whether a user has the right to perform operations on a resource. That is, whether an operation is allowed on a resource does not reflect who performs the operation. So the next step is to assign permissions to users, that is, to define which users are allowed to do what on a resource (permissions). Shiro does not do this, but the implementer provides it.
  • Role: A role represents a set of operations and can be understood as a set of permissions. Generally, users are assigned roles rather than permissions. In this way, users can have a set of permissions. Different roles have a different set of permissions.

Shiro authorization process analysis

The process is as follows:

  1. First call the Subject-. isPermitted/hasRole interface, which delegates to the SecurityManager, which in turn delegates to the Authorizer.
  2. An Authorizer is a real Permission. If we call a license like isPermitted(” user:update “), it first transforms the string into the appropriate Permission instance through the PermissionResolver;
  3. Before authorization, it calls the corresponding Realm to get the Subject’s role/permission to match the role/permission passed in;
  4. The Authorizer determines if the Realm’s roles/permissions match the ones passed in. If there are more than one Realm, it delegates the ModularRealmAuthorizer to loop through them. If the match is such as isPermitted*/hasRole*, it returns true. Otherwise, false is returned to indicate authorization failed.

Authorization way

Shiro supports three types of licensing:

1. Programmatic: This is done by writing an if/ ELSE authorization block:

Subject subject = SecurityUtils.getSubject();
if(subject. HasRole (" admin ")) {/ / have permission
} else {
    / / without permission
}
Copy the code

2. Annotated: This is done by placing appropriate annotations on the executed Java methods:

@RequiresRoles("admin")
public void hello(a) {
    / / have permission
}
Copy the code

No permission will throw the appropriate exception;

3.JSP/GSP tag: In the JSP/GSP page through the corresponding tag:

<shiro:hasRole name="admin"> <! - Yes - > </shiro:hasRole>Copy the code

3.6.1. Use Jsp tags for authorization

Introduce the Shiro tag in the page header

The < %@taglib prefix="shiro" uri="http://shiro.apache.org/tags"% >Copy the code

1. Role-based rights management

Add a role to the user:

/ / authorization
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    // Obtain the primary identity information for user authentication
    String primaryPrincipal = (String) principals.getPrimaryPrincipal();
    // Obtain role and permission information based on primary identity information
    if ("xiaowei".equals(primaryPrincipal)){

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        // Add a role to the user
        simpleAuthorizationInfo.addRole("user");

        return simpleAuthorizationInfo;
    }
    return null;
}
Copy the code

The role authorization rules are then defined on the JSP page

<ul>
    <shiro:hasAnyRoles name="user,admin">< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 16px! Important; word-break: inherit! Important;""> User management </a></li> </shiro:hasAnyRoles> <shiro:hasRole name="admin">< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important; white-space: inherit! Important;"">< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; white-space: inherit! Important;"">< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; white-space: inherit! Important;""></ a></li> </shiro:hasRole> </ul>Copy the code

We assign a user role to the current user and start the program to access the main page:

Because the current user role is user, the user only has the permission to access user management.

Let’s change the role of the current user to admin and start the program to access the main page:

2. Authorization management based on permission string

Permission string writing rules: “Resource IDENTIFIER: Operation: object instance ID” means what operations can be performed on which instance of which resource. By default, wildcard permission characters are supported. “:” indicates the split of resources, operations, and instances. “, “indicates the division of operations; * indicates any resource, operation, or instance.

Grant permission to the current user:

User :*:* user represents the module, the first * represents all operations, the second * represents all resources
simpleAuthorizationInfo.addStringPermission("user:*:*");
Copy the code

Then define the permission string authorization rule in the JSP page:

<li><a href=""> shiro:hasPermission name=. </a> <ul> <%-- shiro:hasPermission name="user:add:*">
          <li><a href=""> Add </a></li> </shiro:hasPermission> <shiro:hasPermission name="user:delete:*">
          <li><a href=""> Delete </a></li> </shiro:hasPermission> <shiro:hasPermission name="user:update:*">
          <li><a href=""> Modify </a></li> </shiro:hasPermission> <shiro:hasPermission name="user:find:*">
          <li><a href=""></ a></li> </shiro:hasPermission> </ul> </li>Copy the code

== Note: Shiro does not provide multiple controls like Shiro :hasAnyRoles for control of permission strings in pages, because permission strings can be written as wildcards. = =

The current user is user, the permission is user:*:*, start the program to access the home page:

User :add:* and user:delete:*

simpleAuthorizationInfo.addStringPermission("user:add:*");
simpleAuthorizationInfo.addStringPermission("user:delete:*");
Copy the code

Restart program access home page: At this point, the user only has access to add and delete operations

3.Shiro’s processing of missing part of permission string

  • For example, user:view is equivalent to user:view:. And “organization” is equivalent to “organization:” or “organization::”. So if you think about it this way, it implements prefix matching.

  • In addition, “‘ ‘user:” can match such as “user:delete”, “user:delete” can match such as “user:delete:1”, “user::1” can match such as “user:view:1”, “user” can match “user:view” or “user” : the view: 1 “and so on. That is, all can be matched, no prefix can be matched; For example, “:view” cannot match “system:user:view”, use “::view ‘”, that is, suffix matching must specify the prefix (if multiple colons are used to match).

3.6.2. Use programmatic authorization

Programmatic: Done by writing an if/ ELSE authorization block:

1. Role-based rights management

Assign the admin role to the current user and determine whether the role exists in the code

@RequestMapping("save")
public String saveOrder(a){
    // Get the current principal object
    Subject subject = SecurityUtils.getSubject();
    // If the current principal object has the admin role
    if (subject.hasRole("admin")) {// Handle business
        System.out.println("Save order successfully");
    }else{
        System.out.println("No access");
    }
    return "redirect:/index.jsp";
}
Copy the code

2. Authorization management based on permission string

Give the current user the admin role and the user:update:* permission, and write a controller that modifies the user to test

@RequestMapping("save")
public String addUser(a){
    // Get the current principal object
    Subject subject = SecurityUtils.getSubject();

    // Check whether the current principal object has the user:update:* permission
    if (subject.isPermitted("user:update:*")) {// Handle business
        return "redirect:/update.jsp";
    }else{
        return "redirect:/index.jsp"; }}Copy the code

Write a JSP that modifies the user

The < %@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"% > <! doctype html> <html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="Width =device-width, user-Scalable =no, initial scale=1.0, maximum-scale=1.0, Minimum-scale =1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge"> < title > Document < / title > < / head > < body > < h1 > modify the user < / h1 > < / body > < / HTML >Copy the code

Control the jump on the home page

<shiro:hasPermission name="user:update:*">
    <li><a href="${pageContext.request.contextPath}/user/update"> Modify </a></li> </shiro:hasPermission>Copy the code

Start the program test, because the current user has user:update:* permission, so we click on the modify link, successfully jump!

Shiro also provides a method that requires multiple permissions to access a resource isPermittedAll;

subject.isPermittedAll("user:delete:*"."user:update:*")
Copy the code

3.6.3. Use annotations to implement authorization

This is done by placing the appropriate annotations on the executing Java methods

1. Role-based authorization management

Example Assign the admin role to the current user

simpleAuthorizationInfo.addRole("admin");
Copy the code

Controller:

@RequestMapping("/update")
@RequiresRoles("admin")// Check whether the current user has the role. If yes, perform the following operations
public String updateUser(a){
     // Handle business
     return "redirect:/update.jsp";
Copy the code

Let’s write another controller, and this method needs the user role to execute

@RequestMapping("/delete")
@RequiresRoles("user")// If the current user does not have this role, an exception is reported
public String deleteUser(a){
    // Handle business
    return "redirect:/delete.jsp";
}
Copy the code

Launch the program for testing, access the request: No access!

The @requiresroles annotation can also specify multiple roles, such as:

@RequiresRoles(value={"user","admin"})// You must have all roles to access
Copy the code

2. Permission management based on the permission string

Give the current user the admin role and the user:update:* permission, and write a controller that modifies the user to test

simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addStringPermission("user:update:*");
Copy the code

controller

   @RequestMapping("/update")
   @RequiresPermissions("user:update:01")// Have this permission to perform the following operations,
    public String updateUser(a){
            // Handle business
            return "redirect:/update.jsp";
    }
Copy the code

@requirespermissions is used similarly to the @requiresRoles annotation

3.7 Authorization data persistence

Our authorization data above is written dead, but in a normal development environment, our authorization data must be from the database, and is persistent. Let’s persist the authorization data to the database.

Library table relationship of permission model:

3.7.1 Setting up the Environment

According to the above library table relationship to build the corresponding library table structure

Register two users in the user table above

The username is xiaowei. The password is 123. The ID is 1

Username: xiaopeng Password: 123, id: 2

Create the role table t_role

CREATE TABLE `shiro`.`t_role`( 
    `id` INT(6) NOT NULL AUTO_INCREMENT, 
    `name` VARCHAR(60), 
    PRIMARY KEY (`id`) 
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci; 
Copy the code

T_role Entity class Role

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)// Open chaining
public class Role {
    private int id;
    private String name;
}
Copy the code

Insert data

 INSERT INTO `shiro`.`t_role` (`id`, `name`) VALUES ('1'.'admin');
 INSERT INTO `shiro`.`t_role` (`id`, `name`) VALUES ('2'.'user'); 
 INSERT INTO `shiro`.`t_role` (`id`, `name`) VALUES ('3'.'product'); 
Copy the code

Create permission table t_perms

CREATE TABLE `shiro`.`t_perms`( 
    `id` INT(6) NOT NULL AUTO_INCREMENT, 
    `name` VARCHAR(60), 
    `url` VARCHAR(255), 
    PRIMARY KEY (`id`) 
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci; 
Copy the code

T_perms Entity class perms

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)// Open chaining
public class Perms {
    private int id;
    private String name;
    private String url;
}
Copy the code

Insert data

INSERT INTO `shiro`.`t_perms` (`id`, `name`, `url`) VALUES ('1'.'user:*:*'.'user/*'); 
INSERT INTO `shiro`.`t_perms` (`id`, `name`, `url`) VALUES ('2'.'product:*:*'.'product/*'); 
Copy the code

Create the user role table t_user_role

CREATE TABLE `shiro`.`t_user_role`( 
    `id` INT(6) NOT NULL, 
    `userid` INT(6), 
    `roleid` INT(6), 
    PRIMARY KEY (`id`) 
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci; 
Copy the code

Insert data

INSERT INTO `shiro`.`t_user_role` (`id`, `userid`, `roleid`) VALUES ('1'.'1'.'1'); 
INSERT INTO `shiro`.`t_user_role` (`id`, `userid`, `roleid`) VALUES ('2'.'1'.'2'); 
INSERT INTO `shiro`.`t_user_role` (`id`, `userid`, `roleid`) VALUES ('3'.'2'.'2'); 
Copy the code

The roles of user xiaowei are admin and user

The second user, xiaopeng, has the user role

Create the role permission table t_roLE_perms

CREATE TABLE `shiro`.`t_role_perms`( 
    `id` INT(6), 
    `roleid` INT(6), 
    `permsid` INT(6) 
) ENGINE=INNODB CHARSET=utf8 COLLATE=utf8_general_ci; 
Copy the code

Insert data

INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES ('1'.'1'.'1');
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES ('2'.'1'.'2');
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES ('3'.'2'.'1');
INSERT INTO `shiro`.`t_role_perms` (`id`, `roleid`, `permsid`) VALUES ('4'.'3'.'2');
Copy the code

Role admin has the user:*:* and product:*:* rights

Role 2 user has the user:*:* permission

Role 3, product, has the product:*:* permission

3.7.2 Obtaining Role Information from the Database

Since the User and role relationships are one-to-many, add one more attribute to the User entity class:

// Define the set of roles
private List<Role> roles;
Copy the code

Define role authorization rules for resources:

<ul>
    <shiro:hasAnyRoles name="user,admin">< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 16px! Important; word-break: inherit! Important;""> shiro:hasPermission name=. </a> <ul> <%-- shiro:hasPermission name="user:add:*">
                  <li><a href=""> Add </a></li> </shiro:hasPermission> <shiro:hasPermission name="user:delete:*">
                  <li><a href="${pageContext.request.contextPath}/user/delete"> Delete </a></li> </shiro:hasPermission> <shiro:hasPermission name="user:update:*">
                  <li><a href="${pageContext.request.contextPath}/user/update"> Modify </a></li> </shiro:hasPermission> <shiro:hasPermission name="user:find:*">
                  <li><a href="${pageContext.request.contextPath}/user/find"> Query </a></li> </shiro:hasPermission> </ul> </li> </shiro:hasAnyRoles> <shiro:hasRole name="admin">< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important; white-space: inherit! Important;"">< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; white-space: inherit! Important;"">< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; white-space: inherit! Important;""></ a></li> </shiro:hasRole> </ul>Copy the code

Query the role of the user in the database based on the user name:

Dao layer

// Query the role by user name
User queryRoleByName(String username);
Copy the code

UserDao.xml

<resultMap id="userMap" type="User">
    <result column="uid" property="id"></result>
    <result column="username" property="username"></result>
    <! -- Role information -->
    <collection property="roles" ofType="Role" javaType="list">
        <result column="ird" property="id"/>
        <result column="rname" property="name"/>
    </collection>
</resultMap>

<select id="queryRoleByName" resultMap="userMap" parameterType="String">
    select tu.id uid,tu.username,tr.id rid,tr.name rname
    from shiro.t_user tu left join shiro.t_user_role tur on tu.id=tur.userid
    left join shiro.t_role tr on tr.id=tur.roleid
    where username=#{username}
</select>
Copy the code

The service layer

// Query the role by user name
User queryRoleByName(String username);
Copy the code

The Service layer implements classes

@Override
public User queryRoleByName(String username) {
    return userDao.queryRoleByName(username);
}
Copy the code

Retrieve user roles from the database and authenticate them in a custom Realm

/ / authorization
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    // Obtain the primary identity information for user authentication
    String primaryPrincipal = (String) principals.getPrimaryPrincipal();

    User user = userService.queryRoleByName(primaryPrincipal);
    List<Role> roles = user.getRoles();
    // If the role information is not empty
    if(! CollectionUtils.isEmpty(roles)){ SimpleAuthorizationInfo simpleAuthorizationInfo =new SimpleAuthorizationInfo();
        roles.forEach(role->{     // Iterate over all roles
            simpleAuthorizationInfo.addRole(role.getName());
        });
        return simpleAuthorizationInfo;
    }
    return null;
}
Copy the code

Start the program, first login user Xiaowei, admin and user roles

Log in to xiaopeng as user and set the role to user

3.7.3 Obtaining the Permission String from the Database

Since the user and Role are in a one-to-many relationship, add another attribute to the Role entity class:

// Define a set of permissions
private List<Perms> perms;
Copy the code

Define permission string authorization rules for resources:

<ul>
    <shiro:hasAnyRoles name="user,admin">< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 16px! Important; word-break: inherit! Important;""> shiro:hasPermission name=. </a> <ul> <%-- shiro:hasPermission name="user:add:*">
                  <li><a href=""> Add </a></li> </shiro:hasPermission> <shiro:hasPermission name="user:delete:*">
                  <li><a href="${pageContext.request.contextPath}/user/delete"> Delete </a></li> </shiro:hasPermission> <shiro:hasPermission name="user:update:*">
                  <li><a href="${pageContext.request.contextPath}/user/update"> Modify </a></li> </shiro:hasPermission> <shiro:hasPermission name="user:find:*">
                  <li><a href="${pageContext.request.contextPath}/user/find"> Query </a></li> </shiro:hasPermission> </ul> </li> </shiro:hasAnyRoles> <shiro:hasRole name="admin">< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; font-size: 14px! Important; white-space: inherit! Important;""</a> <ul> <%-- shiro:hasPermission name="product:add:*">
                <li><a href=""> Add </a></li> </shiro:hasPermission> <shiro:hasPermission name="product:delete:*">
                <li><a href="${pageContext.request.contextPath}/product/delete"> Delete </a></li> </shiro:hasPermission> <shiro:hasPermission name="product:update:*">
                <li><a href="${pageContext.request.contextPath}/product/update"> Modify </a></li> </shiro:hasPermission> <shiro:hasPermission name="product:find:*">
                <li><a href="${pageContext.request.contextPath}/product/find"Query > < / a > < / li > < / shiro: hasPermission > < / ul > < / li > < li > < a href ="">< span style = "box-sizing: border-box; color: RGB (74, 74, 74); line-height: 22px; white-space: inherit! Important;""></ a></li> </shiro:hasRole> </ul>Copy the code

Query permission information by user role:

Dao layer

// Query permission information based on the role ID
List<Perms> findAllPermsByRoleId(int id);
Copy the code

Dao layer XML file

<select id="findAllPermsByRoleId" resultType="Perms"  parameterType="int">
    select tp.id,tp.name,tp.url,tr.name tname from t_role tr
    left join t_role_perms trp on tr.id=trp.roleid
    left join t_perms tp on trp.permsid = tp.id
    where tr.id=#{id}
</select>
Copy the code

The service layer

// Query permission information based on the role ID
List<Perms> findAllPermsByRoleId(int id);
Copy the code

The service implementation class

@Override
public List<Perms> findAllPermsByRoleId(int id) {
    return userDao.findAllPermsByRoleId(id);
}
Copy the code

Custom realm

 / / authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // Obtain the primary identity information for user authentication
        String primaryPrincipal = (String) principals.getPrimaryPrincipal();
        System.out.println("primaryPrincipal-->"+primaryPrincipal);

        User user = userService.queryRoleByName(primaryPrincipal);
        System.out.println(user);
        List<Role> roles = user.getRoles();
        // If the role information is not empty
        if(! CollectionUtils.isEmpty(roles)){ SimpleAuthorizationInfo simpleAuthorizationInfo =new SimpleAuthorizationInfo();
            roles.forEach(role -> {// Iterate over all roles
                simpleAuthorizationInfo.addRole(role.getName());// Put the role information into the Authorizer for comparison

                // Obtain permission information
                List<Perms> perms = userService.findAllPermsByRoleId(role.getId());

                System.out.println(perms);

                if(! CollectionUtils.isEmpty(perms)&& perms.get(0)! =null){ perms.forEach(perm -> { simpleAuthorizationInfo.addStringPermission(perm.getName()); }); }});return simpleAuthorizationInfo;
        }
        return null;
    }
Copy the code

Startup program test:

User Xiaowei logs in first. The roles of user are admin and user. The permissions of admin are user/*/* and product/*/*, and the permissions of user are user/*/*

The login user is xiaopeng. The role is user and the permission is user/*/*

Hit here, we have completed the permission data persistence function!

Integrate the SpringBoot cache

It is clear from shiro’s internal frame diagram that CacheManager is one of the main components in Shiro’s architecture. Shiro implements permission data caching through CacheManager. When the permission information is stored in the database, a database query is required for each front end access request. Especially in the scenario where shiro’s JSP tags are used in large quantities, a page access request corresponding to the front end will have many permission query operations at the same time. It is very uneconomical to carry out a large number of permission database queries for each front end page access when the permission information changes infrequently. Therefore, it is very necessary to use a caching scheme for permission data.

Shiro provides only one implementation that supports specific caching (e.g. Hazelcast, Ehcache, OSCache, Terracotta, Coherence, GigaSpaces, JBossCache, etc.) This gives Shiro users the flexibility to choose a specific CacheManager based on their needs.

Caching simple process:

3.8.1 Ehcache is used in Shiro

Import dependence

<! -- Shiro and EhCache integration dependencies -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.5.3</version>
</dependency>
Copy the code

Set up the cache manager in the Shiro configuration (shiroConfig)

//3. Create a custom Realm
@Bean(name = "realm")
public Realm getRealm(a){
    CustomerRealm customerRealm = new CustomerRealm();
    // Change the password certificate verification matcher
    HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    // Set the encryption algorithm to MD5
    credentialsMatcher.setHashAlgorithmName("MD5");
    // Set the number of hashes
    credentialsMatcher.setHashIterations(1024);
    customerRealm.setCredentialsMatcher(credentialsMatcher);

    
    // Enable cache ehcache management
    customerRealm.setCacheManager(new EhCacheManager());
    // Enable global cache management
    customerRealm.setCachingEnabled(true);
    // Enable the authentication cache
    customerRealm.setAuthenticationCachingEnabled(true);
    // Set the authentication cache name
    customerRealm.setAuthenticationCacheName("authenticationCache");
    // Enable the authorization cache
    customerRealm.setAuthorizationCachingEnabled(true);
    // Set the authorization cache name
    customerRealm.setAuthorizationCacheName("authorizationCache");
    
    return customerRealm;
}
Copy the code

After setting the cache, only the first query will operate the database, the second query will not operate the database, effectively reducing the burden of the database. However, if our application is restarted, the database is still being manipulated the first time when we query again.

3.9 Picture verification code implementation

1. Configure the verification code tool

package com.cheng.utils;

import java.io.IOException;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Random;

public class VerifyCodeUtils {
    // use the Algerian font, and install the font if it is not Algerian. The font is only uppercase, and the 1,0, I,o characters are removed
    public static final String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
    private static Random random = new Random();


    /** * Use the default character source to generate the verification code *@paramVerifySize Length of the verification code (x)@return* /
    public static String generateVerifyCode(int verifySize){
        return generateVerifyCode(verifySize, VERIFY_CODES);
    }
    /** * generate verification code * with the specified source@paramVerifySize Length of the verification code (x)@paramSources Indicates the source of the verification code (*)@return* /
    public static String generateVerifyCode(int verifySize, String sources){
        if(sources == null || sources.length() == 0){
            sources = VERIFY_CODES;
        }
        int codesLen = sources.length();
        Random rand = new Random(System.currentTimeMillis());
        StringBuilder verifyCode = new StringBuilder(verifySize);
        for(int i = 0; i < verifySize; i++){
            verifyCode.append(sources.charAt(rand.nextInt(codesLen-1)));
        }
        return verifyCode.toString();
    }

    /** * generate a random verification code file and return the verification code value *@param w
     * @param h
     * @param outputFile
     * @param verifySize
     * @return
     * @throws IOException
     */
    public static String outputVerifyImage(int w, int h, File outputFile, int verifySize) throws IOException{
        String verifyCode = generateVerifyCode(verifySize);
        outputImage(w, h, outputFile, verifyCode);
        return verifyCode;
    }

    /** * Output a random captcha image stream and return the captcha value *@param w
     * @param h
     * @param os
     * @param verifySize
     * @return
     * @throws IOException
     */
    public static String outputVerifyImage(int w, int h, OutputStream os, int verifySize) throws IOException{
        String verifyCode = generateVerifyCode(verifySize);
        outputImage(w, h, os, verifyCode);
        return verifyCode;
    }

    /** * generate the image file with the specified verification code *@param w
     * @param h
     * @param outputFile
     * @param code
     * @throws IOException
     */
    public static void outputImage(int w, int h, File outputFile, String code) throws IOException{
        if(outputFile == null) {return;
        }
        File dir = outputFile.getParentFile();
        if(! dir.exists()){ dir.mkdirs(); }try{
            outputFile.createNewFile();
            FileOutputStream fos = new FileOutputStream(outputFile);
            outputImage(w, h, fos, code);
            fos.close();
        } catch(IOException e){
            throwe; }}/** * output the specified verification code image stream *@param w
     * @param h
     * @param os
     * @param code
     * @throws IOException
     */
    public static void outputImage(int w, int h, OutputStream os, String code) throws IOException{
        int verifySize = code.length();
        BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Random rand = new Random();
        Graphics2D g2 = image.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        Color[] colors = new Color[5];
        Color[] colorSpaces = new Color[] { Color.WHITE, Color.CYAN,
                Color.GRAY, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
                Color.PINK, Color.YELLOW };
        float[] fractions = new float[colors.length];
        for(int i = 0; i < colors.length; i++){
            colors[i] = colorSpaces[rand.nextInt(colorSpaces.length)];
            fractions[i] = rand.nextFloat();
        }
        Arrays.sort(fractions);

        g2.setColor(Color.GRAY);// Set the border color
        g2.fillRect(0.0, w, h);

        Color c = getRandColor(200.250);
        g2.setColor(c);// Set the background color
        g2.fillRect(0.2, w, h-4);

        // Draw the interference line
        Random random = new Random();
        g2.setColor(getRandColor(160.200));// Set the color of the line
        for (int i = 0; i < 20; i++) {
            int x = random.nextInt(w - 1);
            int y = random.nextInt(h - 1);
            int xl = random.nextInt(6) + 1;
            int yl = random.nextInt(12) + 1;
            g2.drawLine(x, y, x + xl + 40, y + yl + 20);
        }

        // Add noise
        float yawpRate = 0.05 f;/ / noise ratio
        int area = (int) (yawpRate * w * h);
        for (int i = 0; i < area; i++) {
            int x = random.nextInt(w);
            int y = random.nextInt(h);
            int rgb = getRandomIntColor();
            image.setRGB(x, y, rgb);
        }

        shear(g2, w, h, c);// Distort the image

        g2.setColor(getRandColor(100.160));
        int fontSize = h-4;
        Font font = new Font("Algerian", Font.ITALIC, fontSize);
        g2.setFont(font);
        char[] chars = code.toCharArray();
        for(int i = 0; i < verifySize; i++){
            AffineTransform affine = new AffineTransform();
            affine.setToRotation(Math.PI / 4 * rand.nextDouble() * (rand.nextBoolean() ? 1 : -1), (w / verifySize) * i + fontSize/2, h/2);
            g2.setTransform(affine);
            g2.drawChars(chars, i, 1, ((w-10) / verifySize) * i + 5, h/2 + fontSize/2 - 10);
        }

        g2.dispose();
        ImageIO.write(image, "jpg", os);
    }

    private static Color getRandColor(int fc, int bc) {
        if (fc > 255)
            fc = 255;
        if (bc > 255)
            bc = 255;
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }

    private static int getRandomIntColor(a) {
        int[] rgb = getRandomRgb();
        int color = 0;
        for (int c : rgb) {
            color = color << 8;
            color = color | c;
        }
        return color;
    }

    private static int[] getRandomRgb() {
        int[] rgb = new int[3];
        for (int i = 0; i < 3; i++) {
            rgb[i] = random.nextInt(255);
        }
        return rgb;
    }

    private static void shear(Graphics g, int w1, int h1, Color color) {
        shearX(g, w1, h1, color);
        shearY(g, w1, h1, color);
    }

    private static void shearX(Graphics g, int w1, int h1, Color color) {

        int period = random.nextInt(2);

        boolean borderGap = true;
        int frames = 1;
        int phase = random.nextInt(2);

        for (int i = 0; i < h1; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862 D * (double) phase)
                    / (double) frames);
            g.copyArea(0, i, w1, 1, (int) d, 0);
            if (borderGap) {
                g.setColor(color);
                g.drawLine((int) d, i, 0, i);
                g.drawLine((int) d + w1, i, w1, i); }}}private static void shearY(Graphics g, int w1, int h1, Color color) {

        int period = random.nextInt(40) + 10; / / 50;

        boolean borderGap = true;
        int frames = 20;
        int phase = 7;
        for (int i = 0; i < w1; i++) {
            double d = (double) (period >> 1)
                    * Math.sin((double) i / (double) period
                    + (6.2831853071795862 D * (double) phase)
                    / (double) frames);
            g.copyArea(i, 0.1, h1, 0, (int) d);
            if (borderGap) {
                g.setColor(color);
                g.drawLine(i, (int) d, i, 0);
                g.drawLine(i, (int) d + h1, i, h1); }}}public static void main(String[] args) throws IOException {
        // Obtain the verification code
        String s = generateVerifyCode(4);
        // Insert the validation code into the image
        outputImage(260.60.new File("/ Users/chenyannan/Desktop/work Ann information/aa. JPG"),s); System.out.println(s); }}Copy the code

2. Implement the verification code method

// Verification code method
@RequestMapping("/getImage")
public void getImage(HttpSession session, HttpServletResponse response) throws IOException {
    // Call the verification code utility class to generate the verification code, 4 bits verification code
    String code = VerifyCodeUtils.generateVerifyCode(4);
    // Store the verification code in session and compare the verification code during login
    session.setAttribute("code",code);
    // Put the verification code in the image
    ServletOutputStream os = response.getOutputStream();
    // Set the response type
    response.setContentType("image/png");
    VerifyCodeUtils.outputImage(220.60,os,code);
}
Copy the code

3. Add a verification code on the login page

Please enter the verification code <input type="text" name="code" ><img src="${pageContext.request.contextPath}/user/getImage" alt=""><br>
Copy the code

4. On Shiro, configure shiroConfig to permit the verification code request

map.put("/user/getImage"."anon");// Public resources
Copy the code

5. Compare verification codes in authentication methods

@RequestMapping("/login")
    public String login(String username,String password,String code,HttpSession session){

        // Verify code comparison
        String code1 = (String)session.getAttribute("code");// Get the verification code in session
        // If the verification code is correct, log in
        try {
        if (code1.equals(code)){
            Subject subject = SecurityUtils.getSubject();
            // Encapsulate the user information submitted by the front end
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
                subject.login(token);// Execute the login method. If the execution succeeds, jump to the main page
                return "redirect:/index.jsp";
                // Catch possible exceptions
        }else {
            throw new RuntimeException("Verification code error"); }}catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("Incorrect user name");
            return "redirect:/login.jsp";
        } catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("Password error");
        }catch (Exception e){
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
        return "redirect:/login.jsp";
    }
Copy the code

Start the program, test:

Successful login, OK!