61. Introduction to shiro zero Foundation (Foundation completion source code to be supplemented)

shiro (java security framework)

1 Introduction

Apache Shiro is a security framework for Java. At present, more and more people use Apache Shiro because it is quite simple. Compared with Spring Security, it may not be as powerful as Spring Security, but it may not need so complex things in actual work, so using a small and simple Shiro is enough. There is no need to tangle about which of them is better. It's better to solve the project problems more simply.

Shiro can easily develop good enough applications, which can be used not only in Java se environment, but also in Java EE environment. Shiro can help us complete: authentication, authorization, encryption, session management, integration with the Web, caching, etc. This is what we want, and Shiro's API is also very simple; Its basic function points are shown in the figure below:

  • Authentication: identity authentication / login to verify whether the user has the corresponding identity;
  • Authorization: authorization, i.e. permission verification, which verifies whether an authenticated user has a certain permission; That is to judge whether a user can do something. Common examples are: verifying whether a user has a role. Or fine-grained verification of whether a user has certain permissions on a resource;
  • Session Management: Session Management, that is, after a user logs in, it is a session. Before exiting, all its information is in the session; The session can be in ordinary JavaSE environment or in Web environment;
  • Cryptography: encryption to protect the security of data. For example, the password is encrypted and stored in the database instead of plaintext;
  • Web Support: Web Support, which can be easily integrated into the web environment;
  • For example, users do not have to cache information after each login, so that the efficiency can be improved;
  • Concurrency: shiro supports concurrent verification of multithreaded applications, that is, if you start another thread in one thread, you can automatically propagate permissions in the past;
  • Testing: provide test support;
  • Run As: allow one user to pretend to be the identity of another user (if they allow it);
  • Remember Me: Remember Me, this is a very common function, that is, after logging in once, you don't need to log in next time.

Remember that Shiro will not maintain users and permissions; These need to be designed / provided by ourselves; Then inject it into Shiro through the corresponding interface.

shiro implementation principle

  • Subject
  • SecurityManager
  • Realms

Subject: subject, which represents the current "user". This user is not necessarily a specific person. Anything interacting with the current application is a subject, such as web crawler, robot, etc; An abstract concept; All subjects are bound to SecurityManager, and all interactions with subjects will be delegated to SecurityManager; You can think of subject as a facade; The SecurityManager is the actual executor;

SecurityManager: Security Manager; That is, all security related operations will interact with SecurityManager; And it manages all subjects; It can be seen that it is the core of Shiro. It is responsible for interacting with other components introduced later. If you have studied spring MVC, you can regard it as the front-end controller of dispatcher servlet;

Realm: domain. Shiro obtains security data (such as users, roles and permissions) from realm, which means that if SecurityManager wants to verify user identity, it needs to obtain corresponding users from realm for comparison to determine whether the user identity is legal; You also need to get the corresponding role / authority of the user from realm to verify whether the user can operate; You can think of realm as a DataSource, that is, a secure data source.

The application code is authenticated and authorized through the Subject, which is entrusted to the SecurityManager; We need to inject real into Shiro's SecurityManager so that the SecurityManager can obtain legal users and their permissions for judgment.

2. Architecture understanding

From Shiro's internal perspective, Shiro's architecture is shown in the following figure:

  • Subject: subject. It can be seen that the subject can be any "user" who can interact with the application;
  • SecurityManager: equivalent to dispatcher servlet in spring MVC or FilterDispatcher in struts 2; It's Shiro's heart; All specific interactions are controlled through the SecurityManager; It manages all subjects and is responsible for authentication and authorization, session and cache management.
  • Authenticator: authenticator, which is responsible for principal authentication. This is an extension point. If users think Shiro's default is not good, they can customize the implementation; It needs Authentication Strategy, that is, when the user authentication is passed;
  • Authorizer: authorizer or access controller, which is used to determine whether the subject has permission to perform corresponding operations; That is, it controls which functions users can access in the application;
  • Realm: there can be one or more realms, which can be considered as the data source of security entity, that is, the data source used to obtain security entity; It can be implemented by JDBC, LDAP or memory; Provided by the user; Note: Shiro doesn't know where your users / permissions are stored and in what format; Therefore, we generally need to implement our own realm in applications;
  • SessionManager: if you have written servlets, you should know the concept of session. Session needs someone to manage its life cycle. This component is SessionManager; Shiro can be used not only in the Web environment, but also in ordinary JavaSE environment, EJB and other environments; Therefore, Shiro abstracts a session of its own to manage the data interaction between the subject and the application; In this case, for example, when we used it in the Web environment, it was a Web server at the beginning; Then I went to an EJB server; At this time, if you want to put the session data of the two servers in one place, you can realize your own distributed session (such as putting the data on the Memcached server);
  • SessionDAO: DAO has been used by everyone. Data access objects and CRUD for sessions. For example, if we want to save sessions to the database, we can implement our own SessionDAO and write to the database through JDBC; For example, if you want to put a Session into Memcached, you can implement your own Memcached SessionDAO; In addition, in SessionDAO, Cache can be used for caching to improve performance;
  • CacheManager: cache controller to manage the cache of users, roles, permissions, etc; Because these data are rarely changed, putting them in the cache can improve the performance of access
  • Cryptography: password module. Shiro provides some common encryption components, such as password encryption / decryption.

