Shiro integrates with SpringBoot to implement login interception, user authentication, user authorization, etc. Practical demo

Must read at the beginning:

Although the shiro framework is not difficult to learn, the underlying encapsulation is very deep and cannot be explained in a blog. This thing will probably require a few more articles. This article is mainly about setting up the project structure of shiro and including the basic functions of shiro. . There are a lot of things to be added in the future. Based on the project built in this blog, if you want to learn shiro for a while, you either understand it or you can master it. You have to write code by hand. I'm not good at typing the keyboard, but my salary is less than 10,000 yuan. I will continue to update it later. After reading it all, we cannot guarantee that you will definitely learn shiro, but you will definitely gain something.
persist in!

1. Development environment

name Version
I understand the idea 2019.3.5 x64
JDK 1.8
MySQL mysql-5.7.31-winx64
SpringBoot 2.0+
Maven apache-maven-3.6.3
shiro 1 .4.0
MyBatis 2.1.0

2. Project construction

1. Create a SpringBoot project and import relevant pom dependencies:

   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.2.RELEASE</version>
        <relativePath/><!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>2.1.3.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <!--连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.16</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

2. Create the application.yml configuration file in resources.
The detailed configuration is as follows:
If your idea is an old version, an error may be reported because the system cannot find the mapper directory of MyBatis. You can create a mapper folder in the resource directory, or you can temporarily turn off the MyBatis-related configuration in the yml configuration file. (The meaning of commenting out is that the # sign is used for comments in the configuration file).

server:
  port: 8080
spring:
  application:
    name: shiro-springboot
  datasource:
    url: jdbc:mysql://localhost:3306/hibernate
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.xa.DruidXADataSource
  thymeleaf:
    cache: false
  mvc:
    static-path-pattern: /templates/user/**
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.zkr.mingyu.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
  level:
    com.example.demo.dao : debug

The method to enable hot deployment in SpringBoot is as follows: see my other blog introduction.
Air Ticket: How to enable hot deployment for SpringBoot projects

3. Create SpringBoot starter Application.class:
Insert image description here
startup class source code:

package com.zkr.mingyu;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }

}

To test whether the project can start normally, access the localhost:8080 port to check whether it runs normally.
Insert image description here

3. Configure shiro configuration class

Current project directory:
Insert image description here

Write configuration classes to integrate with SpringBoot.
The details of the configuration class are as follows:
Described from the bottom up:
If the shiro tag is not used in the page, you do not need to write the Shiro and Thymeleaf integration configuration class.

  1. Custom Realm.
  2. Create the security manager DefaultWebSecuityManager.
  3. CreateShiroFilterFactoryBean
  4. Shiro integrates configuration classes with Thymeleaf. In order for the page to support shiro tags).

The two most important and critical points in the whole article are the customization of Realm and shiro configuration classes. Full text core

1. Customize the Realm class.

(1) Create the MyRealm class and inherit the AuthorizingRealm class, which must be inherited.
(2) Implement the doGetAuthorizationInfo and doGetAuthenticationInfo methods.
To realize login, doGetAuthenticationInfo is the core, and the account password is verified in the doGetAuthenticationInfo() method.
The authorization operation is performed in the doGetAuthorizationInfo() method, such as page resource restrictions, which resources the user can access, which operations can be performed, etc. All are configured in this method.

package com.zkr.mingyu.shiro;

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;

import java.io.Serializable;

public class MyRealm extends AuthorizingRealm implements Serializable {

    /**
     * 授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行授权方法: doGetAuthorizationInfo");
        return null;
    }

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行认证方法: doGetAuthenticationInfo");
        return null;
    }
}

2. Create a shiro configuration class.

This configuration class is very important. Read it carefully and link it link by link. Later password encryption (MD5 + salt + hashing) is configured in this class, as well as integration configuration with Thymeleaf, etc.

package com.zkr.mingyu.config;

import com.zkr.mingyu.shiro.MyRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**标注 @Configuration 注解,
 * 标注这是一个配置类,
 * 让项目启动时加载该配置类。
 */
@Configuration
public class ShiroConfig {

    /**
     * 创建ShiroFilterFactory
     * 设置权限规则 需要注入securityManage
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }

    /**
     * 创建安全管理器,
     * 并为 securityManager 注入自定义的 Realm 类
     * @param realm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager getSecurityManager(MyRealm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    /**
     * 配置自定义 Realm 类
     * @Bean 将 MyRealm 注入到 Spring 容器当中
     * @return
     */
    @Bean
    public MyRealm getMyRealm(){
        return new MyRealm();
    }

}

4. Create pages, Controllers, Services, etc.

1. Create a templates folder in the resource directory to store html pages. And create index and login pages.
index:
Site homepage
login:
log in page

2. Controller, Service, dao, etc.
Current directory structure:
Insert image description here
Controller:

package com.zkr.mingyu.controller;

import com.zkr.mingyu.entity.User;
import com.zkr.mingyu.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;

@Controller
public class UserController {

    @Resource
    private UserService userService;

    @RequestMapping("/index")
    public String login(User user, Model model) {
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());

        try {
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) {
            model.addAttribute("msg", "用户名错误!");
            return "login";
        } catch (IncorrectCredentialsException e) {
            model.addAttribute("msg", "密码错误!");
            return "login";
        }

    }

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

    @RequestMapping("/loginOut")
    public String loginOut() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "login";
    }

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

}

