[Springboot] integrate Shiro to realize login interception, user authentication and request authorization (thymeleaf, mybatis)

Keywords: Java Spring Spring Boot

Shiro address: https://shiro.apache.org/
reference resources: Crazy God says Java Springboot

1, Construction project

1.1 configuration dependency

Select the Springboot project to build, and select the web and thymeleaf templates. Spring boot starter thymeleaf, spring boot starter web and spring boot starter test will be imported automatically.

After initializing the project, import springboot to integrate with Shiro, which depends on Shiro spring.

The dependencies in pom.xml are as follows:

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.7.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

1.2 front page

Get data and page Jump through theamleaf. The project directory is as follows:

1.2.1 homepage index

src/main/resources/templates/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>home page</h1>
<p th:text="${msg}"></p>
<a th:href="@{user/add}">add</a>  |  <a th:href="@{user/update}">update</a>
</body>
</html>

1.2.2 login

src/main/resources/templates/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>Sign in</h1>
<hr>
<p th:text="${msg}" style="color: red"></p>
<form th:action="@{/login}">
  <p>User:<input type="text" name="username"></p>
  <p>password:<input type="password" name="password"></p>
  <p><input type="submit"></p>
</form>
</body>
</html>

1.2.3 others

All other pages are empty, with only one p tag corresponding to the file name.

2, Basic configuration

First, build the most basic configuration code.

2.1 MyController

Control page Jump

src/main/java/com/zqc/springbootshiro/controller/MyController.java

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MyController {

    @RequestMapping({"/","/index"})
    public String toIndex(Model model) {
        model.addAttribute("msg", "hello,Shiro");
        return "index";
    }

    @RequestMapping("/user/add")
    public String add() {
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String update() {
        return "user/update";
    }

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

2.2 UserRealm

src/main/java/com/zqc/springbootshiro/config/UserRealm.java

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class UserRealm extends AuthorizingRealm {

    // to grant authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("Executive authorization");
        return null;
    }

    // authentication
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("Perform certification");
        return null;
    }
}

2.3 ShiroConfig

src/main/java/com/zqc/springbootshiro/config/ShiroConfig.java

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;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    // ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // Set up security manager
        bean.setSecurityManager(defaultWebSecurityManager);
        return bean;
    }

    // DefaultWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // Associate UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    // To create a realm object, you need to customize the class
    @Bean(name = "userRealm")
    public UserRealm userRealm() {
        return new UserRealm();
    }
}

3, Login interception

3.1 modify ShiroConfig

Modify the getShiroFilterFactoryBean() method in src/main/java/com/zqc/springbootshiro/config/ShiroConfig.java to intercept login.

Create a new map to store configuration information through bean.setFilterChainDefinitionMap(filterMap); Make settings.