3. Introductory case (official case)

3.1. Import dependency

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>

        <!-- configure logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.21</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>

        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.2</version>
        </dependency>
    </dependencies>

resources/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

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

3.2. Example code

package com.sl;

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;

/**
 * Simple Quickstart application showing how to use Shiro's API.
 *
 * @since 0.9 RC2
 */
public class Quickstart {

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


    public static void main(String[] args) {
        //1. Get the SecurityManager factory. Here, use the Ini configuration file to initialize the SecurityManager
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //2. Get the SecurityManager instance and bind it to SecurityUtils
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);

        //3. Get the Subject and create the user name / password authentication Token (i.e. user identity / certificate)
        Subject currentUser = SecurityUtils.getSubject();

        //Using session
        Session session = currentUser.getSession();
        session.setAttribute("someKey", "aValue");
        String value = (String) session.getAttribute("someKey");
        if (value.equals("aValue")) {
            log.info("Retrieved the correct value! [" + value + "]");
        }

        //Log in to the current user so that we can check roles and permissions:
        if (!currentUser.isAuthenticated()) {
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
                //4. Login, i.e. authentication
                currentUser.login(token);
            } 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.");
            }
            // ... catch more exceptions here (maybe custom ones specific to your application?
            catch (AuthenticationException ae) {
                //unexpected condition?  error?
            }
        }

        //say who they are:
        //print their identifying principal (in this case, a username):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //Check role
        if (currentUser.hasRole("schwartz")) {
            log.info("May the Schwartz be with you!");
        } else {
            log.info("Hello, mere mortal.");
        }

        //Verify permissions lightsaber:*
        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.");
        }

        //Verify permissions winnebago:drive:eagle5
        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!");
        }

        //sign out
        currentUser.logout();

        System.exit(0);
    }
}

4. Integrate springboot

Environment construction

1. Import dependency

        <!--shiro relevant-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>

Complete pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.sl</groupId>
    <artifactId>shiro-springboot</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shiro-springboot</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--shiro relevant-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. Custom Realm(UserRealm)

package com.sl.shirospringboot.shiro;

import com.sl.shirospringboot.entity.User;
import com.sl.shirospringboot.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * @author 86178
 * @version 1.0.0
 * @ClassName UserRealm.java
 * @Description TODO
 * @createTime 2021 December 25, 2013 13:10:00
 */
@Slf4j
public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    /**
     * User authorization
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("User authorization");
        return null;
    }

    /**
     * User authentication
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("User authentication");
        return null;
    }
}

3. Create ShiorConfig

package com.sl.shirospringboot.shiro;

import org.apache.shiro.mgt.SecurityManager;
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.LinkedHashMap;
import java.util.Map;

/**
 * @author 86178
 * @version 1.0.0
 * @ClassName Shiro.java
 * @Description TODO
 * @createTime 2021 10:38:00, December 19
 */
@Configuration
public class ShiroConfig {

    /**
     * Custom userRealm
     * @return
     */
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

    /**
     * SecurityManager Create method
     * @param userRealm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {

        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(userRealm);
        return defaultWebSecurityManager;
    }

    /**
     *
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //Set up security manager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }
}

Achieve login interception

Add the following logic in the getShiroFilterFactoryBean function of ShiroConfig class

    /**
     *
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //Set up security manager
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //Add shiro's built-in filter
        /**
         * anon:Access without authentication
         * authc: You must be certified to use this method
         * user: You must have the function of remembering me to use
         * perms: You must have permission on a resource to access it
         * role: You must have a role permission to access
         */
        Map<String, String> filterMap = new LinkedHashMap<>();
        //Release url
        filterMap.put("/login", "anon");

        //Intercept url
        filterMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        //Set login url
        shiroFilterFactoryBean.setLoginUrl("/toLogin");

        return shiroFilterFactoryBean;
    }

Implement user authentication

Login page

