SpringBoot整合Shiro(超详细,适合新手学习,附有源码)

shrio框架介绍

shiro是什么呢?
Shiro是一个功能强大,简单易用的 Java 安全框架,提供了用户认证、授权、加密、会话管理、缓存等功能。和目前另一款Java安全框架Spring Security相比,Shiro更加轻便,易于上手。

各功能点介绍
在这里插入图片描述

  • Authentication:用户身份认证/登录,即验证用户是不是合法用户
  • Authorization:用户权限验证,即验证用户是否拥有某个角色,是否拥有某个权限
  • Session Manager:会话管理,即用户登录后就存在一个session,在用户注销前,用户的所有信息都会存在与session中
  • Crytography:加密,即密码加密存储到数据库
  • Web Support:Web支持,即易于集成到Web环境
  • Caching:缓存,即用户登录后,其用户信息、角色信息、权限信息会缓存起来,不必每次都去查询数据,提高性能
  • Concurrency:支持多线程的并发验证,即在一个线程中开启另一个线程,能把权限自动传播过去
  • Testing:提供测试支持
  • Run As:允许一个用户假装为另一个用户(如果他们允许的话)的身份进行访问
  • Remember Me:记住我,即一次登录后,下次访问就无需登录

shiro流程介绍

Shiro的三大核心组件分别是:SubjectSecurityManagerRealm。他们直接的关系如下:
在这里插入图片描述

  • Subject:主体,代表了“当前用户”。这个“当前用户”通常指的是系统中定义的用户对象,它表示的是一个抽象的概念。所有Subject都绑定到Security Manager,与Subject的所有交互都会委托给Security Manager。可以认为Subject是一个门面,而Security Manager才是真正的执行者
  • Security Manager:安全管理器,即所有与安全有关的操作都会与Security Manager交互,并且管理者所有Subject,它是Shiro的核心组件。如果了解SpringMVC框架的话,可以将其看成是其中的DispatchServlet
  • Realm:域,Shiro从Realm获取数据(用户、角色、权限),就是说Security Manager如果要验证用户的身份,那么他需要从Realm获取相应的用户信息与请求传入的用户信息进行比对,验证是否合法。因此,在Realm相当于开发中的Dao层,其中通常包含了从数据库获取数据的SQL操作。

shiro内部架构介绍

在这里插入图片描述

  • Subject:主体
  • Security Manager:Shiro框架的核心,管理着所有Subject,且负责进行认证、授权、会话以及缓存的管理
  • Authenticator:身份认证器,负责身份认证
  • Authorizer:授权器,负责控制用户权限验证
  • Session Manager:session管理器,负责管理session的生命周期
  • Session Dao:Dao是数据库访问对象,负责将session保存到缓存数据库中
  • Cache Manager:缓存控制器,管理用户、角色、权限信息的缓存管理
  • Cryptography:加密,负责对密码进行加密/解密
  • Realms:域,可使用自带的域,或自定义域,获取数据库中的用户、角色、授权信息,为Security Manager提供数据支持

前提准备

本文使用的
springboot版本:2.7.9
mysql版本:8.0.15
mybatis-plus版本:3.1.1
shiro版本:1.4.0

数据库表结构及数据:


SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_pers
-- ----------------------------
DROP TABLE IF EXISTS `t_pers`;
CREATE TABLE `t_pers`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(80) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_pers
-- ----------------------------
INSERT INTO `t_pers` VALUES (1, 'user:select');
INSERT INTO `t_pers` VALUES (2, 'user:delete');
INSERT INTO `t_pers` VALUES (3, 'user:update');
INSERT INTO `t_pers` VALUES (4, 'user:insert');
INSERT INTO `t_pers` VALUES (5, 'admin:select');
INSERT INTO `t_pers` VALUES (6, 'admin:insert');
INSERT INTO `t_pers` VALUES (7, 'admin:update');
INSERT INTO `t_pers` VALUES (8, 'admin:delete');

-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(60) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_role
-- ----------------------------
INSERT INTO `t_role` VALUES (1, 'admin');
INSERT INTO `t_role` VALUES (2, 'user');