// ShiroFilterFactoryBean
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
    ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
    // Set up security manager
    bean.setSecurityManager(defaultWebSecurityManager);

    // Add shiro's built-in filter
    /*
        anon: Access without authentication
        authc: Authentication is required to access
        user: You must have the remember me function 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<>();
    // The files in the user directory must be authenticated before they can be accessed.
    filterMap.put("/user/*", "authc");

    bean.setFilterChainDefinitionMap(filterMap);

    // If you do not have permission, set the login request
    bean.setLoginUrl("/toLogin");
    return bean;
}

3.2 testing

Run the project and log in to the default address: http://localhost:8080/

Click add or update to jump to the login page.

4, User authentication

4.1 modifying MyController

Add a login() method in src/main/java/com/zqc/springbootshiro/controller/MyController.java.

@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); // Execute the login method. If there is no exception, it indicates that it is OK
        return "index";
    } catch (UnknownAccountException e) {  // user name does not exist
        model.addAttribute("msg", "User name error");
        return "login";
    } catch (IncorrectCredentialsException e) {  // Password does not exist
        model.addAttribute("msg", "Password error");
        return "login";
    }
}

4.2 modifying UserRealm

Modify the UserRealm method, simulate the database to pass in the user name and password, and judge whether it is correct.
src/main/java/com/zqc/springbootshiro/config/UserRealm.java

// authentication
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    System.out.println("Perform certification");
    // User name, password (normally, take it out of the database)
    String name = "root";
    String password = "123456";

    UsernamePasswordToken userToken = (UsernamePasswordToken) token;

    if (!userToken.getUsername().equals(name)) {
        return null; // Throw an exception UnknownAccountException
    }
    return new SimpleAuthenticationInfo("", password,"");
}

4.3 testing

Run the project and log in to the default address: http://localhost:8080/

Click add or update to jump to the login page. Enter the account (root) and password (123456) just set

You will jump to the add or update page

5, Integrate Mybatis to complete user authentication

5.1 creating database

Create a simple database that contains the following fields and insert some data.

5.2 configuration dependency

The following configuration files are added to pom.xml to configure lombok, druid, mysql, jdbc and mybatis integration respectively.

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.21</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

5.3 configuration file

Create a new src/main/resources/application.yml. It is used to configure database, druid and mybatis. The following contents need to be modified according to their own configuration:

  • Database: user name, password, url
  • druid: None
  • mybatis: type-aliases-package,mapper-locations
spring:
  datasource:
    # 1, mysql database configuration
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/pm?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    # 2, Integrated druid
    druid:
      # 1. Connection pool configuration
      # The number and size of connections initializing the connection pool, minimum and maximum
      initialSize: 5
      minIdle: 5
      maxActive: 20
      # Configure the time to get the connection wait timeout
      maxWait: 60000
      # Configure how often to detect idle connections that need to be closed. The unit is milliseconds
      timeBetweenEvictionRunsMillis: 60000
      # Configure the minimum lifetime of a connection in the pool, in milliseconds
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      # Whether to cache the preparedStatement, that is, PSCache. It is officially recommended to close it under MySQL (personally, it is recommended to open it if you want to use SQL firewall)
      poolPreparedStatements: true

      # 2. Basic monitoring configuration
      web-stat-filter:
        enabled: true
        url-pattern: /*
        #Set which URL s are not counted
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
        session-stat-enable: true
        session-stat-max-count: 100

      # 3.
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        reset-enable: true
        #Set the login name and password of the monitoring page
        login-username: admin
        login-password: 123456
        # Accessible
        allow: 127.0.0.1
        # Inaccessible
        #deny: 192.168.1.100
  # 4, Configure date format
  mvc:
    format:
      date: yyyy-MM-dd

# 3, Integrate mybatis
mybatis:
  # Directory of pojo
  type-aliases-package: com.zqc.springbootshiro.pojo
  # mapper directory
  mapper-locations: classpath:mapper/*.xml

5.4 entity USer

Note: idea requires lombok plug-in.

src/main/java/com/zqc/springbootshiro/pojo/User.java

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}

5.5 Mapper

Write mybatis to query users:

src/main/java/com/zqc/springbootshiro/mapper/UserMapper.java

import com.zqc.springbootshiro.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Repository
@Mapper
public interface UserMapper {
    User queryUserByName(String name);
}

src/main/resources/mapper/UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zqc.springbootshiro.mapper.UserMapper">
    <select id="queryUserByName" resultType="com.zqc.springbootshiro.pojo.User">
        select * from mybatis.user where name=#{name}
    </select>
</mapper>

5.6 Service

UserService interface: src/main/java/com/zqc/springbootshiro/service/UserService.java

import com.zqc.springbootshiro.pojo.User;

public interface UserService {
    User queryUSerByName(String name);
}

UserService interface implementation class: Src / main / Java / COM / zqc / springboothero / service / userserviceimpl.java

import com.zqc.springbootshiro.mapper.UserMapper;
import com.zqc.springbootshiro.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper userMapper;

    @Override
    public User queryUSerByName(String name) {
        return userMapper.queryUserByName(name);
    }
}

5.7 test database

Write test code to test whether the above functions are realized. Query the contents of the database according to the specified name.

src/test/java/com/zqc/springbootshiro/SpringbootShiroApplicationTests.java

@SpringBootTest
class SpringbootShiroApplicationTests {

    @Autowired
    UserServiceImpl userService;

    @Test
    void contextLoads() {
        System.out.println(userService.queryUSerByName("zqc"));
    }
}

If the query is successful, continue with the following operation.

5.8 modifying UserRealm

src/main/java/com/zqc/springbootshiro/config/UserRealm.java

Auto assemble UserServiceImpl implementation class

@Autowired
UserServiceImpl userService;

Modify the doGetAuthenticationInfo() method to obtain the user name and password from the database.

UsernamePasswordToken userToken = (UsernamePasswordToken) token;
// Connect to database
User user = userService.queryUSerByName(userToken.getUsername());
if (user==null) {
    // There is no such person
    return null;// Throw an exception UnknownAccountException
}
// shiro password authentication, encrypted.
return new SimpleAuthenticationInfo("", user.getPwd(),"");

The modified code is as follows:

import com.zqc.springbootshiro.pojo.User;
import com.zqc.springbootshiro.service.UserServiceImpl;
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;

public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserServiceImpl userService;
    // to grant authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("Executive authorization");
        return null;
    }

    // authentication
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
	    System.out.println("Perform certification");
	    UsernamePasswordToken userToken = (UsernamePasswordToken) token;
	
	    // Connect to database
	    User user = userService.queryUSerByName(userToken.getUsername());
	
	    if (user==null) {
	        // There is no such person
	        return null;// Throw an exception UnknownAccountException
	    }
		// shiro password authentication, encrypted.
	    return new SimpleAuthenticationInfo("", user.getPwd(),"");
	}

5.9 testing

Open browser: http://localhost:8080/ Test.

Users in the database can log in.

6, Request authorization

6.1 MyController

Add an unauthorized() method in Src / main / Java / COM / zqc / springboothero / controller / mycontroller. Java.

When there is no permission limit, it displays unauthorized and inaccessible.

@RequestMapping("/noauth")
@ResponseBody
public String unauthorized() {
    return "unaccredited,cannot access";
}

6.2 modify ShiroConfig

Set the permissions of the add page.

When logging in, authentication doGetAuthenticationInfo() will be executed; When accessing a page requiring permission, authorization doGetAuthorizationInfo() will be executed, but no user is authorized at this time, so no user can access the add page.

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;

@Configuration
public class ShiroConfig {

    // ShiroFilterFactoryBean
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // Set up security manager
        bean.setSecurityManager(defaultWebSecurityManager);

        // Add shiro's built-in filter
        /*
            anon: Access without authentication
            authc: Authentication is required to access
            user: You must have the remember me function 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<>();
        filterMap.put("/user/add", "perms[user:add]");
        filterMap.put("/user/update", "perms[user:update]");

        filterMap.put("/user/*", "authc");
        bean.setFilterChainDefinitionMap(filterMap);

        // If you do not have permission, set the login request
        bean.setLoginUrl("/toLogin");

        // Unauthorized page
        bean.setUnauthorizedUrl("/noauth");
        return bean;
    }

    // DefaultWebSecurityManager
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // Associate UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

    // To create a realm object, you need to customize the class
    @Bean(name = "userRealm")
    public UserRealm userRealm() {
        return new UserRealm();
    }
}

visit http://localhost:8080/ , visit the add page after logging in:

6.3 modifying database and entity classes

Add the perms field (varchar(100)) to set the permissions of each user. Later, the permissions of users in the database are authorized by querying their permissions.

Data in database:

At the same time, modify the User class and add the perms attribute.

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
    private String perms;
}

6.4 modifying UserRealm

Modify the doGetAuthenticationInfo() method:

Before modification: return new SimpleAuthenticationInfo("", user.getPwd(), "");
After modification: return new SimpleAuthenticationInfo(user, user.getPwd(), "");.

During authentication, the user object is stored in it and later taken out for use.

Modify the doGetAuthorizationInfo() method:

SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// Get the object of the currently logged in user
Subject subject = SecurityUtils.getSubject();
// Remove the user stored above
User currentUser = (User) subject.getPrincipal(); 
// Set the permissions of the current user
info.addStringPermission(currentUser.getPerms());  

The modified code is as follows:

import com.zqc.springbootshiro.pojo.User;
import com.zqc.springbootshiro.service.UserServiceImpl;
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;

public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserServiceImpl userService;
    // to grant authorization
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("Executive authorization");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // Get the object of the currently logged in user
        Subject subject = SecurityUtils.getSubject();
        // Remove the user stored above
        User currentUser = (User) subject.getPrincipal();
        // Set the permissions of the current user
        info.addStringPermission(currentUser.getPerms());

        return info;
    }

    // authentication
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("Perform certification");
        UsernamePasswordToken userToken = (UsernamePasswordToken) token;

        // Connect to database
        User user = userService.queryUSerByName(userToken.getUsername());

        if (user==null) {
            // There is no such person
            return null;// Throw an exception UnknownAccountException
        }

        // Can be encrypted, MD5 salt value encryption
        // shiro password authentication, encrypted.
        return new SimpleAuthenticationInfo(user, user.getPwd(),"");
    }
}

6.5 testing

Similarly, access http://localhost:8080/ After logging in, visit the add or update page. Log in to the account in the following database.

zqc users can access the add page; root user can access the update page; Other users cannot access any pages.

7, Integrate thymeleaf

7.1 dependency

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

7.2 modifying shiroConfig

Add the following

// Integrate ShiroDialect: used to integrate shiro and thymeleaf
@Bean
public ShiroDialect getShiroDialect() {
    return new ShiroDialect();
}

7.3 modify index.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro" >
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>home page</h1>
<hr>
<!--If the user is authenticated, the login button is not displayed-->
<div shiro:notAuthenticated="">
    <a th:href="@{/toLogin}">Sign in</a>
</div>

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

<!--If the user has add If you have no access rights, the add label-->
<diV shiro:hasPermission="user:add">
    <a th:href="@{user/add}">add</a>
</diV>
<!--If the user has update If you have no access rights, the update label-->
<div shiro:hasPermission="user:update">
    <a th:href="@{user/update}">update</a>
</div>
</body>
</html>

7.4 testing

The corresponding a tag will be displayed according to the permissions of the logged in user.

zqc can view the access tag of add; root can view the access tab of update.

Posted by fireMind on Fri, 22 Oct 2021 03:07:25 -0700