Service:

package com.zkr.mingyu.service;

import com.zkr.mingyu.entity.User;
import org.apache.ibatis.annotations.Param;

public interface UserService {

    /**
     * 根据用户名查找用户
     * @param userName
     * @return
     */
    User findByUserName(@Param("username") String userName);

}

ServiceImpl:

package com.zkr.mingyu.service;

import com.zkr.mingyu.dao.UserMapper;
import com.zkr.mingyu.entity.User;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Service
@Transactional //开启事务
public class UserServiceImpl implements UserService {

    @Resource
    private UserMapper userMapper;

    @Override
    public User findByUserName(String userName) {
        return userMapper.findByUserName(userName);
    }
}

Dao:

package com.zkr.mingyu.dao;

import com.zkr.mingyu.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper {

    /**
     * 根据用户名查找用户
     * @param userName
     * @return
     */
    User findByUserName(@Param("username") String userName);

}

entity:

package com.zkr.mingyu.entity;

import java.io.Serializable;

public class User implements Serializable {

    /**
     * id
     */
    private Integer id;
    /**
     * 账号
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 权限
     */
    private String auth;
    /**
     * 随机盐
     */
    private String salt;

    public User() {
    }

    public User(Integer id, String username, String password, String auth, String salt) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.auth = auth;
        this.salt = salt;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getAuth() {
        return auth;
    }

    public void setAuth(String auth) {
        this.auth = auth;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", auth='" + auth + '\'' +
                ", salt='" + salt + '\'' +
                '}';
    }
    
}

Mapper:

<?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.zkr.mingyu.dao.UserMapper">
    <select id="findByUserName" parameterType="String" resultType="user">
        select id, username, password from user where username = #{username}
    </select>
</mapper>

5. Modify the customized MyRealm

The doGetAuthenticationInfo() method in the customized MyRealm class mainly configures user authentication.
The doGetAuthorizationInfo() method mainly configures user permission configuration. To put it bluntly, it means what role and permissions are assigned to this user. And whether certain resources can be accessed, and if so, what operations the user can perform on this resource. Operation == CRUD.

package com.zkr.mingyu.shiro;

import com.zkr.mingyu.entity.User;
import com.zkr.mingyu.service.UserService;
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.springframework.beans.factory.annotation.Autowired;

import java.io.Serializable;