<!DOCTYPE html>
<html lang="en" xml:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Sign in</title>
</head>
<body>
    <form th:action="@{/login}">
        <p><input type="text" name="username"/></p>
        <p><input type="text" name="password"/></p>
        <p><input type="submit" ></p>
    </form>

</body>
</html>

Add logic in Controller

    @GetMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

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

        //Get current user
        Subject subject = SecurityUtils.getSubject();
        //Encapsulate user login data
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);

        try {
            subject.login(token);
            //Log in successfully and jump to home page
            return "home";
        } catch (UnknownAccountException uae) {//user does not exist
            model.addAttribute("msg", "user name does not exist");
            return "login";
        } catch (IncorrectCredentialsException ice) {//Password error
            model.addAttribute("msg", "Password error");
            return "login";
        } catch (LockedAccountException lae) {
            model.addAttribute("msg", "Account locked");
            return "login";
        }
    }

Modify the doGetAuthenticationInfo function in UserRealm

    /**
     * User authentication
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("User authentication");

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //Query user information
        User user = userService.getUserByUserName(token.getUsername());

        if (user == null) {
            //If the user does not exist, shiro will throw an UnknownAccountException
            return null;
        }

		//Password authentication shiro framework processing
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPwd(), "");
        return info;
    }

User authorization

Modify getShiroFilterFactoryBean function of ShiroConfig class and add logic

//Set permission interception
filterMap.put("/user/add", "perms[user:add]");
filterMap.put("/user/update", "perms[user:update]");

//Set unauthorized url
shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");

Full getShiroFilterFactoryBean function

/**
 *
 * @param securityManager
 * @return
 */
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("getDefaultWebSecurityManager") SecurityManager securityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    //Set up security manager
    shiroFilterFactoryBean.setSecurityManager(securityManager);

    //Add shiro's built-in filter
    /**
     * anon:Access without authentication
     * authc: You must be certified to use this method
     * user: You must have the function of remembering me to use
     * perms: You must have permission on a resource to access it
     * role: You must have a role permission to access
     */
    Map<String, String> filterMap = new LinkedHashMap<>();
    //Release the swagger UI address
    filterMap.put("/swagger/**", "anon");
    //Web resources
    filterMap.put("/layuiadmin/**", "anon");
    filterMap.put("/login", "anon");

    //Set permission interception
    filterMap.put("/user/add", "perms[user:add]");
    filterMap.put("/user/update", "perms[user:update]");

    //intercept
    filterMap.put("/**", "authc");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

    //Set login url
    shiroFilterFactoryBean.setLoginUrl("/toLogin");
    //Set unauthorized url
    shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");

    return shiroFilterFactoryBean;
}

Modify the doGetAuthorizationInfo function of UserRealm class

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    log.info("User authorization");

    //Get the current login object
    Subject subject = SecurityUtils.getSubject();
    User currentUser = (User) subject.getPrincipal();

    //Set current user permissions
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    info.addStringPermission(currentUser.getPerms());
    return info;
}

explain:

User currentUser = (User) subject.getPrincipal();

It is the first parameter user passed in by the new SimpleAuthenticationInfo object in user authentication

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPwd(), "");

Integrate thymeleaf

Import dependency

<!--shiro-thymeleaf Integration package-->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

Modify ShiroConfig

@Bean
public ShiroDialect getShiroDialect(){
    return new ShiroDialect();
}
Subject currentUser = SecurityUtils.getSubject();
//Set session
Session session = currentUser.getSession();
session.setAttribute("loginUser", "11111");

home. Add in HTML file

<html lang="en" xml:th="http://www.thymeleaf.org" 
				xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">

perhaps

<html lang="en" xmlns:th="http://www.thymeleaf.org"
                xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

Complete content

<!DOCTYPE html>
<html lang="en" xml:th="http://www.thymeleaf.org"
                xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>home page</title>
</head>
<body>

<h1>home page</h1>
<p th:text="${msg}"></p>

<!--from seesion Determine whether there are users in-->
<div th:if="${session.loginUser==null} ">
    <a th:href="@{/toLogin}">land</a>
</div>
<div th:if="${session.loginUser != null}">
    <a th:href="@{/logout}">cancellation</a>
</div>
<div shiro:hasPermission="user:add">
    <a th:href="@{/user/add}">add</a>
</div>
<div shiro:hasPermission="user:update">
    <a th:href="@{/user/update}">update</a>
</div>

</body>
</html>

Reference articles

Official website: https://shiro.apache.org/

https://www.w3cschool.cn/shiro/co4m1if2.html

Advanced - source code interpretation

To be added

Keywords: Java Maven Shiro Spring

Added by scuba on Wed, 02 Mar 2022 15:12:18 +0200