-- ----------------------------
-- Table structure for t_role_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_role_perms`;
CREATE TABLE `t_role_perms`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `roleid` int NULL DEFAULT NULL,
  `permsid` int NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_role_perms
-- ----------------------------
INSERT INTO `t_role_perms` VALUES (1, 1, 6);
INSERT INTO `t_role_perms` VALUES (2, 1, 7);
INSERT INTO `t_role_perms` VALUES (3, 1, 8);
INSERT INTO `t_role_perms` VALUES (4, 1, 5);
INSERT INTO `t_role_perms` VALUES (5, 2, 3);
INSERT INTO `t_role_perms` VALUES (6, 2, 4);
INSERT INTO `t_role_perms` VALUES (9, 2, 1);
INSERT INTO `t_role_perms` VALUES (10, 2, 2);

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `password` varchar(40) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  `salt` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, 'zhangsan', '9cbcc7faf6f23a57b247610a1fb76b3b', 'sExe#V$X');
INSERT INTO `t_user` VALUES (2, 'lisi', '8f72b3b029adc0541fdbcba30ba25d7e', 'X@i#x^96');

-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `userid` int NULL DEFAULT NULL,
  `roleid` int NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_user_role
-- ----------------------------
INSERT INTO `t_user_role` VALUES (1, 2, 1);
INSERT INTO `t_user_role` VALUES (2, 1, 2);

SET FOREIGN_KEY_CHECKS = 1;

user表用户密码均为:123456

代码已上传至gitee second_dev分支:https://gitee.com/lianaozhe/boot_shiro_demo.git

代码示例

导入依赖

完整pom.xml:

<?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.7.9</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.shiro.test</groupId>
    <artifactId>boot-shiro-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>boot-shiro-demo</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</artifactId>
        </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>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.1.1</version>
        </dependency>

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

        <!-- hutool 工具包,各种封装功能 一应俱全-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.5</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>

        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.yml</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

</project>

添加配置

application.yml配置:

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: root
server:
  port: 8080

mybatis-plus:
  mapper-locations: classpath*:com/shiro/test/mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  #    map-underscore-to-camel-case: true
  type-aliases-package: com.shiro.test.entity

编写mapper

userMapper类:

package com.shiro.test.mapper;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shiro.test.entity.TUser;
import com.shiro.test.entity.vo.UserVo;

import java.util.List;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author laz
 * @since 2023-03-22
 */
public interface TUserMapper extends BaseMapper<TUser> {


    /**
     * 通过用户名,查询用户角色
     * @param username
     * @return
     */
    List<UserVo> getRoleByUserName(String username);

}

<?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.shiro.test.mapper.TUserMapper">

    <select id="getRoleByUserName" parameterType="String" resultType="com.shiro.test.entity.vo.UserVo">
        SELECT
            u.id uid,
            u.username username,
            r.id rid,
            r.NAME rname
        FROM
            t_user u
            LEFT JOIN t_user_role ur ON u.id = ur.userid
            LEFT JOIN t_role r ON ur.roleid = r.id
        WHERE
            u.username = #{username}
    </select>
</mapper>

userVo:

package com.shiro.test.entity.vo;

import lombok.Data;

/**
 * @Author: laz
 * @CreateTime: 2023-03-20  15:54
 * @Version: 1.0
 */
@Data
public class UserVo {

    private Integer uid;

    private String username;

    private Integer rid;

    private String rname;
}

roleMapper类:

package com.shiro.test.mapper;


import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.shiro.test.entity.TRole;
import com.shiro.test.entity.vo.RoleVo;

import java.util.List;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author laz
 * @since 2023-03-22
 */
public interface TRoleMapper extends BaseMapper<TRole> {

    /**
     * 通过角色id查询权限信息
     * @param id
     * @return
     */
    List<RoleVo> getPerByRoleId(int id);

}

<?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.shiro.test.mapper.TRoleMapper">


    <select id="getPerByRoleId" resultType="com.shiro.test.entity.vo.RoleVo">
         SELECT
            r.`id` rid,
            r.`name` rname,
            p.`id` pid,
            p.`name` pname
        FROM
            t_role r
            LEFT JOIN t_role_perms rp ON r.`id` = rp.`roleid`
            LEFT JOIN t_pers p ON rp.`permsid` = p.`id`
        WHERE
            r.`id` = #{id}
    </select>

</mapper>

roleVo:

package com.shiro.test.entity.vo;

import lombok.Data;

/**
 * @Author: laz
 * @CreateTime: 2023-03-20  16:08
 * @Version: 1.0
 */
@Data
public class RoleVo {

    private Integer rid;

    private String rname;

    private Integer pid;

    private String pname;

}

service层

userservice:

package com.shiro.test.service;


import com.baomidou.mybatisplus.extension.service.IService;
import com.shiro.test.entity.TUser;
import com.shiro.test.entity.vo.UserVo;
import com.shiro.test.result.DealResult;

import java.util.List;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author laz
 * @since 2023-03-22
 */
public interface ITUserService extends IService<TUser> {


    /**
     * 通过用户名查询用户信息
     * @param username
     * @return
     */
    TUser getUserByUserName(String username);


    /**
     * 登录
     * @param tUser
     * @return
     */
    DealResult login(TUser tUser);


    /**
     * 根据用户名称查询角色信息
     * @param username
     * @return
     */
    List<UserVo> getRoleByUserName(String username);

}

userservice实现类:

package com.shiro.test.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shiro.test.entity.TUser;
import com.shiro.test.entity.dto.LoginDTO;
import com.shiro.test.entity.vo.UserVo;
import com.shiro.test.exception.LoginException;
import com.shiro.test.mapper.TUserMapper;
import com.shiro.test.result.DealResult;
import com.shiro.test.service.ITUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
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.Service;

import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author laz
 * @since 2023-03-22
 */
@Service
public class TUserServiceImpl extends ServiceImpl<TUserMapper, TUser> implements ITUserService {

    @Override
    public TUser getUserByUserName(String username) {
        LambdaQueryWrapper<TUser> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(TUser::getUsername,username).last("limit 1");
        return this.baseMapper.selectOne(wrapper);
    }

    @Override
    public DealResult login(TUser tUser) {

        // 获取Subject实例对象,用户实例
        Subject user = SecurityUtils.getSubject();

        // 将用户名和密码封装到UsernamePasswordToken
        UsernamePasswordToken token = new UsernamePasswordToken(tUser.getUsername(), tUser.getPassword());
        LoginDTO loginDTO = new LoginDTO();

        try {
            user.login(token);

            TUser userInfo = (TUser) user.getPrincipals().getPrimaryPrincipal();

            loginDTO.setToken(user.getSession().getId().toString());
            BeanUtil.copyProperties(userInfo,loginDTO);
        } catch (UnknownAccountException e) {
            log.error("账户不存在异常:", e);
            throw new LoginException("账号不存在!", e);
        } catch (IncorrectCredentialsException e) {
            log.error("凭据错误(密码错误)异常:", e);
            throw new LoginException("密码不正确!", e);
        } catch (AuthenticationException e) {
            log.error("身份验证异常:", e);
            throw new LoginException("用户验证失败!", e);
        }

        return DealResult.data(loginDTO);
    }

    @Override
    public List<UserVo> getRoleByUserName(String username) {
        return this.baseMapper.getRoleByUserName(username);
    }
}

roleservice类:

package com.shiro.test.service;


import com.baomidou.mybatisplus.extension.service.IService;
import com.shiro.test.entity.TRole;
import com.shiro.test.entity.vo.RoleVo;

import java.util.List;

/**
 * <p>
 *  服务类
 * </p>
 *
 * @author laz
 * @since 2023-03-22
 */
public interface ITRoleService extends IService<TRole> {

    /**
     * 通过角色id查询权限信息
     * @param id
     * @return
     */
    List<RoleVo> getPerByRoleId(int id);


}

roleService实现类:

package com.shiro.test.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shiro.test.entity.TRole;
import com.shiro.test.entity.vo.RoleVo;
import com.shiro.test.mapper.TRoleMapper;
import com.shiro.test.service.ITRoleService;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author laz
 * @since 2023-03-22
 */
@Service
public class TRoleServiceImpl extends ServiceImpl<TRoleMapper, TRole> implements ITRoleService {

    @Override
    public List<RoleVo> getPerByRoleId(int id) {
        return this.baseMapper.getPerByRoleId(id);
    }
}

shiro配置文件

定义登录异常处理类:

package com.shiro.test.exception;

/**
 * @Author: laz
 * @CreateTime: 2023-03-22  09:49
 * @Version: 1.0
 * 异常处理类
 */
public class LoginException extends RuntimeException {
    public LoginException() {
        super();
    }

    public LoginException(String message) {
        super(message);
    }

    public LoginException(String message, Throwable cause) {
        super(message, cause);
    }
}

定义全局异常处理类:

package com.shiro.test.exception;


import com.shiro.test.result.DealResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

import javax.management.ServiceNotFoundException;
import java.util.List;
import java.util.Set;

/**
 * @Author: laz
 * @CreateTime: 2023-03-22  11:49
 * @Version: 1.0
 * 全局异常处理
 */
@EnableWebMvc
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHand {

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public DealResult handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        String msg = "缺少请求参数!";
        log.error(msg, e);
        return DealResult.fail(msg);
    }

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public DealResult handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        String msg = e.getMessage();
        log.error("参数解析失败:", e);
        return DealResult.fail(msg);
    }

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public DealResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        String msg = handleBindingResult(e.getBindingResult());
        log.error("方法参数无效: ", e);
        return DealResult.fail(msg);
    }

    /**
     * 400 - Bad Request
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(BindException.class)
    public DealResult handleBindException(BindException e) {
        String msg = handleBindingResult(e.getBindingResult());
        log.error("参数绑定失败:", e);
        return DealResult.fail(msg);
    }


    /**
     * 401 - Unauthorized
     */
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(LoginException.class)
    public DealResult handleLoginException(LoginException e) {
        String msg = e.getMessage();
        log.error("登录异常:", e);
        return DealResult.fail(msg);
    }

    /**
     * 403 - Unauthorized
     */
    @ResponseStatus(HttpStatus.FORBIDDEN)
    @ExceptionHandler(UnauthorizedException.class)
    public DealResult handleLoginException(UnauthorizedException e) {
        String msg = "用户无权限!";
        log.error("用户无权限:", e);
        return DealResult.fail(msg);
    }

    /**
     * 405 - Method Not Allowed
     */
    @ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public DealResult handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) {
        String msg = "不支持当前请求方法!";
        log.error(msg, e);
        return DealResult.fail(msg);
    }

    /**
     * 415 - Unsupported Media Type
     */
    @ResponseStatus(HttpStatus.UNSUPPORTED_MEDIA_TYPE)
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public DealResult handleHttpMediaTypeNotSupportedException(Exception e) {
        String msg = "不支持当前媒体类型!";
        log.error(msg, e);
        return DealResult.fail(msg);
    }

    /**
     * 422 - UNPROCESSABLE_ENTITY
     */
    @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY)
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public DealResult handleMaxUploadSizeExceededException(Exception e) {
        String msg = "所上传文件大小超过最大限制,上传失败!";
        log.error(msg, e);
        return DealResult.fail(msg);
    }

    /**
     * 500 - Internal Server Error
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(ServiceNotFoundException.class)
    public DealResult handleServiceException(ServiceNotFoundException e) {
        String msg = "服务内部异常:" + e.getMessage();
        log.error(msg, e);
        return DealResult.fail(msg);
    }

    /**
     * 500 - Internal Server Error
     */
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    @ExceptionHandler(Exception.class)
    public DealResult handleException(Exception e) {
        String msg = "服务内部异常!" + e.getMessage();
        log.error(msg, e);
        return DealResult.fail(msg);
    }

    /**
     * 处理参数绑定异常,并拼接出错的参数异常信息。
     * <p>
     * 创建人:leigq <br>
     * 创建时间:2017年10月16日 下午9:09:22 <br>
     * <p>
     * 修改人: <br>
     * 修改时间: <br>
     * 修改备注: <br>
     * </p>
     *
     * @param result
     */
    private String handleBindingResult(BindingResult result) {
        if (result.hasErrors()) {
            final List<FieldError> fieldErrors = result.getFieldErrors();
            return fieldErrors.iterator().next().getDefaultMessage();
        }
        return null;
    }
}

定义统一返回结果:

package com.shiro.test.result;

import lombok.Data;

import java.io.Serializable;

/**
 * @Author: laz
 * @CreateTime: 2022-12-29  11:00
 * @Version: 1.0
 */
@Data
public class DealResult implements Serializable {

    private static final long serialVersionUID = 1L;

    public int code;

    public Object data;

    public String msg;

    public DealResult() {
    }

    public DealResult(int code, Object data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    public DealResult(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public static DealResult success(String msg){
        return new DealResult(200,msg);
    }

    public static DealResult fail(String msg){
        return new DealResult(400,msg);
    }

    public static DealResult data(Object data){
        return new DealResult(200,data,"查询成功!");
    }
}

配置自定义 Realm, 用于shiro的认证、授权:

package com.shiro.test.realm;

import com.shiro.test.entity.TRole;
import com.shiro.test.entity.TUser;
import com.shiro.test.entity.vo.RoleVo;
import com.shiro.test.entity.vo.UserVo;
import com.shiro.test.service.ITRoleService;
import com.shiro.test.service.ITUserService;
import lombok.extern.slf4j.Slf4j;
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.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;

import java.util.List;

/**
 * @Author: laz
 * @CreateTime: 2023-03-22  09:31
 * @Version: 1.0
 * 自定义 Realm, 主要是重写shiro的认证、授权方法
 */
@Slf4j
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private ITUserService userService;

    @Autowired
    private ITRoleService roleService;

    /**
     * 用于授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.error("进入授权.......");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        /**
         *
         * 身份认证的时候new SimpleAuthenticationInfo的第一个参数可以传入对应或者
         * username,如果传入user,那么这里就可以获取到user对象的信息,否则只能获取到userName
         */
        TUser tUser = (TUser) principalCollection.getPrimaryPrincipal();
        //通过用户名查询用户信息
        List<UserVo> userRoles = userService.getRoleByUserName(tUser.getUsername());
        if (!ObjectUtils.isEmpty(userRoles)){
            for (UserVo userVo:userRoles){
                authorizationInfo.addRole(userVo.getRname());
                //获取角色对应的权限信息
                List<RoleVo> perByRoleId = roleService.getPerByRoleId(userVo.getRid());
                if (!ObjectUtils.isEmpty(perByRoleId)){
                    perByRoleId.forEach(per ->{
                        authorizationInfo.addStringPermission(per.getPname());
                    });
                }
            }
        }
        return authorizationInfo;
    }

    /**
     * 用于认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.error("进入认证.......");
        //获取用户账号
        String principal = (String) authenticationToken.getPrincipal();

        //通过用户名获取到用户信息
        TUser tUser = userService.getUserByUserName(principal);
        if (ObjectUtils.isEmpty(tUser)){
            return null;
        }

        return new SimpleAuthenticationInfo(tUser,tUser.getPassword(), ByteSource.Util.bytes(tUser.getSalt()),getName());
    }
}

添加shiro配置类:

package com.shiro.test.config;

import com.shiro.test.realm.MyRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

/**
 * @Author: laz
 * @CreateTime: 2023-03-22  09:43
 * @Version: 1.0
 * shiro配置类
 */
@Configuration
public class ShiroConfig {

    /**
     *定义shiroFilter工厂,设置对应的过滤条件和跳转条件
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 过滤器链定义映射
        Map<String, String> map = new LinkedHashMap<>();

        /**
         * anon:所有url都可以匿名访问
         * authc:所有url都必须认证通过才可以访问
         *  过滤链定义,从上向下顺序执行,authc 应放在 anon 下面
         */
        //登录接口可以匿名访问
        map.put("/user/login","anon");

        //所有接口必须认证后才可访问
        map.put("/**","authc");

        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了, 位置放在 anon、authc下面
        map.put("logout","logout");

        //未认证跳转次接口
        shiroFilterFactoryBean.setLoginUrl("/user/unAuth");
        //未授权跳转次接口
        shiroFilterFactoryBean.setUnauthorizedUrl("/user/unAuthorized");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;
    }


    /**
     * 由于我们的密码校验逻辑是交给Shiro的SimpleAuthenticationInfo进行处理,所以这里需要配置凭证匹配器
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 散列的次数
        hashedCredentialsMatcher.setHashIterations(1024);
        return hashedCredentialsMatcher;
    }


    /**
     * 将自定义的realm校验方式注入到spring容器中
     * @return
     */
    @Bean
    public MyRealm myRealm() {
        MyRealm myRealm = new MyRealm();
        myRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myRealm;
    }


    /**
     * 权限管理,配置主要是Realm的管理认证
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm());
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }


    /**
     * 自定义sessionManager
     * @return
     */
    @Bean
    public SessionManager sessionManager() {
        MySessionManager mySessionManager = new MySessionManager();
        mySessionManager.setSessionDAO(new EnterpriseCacheSessionDAO());
        return mySessionManager;
    }



    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

添加自定义session管理:

package com.shiro.test.config;

import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

/**
 * 自定义session管理
 * 自定义MySessionManager类继承DefaultWebSessionManager类,重写getSessionId方法
 *
 * 传统结构项目中,shiro从cookie中读取sessionId以此来维持会话,而在前后端分离的项目中,
 * 我们选择在ajax的请求头中传递sessionId,因此需要重写shiro获取sessionId的方式。
 *这里采用ajax请求头Authorization携带sessionId的方式
 * @author :laz
 * @date :2023/3/22 10:21
 */
public class MySessionManager extends DefaultWebSessionManager {

    private static final String AUTHORIZATION = "Authorization";

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        //如果请求头中有 Authorization 则其值为sessionId
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {
            //否则按默认规则从cookie取sessionId
            return super.getSessionId(request, response);
        }
    }
}


controller层接口

package com.shiro.test.controller;


import com.shiro.test.entity.TUser;
import com.shiro.test.result.DealResult;
import com.shiro.test.service.ITUserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;

/**
 * <p>
 *  前端控制器
 * </p>
 *
 * @author laz
 * @since 2023-03-22
 */
@Slf4j
@RestController
@RequestMapping("/user")
public class TUserController {

    @Autowired
    private ITUserService userService;

    @PostMapping("/login")
    public DealResult login(@RequestBody TUser user){
        return userService.login(user);
    }

    @RequestMapping("/unAuth")
    public DealResult unAuth(){
        return DealResult.fail("未登录!");
    }

    @RequestMapping("/unAuthorized")
    public DealResult unauthorized(){
        return DealResult.fail("没有权限");
    }


    @PostMapping("insert")
    public DealResult insert(@RequestBody TUser user){
        return DealResult.success("执行插入方法");
    }
}


测试

认证测试

访问UserController的insert方法:

在这里插入图片描述

由于我们在ShiroConfig配置文件中配置了shiroFilterFactoryBean.setLoginUrl(“/user/unAuth”),此行代码含义是**未登录的请求全部重定向到/user/unAuth这个接口。**这里拦截成功!

先执行/login接口:

在这里插入图片描述
把token放入请求头中再次请求/insert接口:

在这里插入图片描述

这里可以看到,访问成功了。

授权测试

由数据库数据可知,账号zhangsan拥有user角色,拥有user:select,user:insert,user:update,user:delete的权限。

/insert接口添加@RequiresPermissions注解

在这里插入图片描述

重启服务器测试:

在这里插入图片描述

由于在ShiroConfig配置了shiroFilterFactoryBean.setUnauthorizedUrl("/user/unAuthorized");,无权限则跳转到/user/unAuthorized接口,由于zhagnsan没有admin:insert权限,所以请求重定向到/user/unAuthorized

修改接口权限:

在这里插入图片描述

重启服务,再次访问:

在这里插入图片描述

可以发现,访问接口成功!

另外授权还有角色相关的注解 @RequiresRoles,用法和@RequiresPermissions一样,这里就不演示了。

以上就是shiro认证,授权的全部过程了!

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
课程简介:历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统中后端应用权限的管理,其中主要涵盖了六大核心业务模块、十几张数据库表。 其中的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring BootSpring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统中员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其中,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程中,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发中,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值