public class MyRealm extends AuthorizingRealm implements Serializable {

    @Autowired
    private UserService userService;

    /**
     * 执行授权逻辑
     * 权限要和资源对应
     * 权限声明该用户可以访问系统中哪些资源,对系统中哪些资源进行操作
     * 不同的用户,拥有不同的权限
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行授权方法: doGetAuthorizationInfo");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        User user = (User) principals.getPrimaryPrincipal();
        User byUserName = userService.findByUserName(user.getUsername());
        /**
         * 这个方法中为授权操作
         * 基本常用方法有:
         */

        /**
         * 控制台打印结果:
         * getRoles: null
         * getObjectPermissions: null
         * getStringPermissions: [user:add]
         * getClass: class org.apache.shiro.authz.SimpleAuthorizationInfo
         */

        //获取用户角色
        /* System.out.println("getRoles: " + info.getRoles());
        System.out.println("getObjectPermissions: " + info.getObjectPermissions());
        //获取用户权限
        System.out.println("getStringPermissions: " + info.getStringPermissions());
        System.out.println("getClass: " + info.getClass());*/
        return null;
    }

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行认证方法: doGetAuthenticationInfo");
        String username = (String) token.getPrincipal();
        User user = userService.findByUserName(username);
        if(user == null){
            return null;
        }
        return new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
    }
}

This is another MyRealm configuration I wrote, just take a look and understand it. It will continue to be updated in the future. As mentioned at the beginning of this blog, it is to build the basic framework of the project. I will add a new blog link at the end of the article later. If you have time recently, thank you for sharing this knowledge.
Insert image description here

6. Configure resource access permissions ShiroConfig

The ShiroFilterFactoryBean method mainly configures what permissions are required to access certain resources. And configure the URL address of the default login page. Which page to jump to when accessing certain resources with insufficient permissions is configured here.
DefaultWebSecurityManager is a security manager. When we do password encryption operations (password + salt + MD5 + hashing) later, we must configure a custom password manager in the DefaultWebSecurityManager() method.

package com.zkr.mingyu.config;

import com.zkr.mingyu.shiro.MyRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

/**标注 @Configuration 注解,
 * 标注这是一个配置类,
 * 让项目启动时加载该配置类。
 */
@Configuration
public class ShiroConfig {

    /**
     * 创建ShiroFilterFactory
     * 设置权限规则 需要注入securityManage
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        /**
         * Shiro内置过滤器,可实现权限相关的拦截器
         *      常用的过滤器:
         *      anon: 无需认证(登录) 可以访问
         *      authc: 必须认证才可以访问
         *      user:如果使用rememberMe的功能可以直接访问
         *      perms: 该资源必须得到资源权限才可以访问
         *      role: 该资源必须得到角色权限才可以访问
         */
        Map<String, String> filterMap = new LinkedHashMap<String,String>();
        filterMap.put("/login","anon");
        filterMap.put("/index","anon");
        filterMap.put("/*","authc");
        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 创建安全管理器,
     * 并为 securityManager 注入自定义的 Realm 类
     * @param realm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager getSecurityManager(MyRealm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    /**
     * 配置自定义 Realm 类
     * @Bean 将 MyRealm 注入到 Spring 容器当中
     * @return
     */
    @Bean
    public MyRealm getMyRealm(){
        return new MyRealm();
    }

}

There is not much code. I simply write a demo and improve it step by step later. I will continue to update new articles when I have time. Such as password encryption, resource permission segmentation, sessions, cache, etc.
After writing new functions later, I will put them at the beginning and end of the article and attach links to them. Please give me a like after reading it. Your like is the motivation for me to update! Collect the article, and there will be a link to the new blog at the head of the article later.

Attached is a sentence (to encourage each other):
You can’t see the reflection in boiling water, and you can’t see the truth in anger.

Guess you like

Origin blog.csdn.net/uziuzi669/article/details/108420499