Introduction to Spring Boot integration Shiro

Posted by Lustre on Sat, 08 Jan 2022 10:07:26 +0100

Introduction to Shiro

Apache Shiro is an open source lightweight Java security framework, which provides authentication, authorization, password management, session management and other functions. Compared with Spring Security, Shiro framework is more intuitive and easy to use, and can also provide robust security. In the traditional SSM framework, there are many steps to manually integrate Shiro configuration. For Spring Boot, Shiro officially provides Shiro Spring Boot web starter to simplify Shiro configuration in Spring Boot. Here are the steps to use Shiro Spring Boot web starter.

Integrate Shiro

1. Create project

First, create a normal Spring Boot Web project


Add Shiro dependency and page template dependency. The code is as follows:

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thyrneleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

Note that there is no need to add spring boot starter web dependency here. Shiro spring boot web starter already relies on spring boot starter web.

At the same time, the Thymeleaf template is used in this case, so the Thymeleaf dependency is added. In addition, in order to use the shiro tag in Thymeleaf, the Thymeleaf extras shiro dependency is introduced.

2. Shiro basic configuration

2.1 application.properties

First, in application The basic information for configuring Shiro in properties is as follows:

shiro.enabled=true
shiro.web.enabled=true
shiro.loginUrl=/login
shiro.successUrl=/index
shiro.unauthorizedUrl=/unauthorized
shiro.sessionManager.sessionidUrlRewritngEnabled=true
shiro.sessionManager.sessionidCookieEnabled=true

Code interpretation:

• the configuration in line 1 indicates that Shiro configuration is enabled, and the default value is true
• the configuration in line 2 indicates that Shiro Web configuration is enabled, and the default value is true
• the configuration in line 3 indicates the login address, which is "login.jsp" by default
• the configuration in line 4 indicates the login success address, which is "/" by default.
• line 5 configuration indicates unauthorized default jump address.
• the configuration in line 6 indicates whether session tracking is allowed through URL parameters. If the website supports cookies, this option can be turned off and the default is true
• the configuration in line 7 indicates whether session tracking is allowed through cookies. The default value is true

2.2 ShiroConfig.class

After configuring the basic information, configure Shiro in the Java code and provide two basic beans. The code is as follows:

ShiroConfig.class

package org.sang.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.TextConfigurationRealm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {
    @Bean
    public Realm realm() {
        TextConfigurationRealm realm = new TextConfigurationRealm();
        realm.setUserDefinitions("sang=123,user\n admin=123,admin");
        realm.setRoleDefinitions("admin=read,write\n user=read");
        return realm;
    }
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition =
                new DefaultShiroFilterChainDefinition();
        chainDefinition.addPathDefinition("/login", "anon");
        chainDefinition.addPathDefinition("/doLogin", "anon");
        chainDefinition.addPathDefinition("/logout", "logout");
        chainDefinition.addPathDefinition("/**", "authc");
        return chainDefinition;
    }
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}

Code interpretation:

Two key beans are provided here, one is Realm and the other is ShiroFilterChainDefinition As for ShiroDialect, it is to support the use of Shiro tag in Thymeleaf. If Shiro tag is not used in Thymeleaf, ShiroDialect may not be provided.

Realm s can be customized realms or realms provided by Shiro. For simplicity, there is no database connection configured in this case. Here, two users are directly configured: sang/123 and admin/123, corresponding to the roles user and admin respectively. User has read permission and admin has read write permission.

The ShiroFilterChainDefinition Bean is equipped with basic filtering rules, "/ login" and "/ doLogin" can be accessed anonymously, "logout" is a logout login request, and other requests can be accessed only after authentication.

2.3 UserController.class

Next, configure the login interface and page access interface. The code is as follows:

UserController.class

package org.sang.shiro;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.Logical;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class UserController {
    @PostMapping("/doLogin")
    public String doLogin(String username, String password, Model model) {
        UsernamePasswordToken token =
                new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            model.addAttribute("error", "User name or password input error!");
            return "login";
        }
        return "redirect:/index";
    }
    @RequiresRoles("admin")
    @GetMapping("/admin")
    public String admin() {
        return "admin";
    }
    @RequiresRoles(value = {"admin","user"},logical = Logical.OR)
    @GetMapping("/user")
    public String user() {
        return "user";
    }
}

Code interpretation:

In the doLogin method, first construct a UsernamePasswordToken instance, then obtain a Subject object and call the login method in the object to perform the login operation. During the execution of the login operation, when an exception is thrown, it indicates that the login fails, and returns to the login view with an error message; When login is successful, redirect to "/ index".

Next, expose two interfaces "/ Admin" and "/ user". For the "/ Admin" interface, you need to have the admin role to access it; For the "/ user" interface, you can access it with either admin role or user role.

2.4 WebMvcConfig.class

For other interfaces that can be accessed without roles, you can directly configure them in WebMvc. The code is as follows:

WebMvcConfig.class

package org.sang.shiro;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer{
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.addViewController("/index").setViewName("index");
        registry.addViewController("/unauthorized").setViewName("unauthorized");
    }
}

2.5 ExceptionController.class

Next, create a global exception handler to handle global exceptions. This case mainly deals with authorization exceptions. The code is as follows:

package org.sang.shiro;

import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;

@ControllerAdvice
public class ExceptionController {
    @ExceptionHandler(AuthorizationException.class)
    public ModelAndView error(AuthorizationException e) {
        ModelAndView mv = new ModelAndView("unauthorized");
        mv.addObject("error", e.getMessage());
        return mv;
    }
}

When the user accesses an unauthorized resource, it jumps to the unauthorized view and carries an error message.

2.6 creating HTML pages

After the configuration is completed, five HTML pages are created in the resources/templates directory for testing.

(1) index.html, the code is as follows:

<!DOCTYPE html>
<html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h3>Hello, <shiro:principal/></h3>
<h3><a href="/logout">Logout login</a></h3>
<h3><a shiro:hasRole="admin" href="/admin">Administrator page</a></h3>
<h3><a shiro:hasAnyRoles="admin,user" href="/user">Ordinary user page</a></h3>
</body>
</html>

index.html is the home page after successful login. First, the user name of the current login user is displayed, and then a "log off login" link is displayed. If the current login user has the role of "admin", a hyperlink of "administrator page" is displayed;

If the user has the role of "admin" or "user", a hyperlink of "normal user page" will be displayed.

Note that the namespace imported here is xmlns:shiro=http://www.pollix.at/thymeleaf/shiro , which is inconsistent with the Shiro namespace imported in JSP.

(2) login.html, the code is as follows:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <form action="/doLogin" method="post">
        <input type="text" name="username"><br>
        <input type="password" name="password"><br>
        <div th:text="${error}"></div>
        <input type="submit" value="Sign in">
    </form>
</div>
</body>
</html>

login.html is a common login page, which displays the login failure information through a div when the login fails.

(3) user.html, the code is as follows:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Ordinary user page</h1>
</body>
</html>

user.html is an ordinary user information display page.

(4) admin.html, the code is as follows:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Administrator page</h1>
</body>
</html>

admin.html is an ordinary administrator information display page.

(5) unauthorized.html, the code is as follows:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div>
    <h3>Unauthorized, illegal access</h3>
    <h3 th:text="${error}"></h3>
</div>
</body>
</html>

unauithorized.html is a display page of authorization failure, which also displays the information of authorization error.

Topics: Java Spring Spring Boot