构建spring boot web项目:九、集成shiro

【Shiro】使用org.crazycake依赖的ShiroConfig 整个Shiro专题中,这个部分是最早需要开始看的,主要先了解ShiroConfig都有哪些东西;由于这个项目使用的redis依赖是org.crazycake的shiro-redis,与我后面所用的不同,所以该部分只是简单的梳理了一下。PS:这里并没有使用到jwt,只是单纯的shiro+redis。因为之前的理解,全都作为注释写在了代码上,所以该篇的介绍主要都在代码中。整体来说,该部分内容并不难,如果感兴趣的大佬可以自行往下深入了解,如果多少还有点迷糊的小伙伴,消化消化继续往专题往下看😉。 阅读详情

目录

一、创建权限表,生成基础代码

二、spring boot整合shiro

2.1创建权限管理功能模块(shiro)

2.2引入依赖pom.xml(shiro)

2.3配置shiro

三、管理应用模块(managemet)集成shiro

一、创建权限表,生成基础代码

-- init.sys_menu_authority definition

CREATE TABLE `sys_menu_authority` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(64) DEFAULT NULL COMMENT '权限名称',
  `cascade_code` varchar(256) DEFAULT NULL COMMENT '层级全路径 示例 1#2#3',
  `parent_id` bigint DEFAULT NULL COMMENT '父权限id',
  `level` int NOT NULL COMMENT '级别从1开始',
  `permission_code` varchar(64) DEFAULT NULL COMMENT '权限编码字符串',
  `sort` int DEFAULT NULL COMMENT '排序',
  `button_flag` tinyint DEFAULT NULL COMMENT '是否是按钮 0:菜单 1:按钮',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `delete_flag` tinyint DEFAULT '0' COMMENT ' 是否删除 0否 1是 ',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=312 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='菜单权限表';


-- init.sys_role definition

CREATE TABLE `sys_role` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `role_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '名称',
  `role_type` tinyint DEFAULT NULL COMMENT '1是系统用户 2 普通用户',
  `permission` json DEFAULT NULL COMMENT '角色权限',
  `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '描述',
  `create_by` bigint NOT NULL COMMENT '创建人',
  `update_by` bigint NOT NULL COMMENT '修改人',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '修改时间',
  `delete_flag` tinyint NOT NULL DEFAULT '0' COMMENT ' 是否删除 0否 1是',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='角色表';


-- init.sys_role_authority definition

CREATE TABLE `sys_role_authority` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `role_id` bigint DEFAULT NULL COMMENT '角色id',
  `authority_id` bigint DEFAULT NULL COMMENT '菜单权限id',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `create_by` bigint DEFAULT NULL COMMENT '创建人',
  `update_by` bigint DEFAULT NULL COMMENT '更新人',
  `delete_flag` tinyint DEFAULT '0' COMMENT '是否删除 0否 1是',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `index_role_id` (`role_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=24600 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='角色权限表';


-- init.sys_user_role definition

CREATE TABLE `sys_user_role` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键Id',
  `user_id` bigint unsigned DEFAULT NULL COMMENT '用户主键Id',
  `user_type` tinyint unsigned DEFAULT NULL COMMENT '用户类型:1是系统用户 2 普通用户',
  `dept_id` bigint unsigned NOT NULL COMMENT '部门id',
  `dept_name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '部门名称',
  `role_id` bigint unsigned DEFAULT NULL COMMENT '角色主键Id',
  `create_by` bigint NOT NULL COMMENT '创建人',
  `update_by` bigint NOT NULL COMMENT '更新人',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '更新时间',
  `delete_flag` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否删除 0否 1是 ',
  PRIMARY KEY (`id`) USING BTREE,
  KEY `index_user_id` (`user_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=COMPACT COMMENT='用户角色表';

INSERT INTO init.sys_menu_authority
(id, name, cascade_code, parent_id, `level`, permission_code, sort, button_flag, create_time, delete_flag)
VALUES(1, '系统管理', '1', 0, 1, NULL, 3, 0, '2022-03-03 14:44:17', 0);
INSERT INTO init.sys_menu_authority
(id, name, cascade_code, parent_id, `level`, permission_code, sort, button_flag, create_time, delete_flag)
VALUES(2, '角色管理', '1#2', 1, 2, NULL, 1, 0, '2022-03-03 14:44:17', 0);
INSERT INTO init.sys_menu_authority
(id, name, cascade_code, parent_id, `level`, permission_code, sort, button_flag, create_time, delete_flag)
VALUES(3, '人员管理', '1#3', 1, 2, NULL, 2, 0, '2022-03-03 14:44:17', 0);
INSERT INTO init.sys_menu_authority
(id, name, cascade_code, parent_id, `level`, permission_code, sort, button_flag, create_time, delete_flag)
VALUES(4, '添加、编辑', '1#2#4', 2, 3, NULL, 1, 1, '2022-03-03 14:44:17', 0);
INSERT INTO init.sys_menu_authority
(id, name, cascade_code, parent_id, `level`, permission_code, sort, button_flag, create_time, delete_flag)
VALUES(5, '删除', '1#2#5', 2, 3, NULL, 2, 1, '2022-03-03 14:44:17', 0);
INSERT INTO init.sys_menu_authority
(id, name, cascade_code, parent_id, `level`, permission_code, sort, button_flag, create_time, delete_flag)
VALUES(6, '添加', '1#3#6', 3, 3, NULL, 1, 1, '2022-03-03 14:44:17', 0);
INSERT INTO init.sys_menu_authority
(id, name, cascade_code, parent_id, `level`, permission_code, sort, button_flag, create_time, delete_flag)
VALUES(7, '设置角色', '1#3#7', 3, 3, NULL, 2, 1, '2022-03-03 14:44:17', 0);
INSERT INTO init.sys_menu_authority
(id, name, cascade_code, parent_id, `level`, permission_code, sort, button_flag, create_time, delete_flag)
VALUES(8, '删除', '1#3#8', 3, 3, NULL, 3, 1, '2022-03-03 14:44:17', 0);

INSERT INTO init.sys_role
(id, role_name, role_type, permission, remark, create_by, update_by, create_time, update_time, delete_flag)
VALUES(1, '超级管理员', 0, '[{"code": "REGULAR:WORK_GROUP", "message": "工作组"}, {"code": "REGULAR:OUTSOURCING_USER", "message": "外协人员名单"}, {"code": "REGULAR:PUNCH_RECORD", "message": "打卡记录"}, {"code": "REGULAR:PUNCH_SUMMARY", "message": "打卡汇总"}, {"code": "ADMIN:PUNCH_CONFIG", "message": "打卡设置"}, {"code": "ADMIN:ROLE_MANAGE", "message": "角色管理"}, {"code": "ADMIN:USER_MANAGE", "message": "用户管理"}]', '超级管理员', 0, 0, '2021-12-31 00:00:00', '2021-12-31 00:00:00', 0);

INSERT INTO init.sys_role_authority
(id, role_id, authority_id, update_time, create_time, create_by, update_by, delete_flag)
VALUES(24569, 1, 1, '2022-03-03 15:29:42', '2022-03-03 15:29:38', 0, 0, 0);
INSERT INTO init.sys_role_authority
(id, role_id, authority_id, update_time, create_time, create_by, update_by, delete_flag)
VALUES(24570, 1, 2, '2022-03-03 15:29:42', '2022-03-03 15:29:38', 0, 0, 0);
INSERT INTO init.sys_role_authority
(id, role_id, authority_id, update_time, create_time, create_by, update_by, delete_flag)
VALUES(24571, 1, 3, '2022-03-03 15:29:42', '2022-03-03 15:29:38', 0, 0, 0);
INSERT INTO init.sys_role_authority
(id, role_id, authority_id, update_time, create_time, create_by, update_by, delete_flag)
VALUES(24572, 1, 4, '2022-03-03 15:29:42', '2022-03-03 15:29:38', 0, 0, 0);
INSERT INTO init.sys_role_authority
(id, role_id, authority_id, update_time, create_time, create_by, update_by, delete_flag)
VALUES(24573, 1, 5, '2022-03-03 15:29:42', '2022-03-03 15:29:38', 0, 0, 0);
INSERT INTO init.sys_role_authority
(id, role_id, authority_id, update_time, create_time, create_by, update_by, delete_flag)
VALUES(24574, 1, 6, '2022-03-03 15:29:42', '2022-03-03 15:29:38', 0, 0, 0);
INSERT INTO init.sys_role_authority
(id, role_id, authority_id, update_time, create_time, create_by, update_by, delete_flag)
VALUES(24575, 1, 7, '2022-03-03 15:29:42', '2022-03-03 15:29:38', 0, 0, 0);
INSERT INTO init.sys_role_authority
(id, role_id, authority_id, update_time, create_time, create_by, update_by, delete_flag)
VALUES(24576, 1, 8, '2022-03-03 15:29:42', '2022-03-03 15:29:38', 0, 0, 0);

INSERT INTO init.sys_user_role
(id, user_id, user_type, dept_id, dept_name, role_id, create_by, update_by, create_time, update_time, delete_flag)
VALUES(1, 1, 0, 1, '中华人民共和国', 1, 1, 1, '2021-12-31 00:00:00', '2021-12-31 00:00:00', 0);

生成代码

二、spring boot整合shiro

2.1创建权限管理功能模块(shiro)

2.2引入依赖pom.xml(shiro)

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.lyj.initMode</groupId>
        <artifactId>initMode-function</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>initMode-function-shiro</artifactId>

    <description>权限管理功能模块</description>

    <dependencies>
        <dependency>
            <groupId>com.lyj.initMode</groupId>
            <artifactId>initMode-function-mysql</artifactId>
        </dependency>
        <dependency>
            <groupId>com.lyj.initMode</groupId>
            <artifactId>initMode-function-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.lyj.initMode</groupId>
            <artifactId>initMode-common-base</artifactId>
        </dependency>

        <!--shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
        </dependency>
        <!--jjwt工具类 生成、解析和校验Json Web Token-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
        </dependency>
    </dependencies>

</project>

pom.xml(initMode)


        <!--shiro 安全认证框架-->
        <shiro.verison>1.6.0</shiro.verison>
        <!--jjwt工具类 生成、解析和校验Json Web Token-->
        <jjwt.version>0.11.2</jjwt.version>


            <dependency>
                <groupId>com.lyj.initMode</groupId>
                <artifactId>initMode-function-shiro</artifactId>
                <version>${project.version}</version>
            </dependency>

            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>${shiro.verison}</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-jackson</artifactId>
                <version>${jjwt.version}</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-api</artifactId>
                <version>${jjwt.version}</version>
            </dependency>
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt-impl</artifactId>
                <version>${jjwt.version}</version>
            </dependency>

pom.xml(function)

<module>shiro</module>

2.3配置shiro

LoginTypeEnum.java(base)
package com.lyj.common.base.enums;

import lombok.Getter;

/**
 * 登录方式枚举
 */
@Getter
public enum LoginTypeEnum {

    /**
     * 登录方式
     */
    PC_LOGIN("PC", "PC端登录"),
    APP_LOGIN("APP", "APP/小程序端登录");

    /**
     * 登录方式码
     */
    private final String code;
    /**
     * 登录方式描述
     */
    private final String message;

    LoginTypeEnum(String code, String message) {
        this.code = code;
        this.message = message;
    }

    public static LoginTypeEnum getByCode(String code){
        for (LoginTypeEnum type: LoginTypeEnum.values()){
            if(type.getCode().equals(code)){
                return type;
            }
        }
        return null;
    }
}
AdminFlagEnum.java(base)
package com.lyj.service.enums;

import lombok.Getter;

/**
 * 管理员枚举
 */
@Getter
public enum AdminFlagEnum {

    /**
     * 管理员标记
     */
    NO(0, "非管理员"),
    YES(1, "管理员");

    private final Integer code;
    private final String message;

    AdminFlagEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}
SecurityConstant.java(base)
package com.lyj.service.constant;

/**
 * Shiro常量池
 */
public class SecurityConstant {

    private SecurityConstant() {
    }

    /**
     * anon字符串
     */
    public static final String ANON_TAG = "anon";
    /**
     * logout字符串
     */
    public static final String LOGOUT_TAG = "logout";
    /**
     * token字符串
     */
    public static final String TOKEN_TAG = "token";
    /**
     * token请求头字段码
     */
    public static final String HEADER_KEY_AUTH_TOKEN = "Authorization";
    /**
     * 用户认证信息 key
     */
    public static final String USER_AUTH_INFO_KEY = "USER_AUTH_INFO_KEY";
    /**
     * 用户ID key
     */
    public static final String USER_ID_KEY = "USER_ID_KEY";
    /**
     * 用户账号 key
     */
    public static final String USER_ACCOUNT_KEY = "USER_ACCOUNT_KEY";
    /**
     * 用户名 key
     */
    public static final String USER_NAME_KEY = "USER_NAME_KEY";
    /**
     * 用户部门ID key
     */
    public static final String USER_DEPT_ID_KEY = "USER_DEPT_ID_KEY";
    /**
     * 用户部门名称 key
     */
    public static final String USER_DEPT_NAME_KEY = "USER_DEPT_NAME_KEY";
    /**
     * 用户权限列表 key
     */
    public static final String USER_PERMISSION_KEY = "USER_PERMISSION_KEY";
}
AuthInfoDTO.java(domain)
package com.lyj.common.domain.sysUser.dto;

import lombok.Data;

import java.io.Serializable;

/**
 * 用户登录认证信息
 */
@Data
public class AuthInfoDTO implements Serializable {
    private static final long serialVersionUID = 3767898741324992335L;

    /**
     * 用户id
     */
    private Long userId;
    /**
     * 用户名
     */
    private String account;
    /**
     * 姓名
     */
    private String name;
    /**
     * 昵称
     */
    private String nickName;
    /**
     * 用户登录时选择的部门id
     */
    private Long departId;
    /**
     * 用户登录时选择的部门名字
     */
    private String departName;

    /**
     * 用户登录时选择的角色
     */
    private Long roleId;
    /**
     * 角色名称
     */
    private String roleName;
    /**
     * 是不是超级管理员 0 不是 1 是
     */
    private Integer adminFlag;
}

  生成权限service(shiro)

SysUserRoleService.java(shiro)
package com.lyj.function.shiro.service.sysUserRole;

import com.baomidou.mybatisplus.extension.service.IService;
import com.lyj.common.domain.sysUserRole.entity.SysUserRole;

import java.util.List;

/**
 * 用户角色表(SysUserRole)表服务接口
 *
 * @author lyj
 * @since 2024-07-04 17:48:24
 */
public interface SysUserRoleService extends IService<SysUserRole> {


    /**
     * 查询用户所有的角色id
     *
     * @param userId 用户id
     * @return 角色id
     */
    List<Long> getRoleListByUser(Long userId);

}

SysUserRoleServiceImpl.java(shiro)
package com.lyj.function.shiro.service.sysUserRole.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lyj.function.mysql.dao.sysUserRole.SysUserRoleMapper;
import com.lyj.common.domain.sysUserRole.entity.SysUserRole;
import com.lyj.function.shiro.service.sysUserRole.SysUserRoleService;
import org.springframework.stereotype.Service;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.stream.Collectors;

/**
 * 用户角色表(SysUserRole)表服务实现类
 *
 * @author lyj
 * @since 2024-07-04 17:48:24
 */
@Service
@Slf4j
@AllArgsConstructor
public class SysUserRoleServiceImpl extends ServiceImpl<SysUserRoleMapper, SysUserRole> implements SysUserRoleService {
    private SysUserRoleMapper sysUserRoleMapper;

    @Override
    public List<Long> getRoleListByUser(Long userId) {
        List<SysUserRole> sysUserRoleList = lambdaQuery().eq(SysUserRole::getUserId, userId).list();
        return sysUserRoleList.stream().map(SysUserRole::getRoleId).collect(Collectors.toList());
    }
}

 

SysRoleAuthorityService.java(shiro)
package com.lyj.function.shiro.service.sysRoleAuthority;

import com.baomidou.mybatisplus.extension.service.IService;
import com.lyj.common.domain.sysMenuAuthority.entity.SysMenuAuthority;
import com.lyj.common.domain.sysRoleAuthority.entity.SysRoleAuthority;

import java.util.List;

/**
 * 角色权限表(SysRoleAuthority)表服务接口
 *
 * @author lyj
 * @since 2024-07-04 17:48:24
 */
public interface SysRoleAuthorityService extends IService<SysRoleAuthority> {


    /**
     * 根据roleId获取权限列表,如果不传则获取全部
     *
     * @param roleId 角色id
     * @return List<SysMenuAuthorityVo>
     */
    List<SysMenuAuthority> authorityListById(List<Long> roleId);

}

 

SysRoleAuthorityServiceImpl.java(shiro)
package com.lyj.service.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.lyj.service.entity.SysMenuAuthority;
import com.lyj.service.entity.SysRoleAuthority;
import com.lyj.service.vo.SysMenuAuthorityVO;

import java.util.List;

/**
 * 角色权限表(SysRoleAuthority)表服务接口
 *
 * @author makejava
 * @since 2024-06-26 18:38:41
 */
public interface SysRoleAuthorityService extends IService<SysRoleAuthority> {

    /**
     * 根据roleId获取权限列表,如果不传则获取全部
     *
     * @param roleId 角色id
     * @return List<SysMenuAuthorityVo>
     */
    List<SysMenuAuthorityVO> authorityTreeById(Long roleId);

    /**
     * 根据roleIdList获取权限列表,如果不传则获取全部
     *
     * @param roleIdList 角色id
     * @return List<SysMenuAuthorityVo>
     */
    List<SysMenuAuthorityVO> authorityTreeByRoleList(List<Long> roleIdList);

    /**
     * 根据roleId获取权限列表,如果不传则获取全部
     *
     * @param roleId 角色id
     * @return List<SysMenuAuthorityVo>
     */
    List<SysMenuAuthority> authorityListById(Long roleId);

    /**
     * 根据roleId获取权限列表,如果不传则获取全部
     *
     * @param roleId 角色id
     * @return List<SysMenuAuthorityVo>
     */
    List<SysMenuAuthority> authorityListById(List<Long> roleId);

    /**
     * 更新角色权限
     *
     * @param roleId 角色id
     * @return List<SysMenuAuthorityVo>
     */
    void update(Long roleId, List<Long> authorityIdList);
}

ShiroProperties.java(shiro)
package com.lyj.function.shiro.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

import java.util.Collections;
import java.util.List;

/**
 * Shiro属性配置类
 * @author faminefree
 */
@Data
@ConfigurationProperties("shiro")
public class ShiroProperties {

    /**
     * 登录接口地址
     */
    private String loginUrl = "/api/v1/security/login";
    /**
     * 登出接口地址
     */
    private String logoutUrl = "/api/v1/security/logout";
    /**
     * 注册接口地址
     */
    private String registerUrl = "/api/v1/security/register";

    private String feignUrl = "/api/v1/punch_summary_month/summary";

    /**
     * 登录成功重定向地址
     */
    private String successUrl = "/";
    /**
     * 未授权失败地址
     */
    private String unauthorizedUrl = "/unauthorized";
    /**
     * session默认失效时间(单位秒)
     */
    private long globalSessionTimeout = 86400;
    /**
     * 授权后方可访问资源列表
     */
    private List<String> authcResources = Collections.singletonList("/**");
    /**
     * 未授权即可访问资源列表
     */
    private List<String> anonResources = Collections.emptyList();
    /**
     * token默认失效时间(单位秒)
     */
    private long globalTokenTimeout = 604800;
    /**
     * token默认盐
     */
    private String globalTokenSalt = "b0s16nqqwq19534xucgd3n0wbsz6w0o1";
    /**
     * Opms地址
     */
    private String opmsUrl = "http://10.10.48.31:30080";
    /**
     * Opms调用密钥
     */
    private String opmsSecurityKey = "b9a798e61eeadf2d98fb2b3fd19c6971";
    /**
     * Opms应用ID
     */
    private String opmsAppId = "HN202108271706091431181229691510786";
    /**
     * Opms链接-获取组织树
     */
    private String opmsUrlDeptTree = "/api/v1/find/org/structure";
    /**
     * Opms链接-获取组织列表
     */
    private String opmsUrlDeptList = "/api/v1/find/org/organize/2";
    /**
     * Opms链接-用户详情
     */
    private String opmsUrlUserDetail = "/api/v1/system/user/userEcho/{userId}";
    /**
     * Opms链接-根据oa账号获取用户详情
     */
    private String opmsUrlUserDetailByOa = "/api/v1/system/user/findUserByOa/{oaAccount}";
    /**
     * Opms连接-根据部门ID获取用户列表
     */
    private String opmsUrlUserListByDept = "/api/v1/user/info/dept/{deptId}";
    /**
     * Opms链接-用户登录接口
     */
    private String opmsUrlUserLogin = "/api/v2/user/login";
}

 

StatelessAuthToken.java(shiro)
package com.lyj.function.shiro.authc;

import lombok.Getter;
import lombok.Setter;
import org.apache.shiro.authc.AuthenticationToken;

/**
 *身份验证实体类 用于收集用户在过滤器TokenFilter中提交的token信息
 */
public class StatelessAuthToken implements AuthenticationToken {

    private static final long serialVersionUID = -2827334463469969309L;

    @Getter
    @Setter
    private String token;

    @Override
    public Object getPrincipal() {
        return this.token;
    }

    @Override
    public Object getCredentials() {
        return this.token;
    }

}

 TokenRealm.java(shiro)

package com.lyj.service.realm;

import com.lyj.service.authc.StatelessAuthToken;
import com.lyj.service.constant.SecurityConstant;
import com.lyj.service.dto.AuthInfoDTO;
import com.lyj.service.entity.SysMenuAuthority;
import com.lyj.service.enums.AdminFlagEnum;
import com.lyj.service.enums.ErrorCodeEnum;
import com.lyj.service.service.SysMenuAuthorityService;
import com.lyj.service.service.SysRoleAuthorityService;
import com.lyj.service.service.SysUserRoleService;
import com.lyj.service.util.BizExceptionUtil;
import com.lyj.service.util.DateUtil;
import com.lyj.service.util.JwtTokenUtil;
import com.lyj.service.util.RedisUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
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.subject.Subject;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 *Realm:域,Realm 充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,
 * Shiro 会从应用配置的 Realm 中查找用户及其权限信息。
 * 从这个意义上讲,Realm 实质上是一个安全相关的 DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给 Shiro 。
 * 当配置 Shiro时,你必须至少指定一个 Realm ,用于认证和(或)授权。配置多个 Realm 是可以的,但是至少需要一个。
 * Shiro 内置了可以连接大量安全数据源(又名目录)的 Realm,如 LDAP、关系数据库(JDBC)、类似 INI 的文本配置资源以及属性文件等。
 * 如果缺省的 Realm 不能满足需求,你还可以插入代表自定义数据源的自己的 Realm 实现。
 */
@Slf4j
@Component
@AllArgsConstructor
public class TokenRealm extends AuthorizingRealm {

    private final SysRoleAuthorityService sysRoleAuthorityService;
    private final SysMenuAuthorityService sysMenuAuthorityService;
    private final SysUserRoleService userRoleService;

    //权限获取  获取指定身份的权限,并返回相关信息
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String token = (String) principals.getPrimaryPrincipal();
        String account = JwtTokenUtil.getUsernameFromToken(token);
        if (StringUtils.isBlank(account)) {
            log.error("TokenRealm|doGetAuthorizationInfo|error|token={}|message={}", token, "获取用户token失败");
            BizExceptionUtil.bizException(ErrorCodeEnum.LACK_TOKEN);
            return null;
        }

        AuthInfoDTO authInfo = RedisUtil.get(JwtTokenUtil.getAuthInfoKey(account, token), AuthInfoDTO.class);
        if (authInfo == null || StringUtils.isBlank(authInfo.getAccount())) {
            log.error("TokenRealm|doGetAuthorizationInfo|error|token={}|message={}", token, "用户未认证");
            BizExceptionUtil.bizException(ErrorCodeEnum.NO_LOGGED_IN);
            return null;
        }

        List<String> roleList = new ArrayList<>();
        if (this.isAdmin(authInfo)) {
            List<SysMenuAuthority> list = sysMenuAuthorityService.lambdaQuery().list();
            // 管理员添加所有权限
            roleList = list.stream().map(SysMenuAuthority::getCascadeCode).collect(Collectors.toList());
        } else {
            List<Long> roleListByUser = userRoleService.getRoleListByUser(authInfo.getUserId());
            if (roleListByUser.isEmpty()) {
                log.error("TokenRealm|doGetAuthorizationInfo|error|account={}|message={}", account, "当前用户无角色");
                BizExceptionUtil.bizException(ErrorCodeEnum.WITHOUT_AUTHORITY);
                return null;
            }

            List<SysMenuAuthority> sysMenuAuthorities = sysRoleAuthorityService.authorityListById(roleListByUser);
            if (!sysMenuAuthorities.isEmpty()) {
                roleList = sysMenuAuthorities.stream().map(SysMenuAuthority::getCascadeCode).collect(Collectors.toList());
            }
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(roleList);
        return info;
    }

    //身份验证 并返回相关信息
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        String authToken = ((StatelessAuthToken) token).getToken();

        if (StringUtils.isBlank(authToken)) {
            BizExceptionUtil.bizException(ErrorCodeEnum.LACK_TOKEN);
            return null;
        }

        String account = JwtTokenUtil.getUsernameFromToken(authToken);
        if (StringUtils.isBlank(account)) {
            BizExceptionUtil.bizException(ErrorCodeEnum.LACK_TOKEN);
            return null;
        }

        Date expire = JwtTokenUtil.getExpirationFromToken(authToken);

        long nowTime = DateUtil.nowTimeInMillis();
        if (nowTime > expire.getTime()) {
            BizExceptionUtil.bizException(ErrorCodeEnum.AUTHORIZED_TIMEOUT);
            return null;
        }

        AuthInfoDTO authInfo = RedisUtil.get(JwtTokenUtil.getAuthInfoKey(account, authToken), AuthInfoDTO.class);

        if (authInfo == null) {
            BizExceptionUtil.bizException(ErrorCodeEnum.NO_LOGGED_IN);
            return null;
        }

        Subject subject = SecurityUtils.getSubject();
        subject.getSession().setAttribute(SecurityConstant.USER_AUTH_INFO_KEY, authInfo);
        subject.getSession().setAttribute(SecurityConstant.USER_ID_KEY, authInfo.getUserId());
        subject.getSession().setAttribute(SecurityConstant.USER_ACCOUNT_KEY, authInfo.getAccount());
        subject.getSession().setAttribute(SecurityConstant.USER_NAME_KEY, authInfo.getName());
        subject.getSession().setAttribute(SecurityConstant.USER_DEPT_ID_KEY, authInfo.getDepartId());
        subject.getSession().setAttribute(SecurityConstant.USER_DEPT_NAME_KEY, authInfo.getDepartName());

        return new SimpleAuthenticationInfo(authToken, authToken, this.getName());
    }

    //令牌支持(supports方法)判断当前Realm是否支持指定的认证令牌
    //这个方法通常用于Shiro框架在多个Realm的环境中,以决定使用哪一个Realm来处理当前的认证请求。
    @Override
    public boolean supports(AuthenticationToken token) {
        //仅支持StatelessAuthToken类型的Token
        return token instanceof StatelessAuthToken;
    }

    private boolean isAdmin(AuthInfoDTO authInfo) {
        return authInfo != null && authInfo.getAdminFlag() != null && AdminFlagEnum.YES.getCode().compareTo(authInfo.getAdminFlag()) == 0;
    }

}
TokenFilter.java(shiro)
package com.lyj.function.shiro.filter;

import com.alibaba.fastjson.JSON;
import com.lyj.common.base.common.R;
import com.lyj.common.base.enums.ErrorCodeEnum;
import com.lyj.common.base.exceptions.BusinessException;
import com.lyj.function.shiro.authc.StatelessAuthToken;
import com.lyj.function.shiro.util.JwtTokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.MediaType;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;

/**
 * 自定义认证过滤器
 * 不能 filter 交给 spring 创建。
 * shiro 的权限控制是通过 filter 控制的,且 shiro 自己维护了一个 filter chain。
 * spring 也维护了一个 filter chain。
 * denyAllFilter 交给 spring 创建后, denyAllFilter 虽然如愿的加入到了 shiro filter chain 中,
 * 但,同时也加入到了 spring filter chain 中。
 * 具体原因参考:https://www.cnblogs.com/guitu18/p/12163872.html
 */
@Slf4j
public class TokenFilter extends BasicHttpAuthenticationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // 判断用户是否想要登入
        if (this.isLoginAttempt(request, response)) {
            try {
                // 进行Shiro的登录UserRealm
                this.executeLogin(request, response);
            } catch (Exception e) {
                if (e instanceof AuthenticationException) {
                    log.error("TokenFilter|isAccessAllowed|error|message={}", e.getMessage());
                } else {
                    log.error("TokenFilter|isAccessAllowed|error", e);
                }
                if (isBusinessException(e)) {
                    this.responseError(response, (BusinessException) e.getCause());
                } else {
                    this.response401(response);
                }
                return false;
            }
        } else {
            log.warn("TokenFilter|isAccessAllowed|failed|LackToken");
            response401(response);
            return false;
        }
        return true;
    }
    /**
     * 检测是否包含token字段,有就进行Token登录认证授权
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = JwtTokenUtil.getToken(httpServletRequest);
        return StringUtils.isNotBlank(token);
    }
    /**
     * 进行token登录认证授权
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = JwtTokenUtil.getToken(httpServletRequest);
        StatelessAuthToken authToken = new StatelessAuthToken();
        authToken.setToken(token);
        getSubject(request, response).login(authToken);
        return true;
    }

    //isAccessAllowed 返回 false 之后执行的,即访问拒绝的逻辑
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        response401(response);
        return false;
    }

    private boolean isBusinessException(Exception e) {
        return e instanceof AuthenticationException && e.getCause() instanceof BusinessException;
    }

    private void response401(ServletResponse response) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
            httpServletResponse.setCharacterEncoding(StandardCharsets.UTF_8.name());
            PrintWriter writer = response.getWriter();
            writer.print(JSON.toJSONString(R.error(ErrorCodeEnum.WITHOUT_AUTHORITY)));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            log.error("TokenFilter|response401|error", e);
        }
    }

    private void responseError(ServletResponse response, BusinessException se) {
        try {
            HttpServletResponse httpServletResponse = (HttpServletResponse) response;
            httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);
            httpServletResponse.setCharacterEncoding(StandardCharsets.UTF_8.name());
            R error = R.error(se.getCode(), se.getMessage());
            PrintWriter writer = response.getWriter();
            writer.print(JSON.toJSONString(error));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            log.error("TokenFilter|responseError|error", e);
        }
    }

}
ShiroConfig.java(shiro)
package com.lyj.function.shiro.config;

import com.lyj.common.base.constant.CommonConstant;
import com.lyj.common.base.constant.SecurityConstant;
import com.lyj.function.shiro.filter.TokenFilter;
import com.lyj.function.shiro.realm.TokenRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.*;

/**
 * 认证系统shiro配置类
 */
@Configuration
@EnableConfigurationProperties(ShiroProperties.class)
public class ShiroConfig {

    private List<String> defaultAnonResources = new ArrayList<>();

    private final ShiroProperties properties;

    public ShiroConfig(ShiroProperties properties) {
        this.properties = properties;
    }

    /**
     * DefaultSessionManager: JavaSE环境
     * ServletContainerSessionManager: Web环境,直接使用servlet容器会话
     * DefaultWebSessionManager:用于Web环境的实现,可以替第二个,自己维护着会话,直接废弃了Servlet容器的会话管理
     *
     * 单机环境,session交给shiro管理
     */
    @Bean
    //条件注解 在没有组件SpringSessionRedisConnectionFactory时构件该bean
    @ConditionalOnMissingClass("org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory")
    public DefaultWebSessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setSessionValidationInterval(properties.getGlobalSessionTimeout() * 1000);
        sessionManager.setGlobalSessionTimeout(properties.getGlobalSessionTimeout() * 1000);

        return sessionManager;
    }

    /**
     * 集群环境,session交给spring-session管理
     */
    @Bean
    //条件注解 在有组件SpringSessionRedisConnectionFactory时构件该bean
    @ConditionalOnClass(name = "org.springframework.session.data.redis.config.annotation.SpringSessionRedisConnectionFactory")
    public ServletContainerSessionManager servletContainerSessionManager() {
        return new ServletContainerSessionManager();
    }

    /**
     * 自定义DefaultWebSecurityManager 否则会报bean冲突
     * @param tokenRealm
     * @param sessionManager
     * @return
     */
    @Bean
    public SecurityManager securityManager(TokenRealm tokenRealm, SessionManager sessionManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设定Realm安全组件
        securityManager.setRealm(tokenRealm);
        //Session管理器
        securityManager.setSessionManager(sessionManager);

        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        // Shiro的核心安全接口,这个属性是必须的
        bean.setSecurityManager(securityManager);
        //登录接口地址
        bean.setLoginUrl(properties.getLoginUrl());
        //登录成功重定向地址
        bean.setSuccessUrl(properties.getSuccessUrl());
        //未授权失败地址
        bean.setUnauthorizedUrl(properties.getUnauthorizedUrl());
        //自定义名为“token(SecurityConstant.TOKEN_TAG)”的过滤器
        Map<String, Filter> filterMap = new HashMap<>(CommonConstant.DEFAULT_HASH_MAP_SIZE);
        filterMap.put(SecurityConstant.TOKEN_TAG, new TokenFilter());
        bean.setFilters(filterMap);

        //当运行一个Web应用程序时,Shiro将会创建一些有用的默认Filter实例,并自动地在[main]项中将它们置为可用自动地可用的默认的Filter实例是被DefaultFilter枚举类定义的,枚举的名称字段就是可供配置的名称
        /**
         * anon---------------org.apache.shiro.web.filter.authc.AnonymousFilter 没有参数,表示可以匿名使用。
         * authc--------------org.apache.shiro.web.filter.authc.FormAuthenticationFilter 表示需要认证(登录)才能使用,没有参数
         * authcBasic---------org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 没有参数表示httpBasic认证
         * logout-------------org.apache.shiro.web.filter.authc.LogoutFilter
         * noSessionCreation--org.apache.shiro.web.filter.session.NoSessionCreationFilter
         * perms--------------org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter 参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
         * port---------------org.apache.shiro.web.filter.authz.PortFilter port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。
         * rest---------------org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter 根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
         * roles--------------org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
         * ssl----------------org.apache.shiro.web.filter.authz.SslFilter 没有参数,表示安全的url请求,协议为https
         * user---------------org.apache.shiro.web.filter.authz.UserFilter 没有参数表示必须存在用户,当登入操作时不做检查
         */
        /**
         * 通常可将这些过滤器分为两组
         * anon,authc,authcBasic,user是第一组认证过滤器
         * perms,port,rest,roles,ssl是第二组授权过滤器
         * 注意user和authc不同:当应用开启了rememberMe时,用户下次访问时可以是一个user,但绝不会是authc,因为authc是需要重新认证的
         * user表示用户不一定已通过认证,只要曾被Shiro记住过登录状态的用户就可以正常发起请求,比如rememberMe 说白了,以前的一个用户登录时开启了rememberMe,然后他关闭浏览器,下次再访问时他就是一个user,而不会authc
         *
         *
         * 举几个例子
         *  /admin=authc,roles[admin]      表示用户必需已通过认证,并拥有admin角色才可以正常发起'/admin'请求
         *  /edit=authc,perms[admin:edit]  表示用户必需已通过认证,并拥有admin:edit权限才可以正常发起'/edit'请求
         *  /home=user     表示用户不一定需要已经通过认证,只需要曾经被Shiro记住过登录状态就可以正常发起'/home'请求
         */


        /**
         * 各默认过滤器常用如下(注意URL Pattern里用到的是两颗星,这样才能实现任意层次的全匹配)
         * /admins/**=anon             无参,表示可匿名使用,可以理解为匿名用户或游客
         *  /admins/user/**=authc       无参,表示需认证才能使用
         *  /admins/user/**=authcBasic  无参,表示httpBasic认证
         *  /admins/user/**=ssl         无参,表示安全的URL请求,协议为https
         *  /admins/user/**=perms[user:add:*]  参数可写多个,多参时必须加上引号,且参数之间用逗号分割,如/admins/user/**=perms["user:add:*,user:modify:*"]。当有多个参数时必须每个参数都通过才算通过,相当于isPermitedAll()方法
         *  /admins/user/**=port[8081] 当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString。其中schmal是协议http或https等,serverName是你访问的Host,8081是Port端口,queryString是你访问的URL里的?后面的参数
         *  /admins/user/**=rest[user] 根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等
         *  /admins/user/**=roles[admin]  参数可写多个,多个时必须加上引号,且参数之间用逗号分割,如:/admins/user/**=roles["admin,guest"]。当有多个参数时必须每个参数都通过才算通过,相当于hasAllRoles()方法
         *
         */

        // 定义 Shiro Filter 链
        Map<String, String> filterRuleMap = new LinkedHashMap<>();
        // 登出接口
        filterRuleMap.put(properties.getLogoutUrl(), SecurityConstant.LOGOUT_TAG);
        // 未授权即可访问资源
        List<String> anonResources = properties.getAnonResources();
        anonResources.add(properties.getFeignUrl());
        defaultAnonResources.addAll(anonResources);
        defaultAnonResources.forEach(s -> filterRuleMap.put(s, SecurityConstant.ANON_TAG));
        // 授权后方可访问资源 由TokenFilter过滤
        properties.getAuthcResources().forEach(s -> filterRuleMap.put(s, SecurityConstant.TOKEN_TAG));
        bean.setFilterChainDefinitionMap(filterRuleMap);

        return bean;
    }


    //开启权限注解 @RequiresPermissions({"file:read"} )
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

}

登录服务接口service(shiro)

SysUserLoginDTO.java(domain)
package com.lyj.common.domain.sysLog.dto;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.validation.constraints.NotBlank;
import java.io.Serializable;

/**
 * 用户登录DTO
 */
@Data
@ApiModel("用户登录DTO")
public class SysUserLoginDTO implements Serializable {
    private static final long serialVersionUID = -8501885886769682045L;

    @ApiModelProperty("用户名")
    @NotBlank(message = "账号不能为空")
    private String account;

    @ApiModelProperty("密码")
    @NotBlank(message = "密码不能为空")
    private String password;
}
SysLoginResultVO.java(domain)
package com.lyj.common.domain.sysLog.vo;

import com.lyj.common.domain.sysRole.vo.SysRoleDetailVO;
import com.lyj.common.domain.sysUser.vo.SysUserDetailVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * 登录结果数据
 */
@Data
@ApiModel("登录结果数据")
public class SysLoginResultVO implements Serializable {
    private static final long serialVersionUID = -6228892782691441309L;

    @ApiModelProperty("token")
    private String token;

    @ApiModelProperty("用户信息")
    private SysUserDetailVO userInfo;

    @ApiModelProperty("权限信息")
    private List<SysRoleDetailVO> roleInfo;

    @ApiModelProperty("验证码验证标识")
    private String verifyFlag;

    @ApiModelProperty("手机号列表")
    private List<String> phones;
}
SysUserDetailVO.java(domain)
package com.lyj.common.domain.sysUser.vo;


import com.fasterxml.jackson.annotation.JsonFormat;
import com.lyj.common.domain.sysDept.vo.SysDeptVO;
import com.lyj.common.domain.sysUserRole.vo.SysUserRoleDeptVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 系统用户详情数据
 */
@Data
@ApiModel("系统用户详情数据")
public class SysUserDetailVO implements Serializable {
    private static final long serialVersionUID = 1091144072198993309L;

    /**
     * 主键ID
     */
    @ApiModelProperty("主键ID")
    private Long id;
    /**
     * 所属部门id
     */
    @ApiModelProperty("部门id")
    private String deptId;
    /**
     * 所属部门名称
     */
    @ApiModelProperty("所属部门名称,多个逗号隔开")
    private String deptNames;
    /**
     * 用户名称
     */
    @ApiModelProperty("用户名称")
    private String name;
    /**
     * 用户昵称
     */
    @ApiModelProperty("用户昵称")
    private String nickname;
    /**
     * 员工号
     */
    @ApiModelProperty("员工号")
    private String employeeNumber;
    /**
     * 用户头像地址
     */
    @ApiModelProperty("用户头像地址")
    private String avatarUrl;
    /**
     * 用户邮箱
     */
    @ApiModelProperty("用户邮箱")
    private String email;
    /**
     * 手机号码
     */
    @ApiModelProperty("手机号码")
    private String phone;
    /**
     * 办公手机号
     */
    @ApiModelProperty("办公手机号")
    private String workPhone;
    /**
     * 角色id
     */
    @ApiModelProperty("角色id")
    private String roleId;

    /**
     * 角色id
     */
    @ApiModelProperty("角色id")
    private List<Long> roleIdList;
    /**
     * 创建时间
     */
    @ApiModelProperty("创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    /**
     * 更新时间
     */
    @ApiModelProperty("更新时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    /**
     * 账号状态  0:有效 1:失效
     */
    @ApiModelProperty("账号状态  0:有效 1:失效")
    private Integer status;

    @ApiModelProperty("用户部门列表")
    private List<SysDeptVO> deptList;

    @ApiModelProperty("角色部门信息")
    private List<SysUserRoleDeptVO> roleDeptList;
}
SysRoleDetailVO.java(domain)
package com.lyj.common.domain.sysRole.vo;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.lyj.common.domain.sysMenuAuthority.vo.SysMenuAuthorityVO;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.sql.Timestamp;
import java.util.List;

/**
 * 角色详情数据
 */
@Data
@ApiModel("角色详情数据")
public class SysRoleDetailVO implements Serializable {

    private static final long serialVersionUID = 6382820867770299669L;

    /**
     * 主键Id
     */
    @ApiModelProperty("主键Id")
    private Long id;
    /**
     * 名称
     */
    @ApiModelProperty("名称")
    private String roleName;
    /**
     * 用户类型:0-系统用户;1-OA用户;2-外协用户;3-供应商用户
     */
    @ApiModelProperty("用户类型:0-系统用户;1-OA用户;2-外协用户;3-供应商用户")
    private Integer roleType;

    @ApiModelProperty("权限")
    private List<SysMenuAuthorityVO> permissionList;

    /**
     * 描述
     */
    @ApiModelProperty("描述")
    private String remark;
    /**
     * 创建人
     */
    @ApiModelProperty("创建人")
    private Long createBy;
    /**
     * 修改人
     */
    @ApiModelProperty("修改人")
    private Long updateBy;
    /**
     * 创建时间
     */
    @ApiModelProperty("创建时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Timestamp createTime;
    /**
     * 修改时间
     */
    @ApiModelProperty("修改时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Timestamp updateTime;
}

将management模块中的用户、部门服务类迁移至shiro模块

后门登录配置LoginBackDoorProperties.java(shiro)

package com.lyj.function.shiro.config;

import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 后门登录配置
 */
@Data
@Component
public class LoginBackDoorProperties {

    /**
     * 后门登录配置
     */
    @Value("${backdoor.open-flag:false}")
    private Boolean openFlag = Boolean.FALSE;
    @Value("${backdoor.fake-head}")
    private String fakeHead = "";
    @Value("${backdoor.password}")
    private String unifiedPassword = "";

}
AuthInfoUtil.java(shiro)
package com.lyj.function.shiro.util;


import com.lyj.common.base.constant.SecurityConstant;
import com.lyj.common.domain.sysUser.dto.AuthInfoDTO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

/**
 * 当前登录人信息工具类
 */
@Slf4j
public class AuthInfoUtil {

    private AuthInfoUtil() {
        super();
    }

    private static HttpSession getSession() {
        ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = attr.getRequest();
        return request.getSession();
    }

    public static AuthInfoDTO currentUserInfo() {
        AuthInfoDTO authInfoDTO = new AuthInfoDTO();
        authInfoDTO.setUserId(currentUserId());
        authInfoDTO.setName(currentUserRealName());
        authInfoDTO.setAccount(currentUserAccount());
        authInfoDTO.setDepartId(currentDeptId());
        authInfoDTO.setDepartName(currentDeptName());
        return authInfoDTO;
    }

    public static Long currentUserId() {
        return NumberUtils.toLong(String.valueOf(AuthInfoUtil.getSession().getAttribute(SecurityConstant.USER_ID_KEY)));
    }

    public static String currentUserAccount() {
        return String.valueOf(AuthInfoUtil.getSession().getAttribute(SecurityConstant.USER_ACCOUNT_KEY));
    }

    public static String currentUserRealName() {
        return String.valueOf(AuthInfoUtil.getSession().getAttribute(SecurityConstant.USER_NAME_KEY));
    }

    public static Long currentDeptId() {
        return NumberUtils.toLong(String.valueOf(AuthInfoUtil.getSession().getAttribute(SecurityConstant.USER_DEPT_ID_KEY)));
    }

    public static String currentDeptName() {
        return String.valueOf(AuthInfoUtil.getSession().getAttribute(SecurityConstant.USER_DEPT_NAME_KEY));
    }

    /**
     * 清楚session中保存的信息
     */
    public static void clearSession() {
        try {
            AuthInfoUtil.getSession().removeAttribute(SecurityConstant.USER_AUTH_INFO_KEY);
            AuthInfoUtil.getSession().removeAttribute(SecurityConstant.USER_ID_KEY);
            AuthInfoUtil.getSession().removeAttribute(SecurityConstant.USER_NAME_KEY);
            AuthInfoUtil.getSession().removeAttribute(SecurityConstant.USER_DEPT_ID_KEY);
            AuthInfoUtil.getSession().removeAttribute(SecurityConstant.USER_DEPT_NAME_KEY);
            AuthInfoUtil.getSession().removeAttribute(SecurityConstant.USER_PERMISSION_KEY);
        } catch (Exception e) {
            log.warn("clearSession:null");
        }
    }
}
YesNoEnum.java(base)
package com.lyj.common.base.enums;

public enum YesNoEnum {

    NO(0, "0", "否"),
    YES(1, "1", "是");

    private Integer code;
    private String codeStr;
    private String desc;

    YesNoEnum(Integer code, String codeStr, String desc) {
        this.code = code;
        this.codeStr = codeStr;
        this.desc = desc;
    }

    public Integer getCode() {
        return code;
    }

    public String getCodeStr() {
        return codeStr;
    }

    public String getDesc() {
        return desc;
    }
}
DesensitizationUtil.java(base)
package com.lyj.common.base.util;

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.util.List;


/**
 * 手机号码工具类
 */

public class DesensitizationUtil {
    /**
     * [手机固话] 电话中间隐藏,前面保留3位明文,后面保留4位明文
     *
     * @param num   电话号码
     * @param index 3
     * @param end   4
     * @return
     */
    public static String mobileEncrypt(String num, int index, int end) {
        if (StringUtils.isBlank(num)) {
            return "";
        }
        return StringUtils.left(num, index).concat(StringUtils.removeStart(StringUtils.leftPad(StringUtils.right(num, end), StringUtils.length(num), "*"), "***"));
    }

    /**
     * 校验手机号前后是否一致
     *
     * @param sourcePhoneList 明文手机号列表
     * @param phone           脱敏手机号
     * @param index           3
     * @param end             4
     * @return 匹配到的明文手机号
     */
    public static String checkPhone(List<String> sourcePhoneList, String phone, int index, int end) {
        if (CollectionUtils.isEmpty(sourcePhoneList) || StringUtils.isEmpty(phone)) {
            return "";
        }
        String sourcePhone = "";
        for (String str : sourcePhoneList) {
            if (str.contains(StringUtils.left(phone, index)) && str.contains(StringUtils.right(phone, end))) {
                sourcePhone = str;
                break;
            }
        }
        return sourcePhone;
    }
}
SysLoginService.java(shiro)
package com.lyj.function.shiro.service.sysLog;


import com.lyj.common.domain.sysLog.dto.SysUserLoginDTO;
import com.lyj.common.domain.sysLog.vo.SysLoginResultVO;

import javax.servlet.http.HttpServletRequest;

/**
 * 登录服务接口类
 */
public interface SysLoginService {

    /**
     * PC端用户登录
     *
     * @param loginDTO 登录请求体
     * @return 认证成功的token
     */
    SysLoginResultVO pcLogin(SysUserLoginDTO loginDTO);

    /**
     * 用户登出
     *
     * @param request 用户请求
     * @return true-成功;false-失败
     */
    boolean pcLogout(HttpServletRequest request);
    /**
     * 如果用户是多部门需要选择当前部门信息
     *
     * @param deptId 当前部门
     * @return Boolean
     */
    Boolean saveDept(Long deptId);
}
SysLoginServiceImpl.java(shiro)
package com.lyj.function.shiro.service.sysLog.impl;


import com.lyj.common.base.constant.CommonConstant;
import com.lyj.common.base.enums.ErrorCodeEnum;
import com.lyj.common.base.enums.LoginTypeEnum;
import com.lyj.common.base.enums.YesNoEnum;
import com.lyj.common.base.exceptions.BusinessException;
import com.lyj.common.base.util.BeanUtil;
import com.lyj.common.base.util.BizExceptionUtil;
import com.lyj.common.base.util.DesensitizationUtil;
import com.lyj.common.domain.sysDept.entity.SysDept;
import com.lyj.common.domain.sysLog.dto.SysUserLoginDTO;
import com.lyj.common.domain.sysLog.vo.SysLoginResultVO;
import com.lyj.common.domain.sysUser.dto.AuthInfoDTO;
import com.lyj.common.domain.sysUser.entity.SysUser;
import com.lyj.common.domain.sysUser.vo.SysUserDetailVO;
import com.lyj.common.domain.sysUserDept.entity.SysUserDept;
import com.lyj.function.redis.util.RedisUtil;
import com.lyj.function.shiro.config.LoginBackDoorProperties;
import com.lyj.function.shiro.config.ShiroProperties;
import com.lyj.function.shiro.service.sysDept.SysDeptService;
import com.lyj.function.shiro.service.sysLog.SysLoginService;
import com.lyj.function.shiro.service.sysUser.SysUserService;
import com.lyj.function.shiro.service.sysUserDept.SysUserDeptService;
import com.lyj.function.shiro.service.sysUserRole.SysUserRoleService;
import com.lyj.function.shiro.util.AuthInfoUtil;
import com.lyj.function.shiro.util.JwtTokenUtil;
import com.lyj.function.shiro.util.PasswordUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * 登录服务实现类
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SysLoginServiceImpl implements SysLoginService {


    private final ShiroProperties properties;
    private final LoginBackDoorProperties loginBackDoorProperties;
    private final SysUserService sysUserService;
    private final SysDeptService sysDeptService;
    private final SysUserRoleService userRoleService;
    private final SysUserDeptService sysUserDeptService;


    @Override
    public SysLoginResultVO pcLogin(SysUserLoginDTO loginDTO) {
        return this.login(loginDTO.getAccount(), loginDTO.getPassword(), LoginTypeEnum.PC_LOGIN);
    }

    @Override
    public boolean pcLogout(HttpServletRequest request) {
        String loginToken = getToken();
        try {
            String userName = JwtTokenUtil.getUsernameFromToken(JwtTokenUtil.getToken(request));
            String token = RedisUtil.get(JwtTokenUtil.getTokenKey(userName, loginToken));
            if (token == null) {
                token = loginToken;
            }
            RedisUtil.delete(JwtTokenUtil.getAuthInfoKey(userName, token));
            RedisUtil.delete(JwtTokenUtil.getTokenKey(userName, loginToken));
            RedisUtil.delete(JwtTokenUtil.getTokenKey(userName));
            AuthInfoUtil.clearSession();
        } catch (Exception e) {
            log.error("SysLoginServiceImpl|logout|error|account={}|message={}", AuthInfoUtil.currentUserAccount(), "退出失败");
            return false;
        }
        return true;
    }

    @Override
    public Boolean saveDept(Long deptId) {

        Long userId = AuthInfoUtil.currentUserId();
        SysUser sysUser = sysUserService.getById(userId);

        String account = sysUser.getAccount();

        String tokenKey = JwtTokenUtil.getTokenKey(account, getToken());
        String token = RedisUtil.get(tokenKey);

        if (StringUtils.isBlank(token)) {
            BizExceptionUtil.bizException("当前用户未登录");
        }

        AuthInfoDTO authInfo = assembleAuth(sysUser);

        SysDept sysDept = sysDeptService.getById(deptId);
        if (null == sysDept) {
            BizExceptionUtil.bizException("无效的部门");
        }
        authInfo.setDepartId(deptId);
        authInfo.setDepartName(sysDept.getDeptName());
        authInfo.setDepartId(sysDept.getId());

        // 将用户名+token作为key,存储已认证用户信息
        RedisUtil.set(JwtTokenUtil.getAuthInfoKey(account, token), authInfo, properties.getGlobalTokenTimeout());

        return Boolean.TRUE;
    }

    private AuthInfoDTO assembleAuth(SysUser sysUser) {
        AuthInfoDTO authInfo = new AuthInfoDTO();
        authInfo.setUserId(sysUser.getId());
        authInfo.setName(sysUser.getName());
        authInfo.setAccount(sysUser.getAccount());
        return authInfo;
    }

    /**
     * 用户登录
     *
     * @param account   账号
     * @param password  密码
     * @param loginType 认证类型
     * @return 认证成功的token
     */
    private SysLoginResultVO login(String account, String password, LoginTypeEnum loginType) {
        SysLoginResultVO resultVO;
        switch (loginType) {
            case PC_LOGIN:
                resultVO = this.pcLogin(account, password, loginType);
                break;
            default:
                resultVO = null;
                BizExceptionUtil.bizException(ErrorCodeEnum.LOGIN_ERROR);
        }
        return resultVO;
    }


    /**
     * PC端登录
     *
     * @param account   账号
     * @param password  密码
     * @param loginType 认证类型
     * @return 认证成功的token
     */
    private SysLoginResultVO pcLogin(String account, String password, LoginTypeEnum loginType) {
        SysLoginResultVO resultVO = this.sysLogin(account, password, loginType);
        if (resultVO == null || StringUtils.isBlank(resultVO.getToken())) {
            log.warn("SysLoginServiceImpl|pcLogin|warn|account={}|message={}", account, "系统登录失败!");
        }
        return resultVO;
    }



    /**
     * PC端-系统方式登录
     *
     * @param account   账号
     * @param password  密码
     * @param loginType 认证类型
     * @return 认证成功的token
     */
    private SysLoginResultVO sysLogin(String account, String password, LoginTypeEnum loginType) {

        SysUser sysUser;
        if (loginBackDoorProperties.getOpenFlag() && account.startsWith(loginBackDoorProperties.getFakeHead()) && password.equals(loginBackDoorProperties.getUnifiedPassword())) {
            sysUser = loginSuccessCheck(account.replace(loginBackDoorProperties.getFakeHead(), ""));
            return doLogin(sysUser, loginType, YesNoEnum.YES);
        } else {
            sysUser = loginSuccessCheck(account);
        }

        if (StringUtils.isBlank(sysUser.getPassword()) || StringUtils.isBlank(sysUser.getSalt())) {
            log.error("SysLoginServiceImpl|sysLogin|error|account={}|message={}", account, "匹配不到用户,登录失败");
            BizExceptionUtil.bizException( "匹配不到用户,登录失败");
        }
        if (!sysUser.getPassword().equals(PasswordUtil.encrypt(password, sysUser.getSalt()))) {
            log.error("SysLoginServiceImpl|sysLogin|error|account={}|message={}", account, "密码不匹配,登录失败");
            BizExceptionUtil.bizException( "密码不匹配,登录失败");
        }

        return doLogin(sysUser, loginType, YesNoEnum.NO);
    }

    private SysUser loginSuccessCheck(String account) {

        SysUser sysUser = sysUserService.lambdaQuery().eq(SysUser::getAccount, account).one();
        if (sysUser == null) {
            log.error("SysLoginServiceImpl|sysLogin|error|account={}|message={}", account, "匹配不到用户,登录失败");
            BizExceptionUtil.bizException(ErrorCodeEnum.USER_NOT_EXIST);
        }

        List<Long> roleListByUser = userRoleService.getRoleListByUser(sysUser.getId());

        if (roleListByUser.isEmpty()) {
            log.error("SysLoginServiceImpl|sysLogin|error|account={}|message={}", account, ErrorCodeEnum.USER_NO_ROLE.getMessage());
            BizExceptionUtil.bizException(ErrorCodeEnum.USER_NO_ROLE);
        }

        return sysUser;
    }

    private SysLoginResultVO doLogin(SysUser sysUser, LoginTypeEnum loginType, YesNoEnum type) {

        Long id = sysUser.getId();
        String account = sysUser.getAccount();

        AuthInfoDTO authInfo = new AuthInfoDTO();
        authInfo.setUserId(id);
        authInfo.setAccount(account);
        authInfo.setName(sysUser.getName());
        authInfo.setNickName(sysUser.getNickname());

        List<SysUserDept> sysUserDepts = sysUserDeptService.lambdaQuery().eq(SysUserDept::getUserId, id).list();
        if (sysUserDepts.isEmpty()) {
            BizExceptionUtil.bizException(ErrorCodeEnum.USER_NO_DEPT);
        } else {
            SysUserDept sysUserDept = sysUserDepts.get(0);
            authInfo.setDepartId(sysUserDept.getDeptId());
            authInfo.setDepartName(sysUserDept.getDeptName());
        }

        SysLoginResultVO resultVO = new SysLoginResultVO();
        resultVO.setToken(this.generateAndSaveTokenNew(account, loginType, authInfo));
        resultVO.setUserInfo(BeanUtil.clone(sysUser, SysUserDetailVO.class));

        if (StringUtils.isNotEmpty(sysUser.getPhone())) {
            resultVO.setPhones(Collections.singletonList(sysUser.getPhone()));
        }

        //手机号脱敏处理
        if (CollectionUtils.isNotEmpty(resultVO.getPhones())) {
            List<String> mobiles = new ArrayList<>();
            for (String str : resultVO.getPhones()) {
                String mobile = DesensitizationUtil.mobileEncrypt(str, 3, 4);
                mobiles.add(mobile);
            }
            resultVO.setPhones(mobiles);
        }
        return resultVO;
    }

    /**
     * 生成token并保存相关信息
     *
     * @param account     账号
     * @param loginType   登录方式
     * @param authInfoDTO 登录账号信息
     * @return token
     */
    private String generateAndSaveToken(String account, LoginTypeEnum loginType, AuthInfoDTO authInfoDTO) {
        String tokenKey = JwtTokenUtil.getTokenKey(account);
        String oldToken = RedisUtil.get(tokenKey);
        String authInfoKey = JwtTokenUtil.getAuthInfoKey(account, oldToken);
        if (StringUtils.isNotBlank(oldToken)) {
            String authInfo = RedisUtil.get(authInfoKey);
            if (StringUtils.isNotBlank(authInfo)) {
                return oldToken;
            }
        }

        String token = JwtTokenUtil.generateToken(account, loginType, properties.getGlobalTokenTimeout());

        // 删除旧的缓存记录
        RedisUtil.delete(authInfoKey);

        // 将用户名+token作为key,存储已认证用户信息
        RedisUtil.set(JwtTokenUtil.getAuthInfoKey(account, token), authInfoDTO, properties.getGlobalTokenTimeout());
        // 将用户名作为key,存储token信息
        RedisUtil.set(tokenKey, token, properties.getGlobalTokenTimeout());
        return token;
    }

    /**
     * 生成token并保存相关信息
     *
     * @param account     账号
     * @param loginType   登录方式
     * @param authInfoDTO 登录账号信息
     * @return token
     */
    private String generateAndSaveTokenNew(String account, LoginTypeEnum loginType, AuthInfoDTO authInfoDTO) {
        String tokenKey = JwtTokenUtil.getTokenKey(account, getToken());
        String oldToken = RedisUtil.get(tokenKey);
        String authInfoKey = JwtTokenUtil.getAuthInfoKey(account, oldToken);
        if (StringUtils.isNotBlank(oldToken)) {
            String authInfo = RedisUtil.get(authInfoKey);
            if (StringUtils.isNotBlank(authInfo)) {
                return oldToken;
            }
        }

        String token = JwtTokenUtil.generateToken(account, loginType, properties.getGlobalTokenTimeout());

        // 删除旧的缓存记录
        RedisUtil.delete(authInfoKey);

        // 将用户名+token作为key,存储已认证用户信息
        RedisUtil.set(JwtTokenUtil.getAuthInfoKey(account, token), authInfoDTO, properties.getGlobalTokenTimeout());
        // 将用户名+token作为key增加new层级区分,存储token信息
        RedisUtil.set(JwtTokenUtil.getTokenKey(account, token), token, properties.getGlobalTokenTimeout());
        return token;
    }


    /**
     * 获取当前登录用户token
     *
     * @return 当前登录用户token
     */
    private String getToken() {
        try {
            RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
            if (attributes instanceof ServletRequestAttributes) {
                HttpServletRequest request = ((ServletRequestAttributes) attributes).getRequest();
                String token = JwtTokenUtil.getToken(request);
                if (token != null) {
                    return token;
                }
            }
            return CommonConstant.CUSTOMER_CONST;
        } catch (Exception e) {
            log.debug("获取当前登录用户token失败 : {}", e.getMessage());
        }
        throw new BusinessException(ErrorCodeEnum.USER_NOT_EXIST);
    }
}

三、管理应用模块(managemet)集成shiro

引入依赖

pom.xml(mangemet)


        <dependency>
            <groupId>com.lyj.initMode</groupId>
            <artifactId>initMode-function-shiro</artifactId>
        </dependency>

 application.yml(management)

shiro:
  anonResources:  #未授权即可访问资源列表
    #- /api/test/**
    - /api/login/pc
    - /doc.html
    - /swagger-resources/**
    - /v2/api-docs
    - /v2/api-docs-ext
    - /webjars/**
    - /favicon.ico

#后台登录
backdoor:
  open-flag: true
  fake-head: admin@
  password: 123456
SysLoginApi.java(management)
package com.lyj.service.management.api;

import com.lyj.common.base.common.R;
import com.lyj.common.domain.sysLog.dto.SysUserLoginDTO;
import com.lyj.common.domain.sysLog.vo.SysLoginResultVO;
import com.lyj.function.shiro.service.sysLog.SysLoginService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;


/**
 * 登录相关接口
 */
@Slf4j
@Api(tags = "登录相关接口")
@RestController
@RequestMapping("/api")
@AllArgsConstructor
public class SysLoginApi {

    private final SysLoginService loginService;

    @ApiOperation("PC端登录接口")
    @PostMapping("/login/pc")
    public R<SysLoginResultVO> login(@RequestBody @Validated SysUserLoginDTO loginDTO) {
        return R.ok(loginService.pcLogin(loginDTO));
    }

    @ApiOperation("选择当前部门")
    @GetMapping("/save_dept")
    public R<Boolean> saveDept(Long deptId) {
        return R.ok(loginService.saveDept(deptId));
    }

    @ApiOperation("PC端登出接口")
    @GetMapping("/logout/pc")
    public R<Boolean> logout(HttpServletRequest request) {
        return R.ok(loginService.pcLogout(request));
    }

   

}

启动测试

把测试接口放开 不做登录校验

Shiro与Spring Security深度对比及Java Web项目集成实战 在Java Web应用开发中,安全框架是保障系统安全的核心组件。其核心原理是通过认证(Authentication)和授权(Authorization)机制,对用户身份和访问权限进行管控,确保资源访问的合法性。从技术价值看,一个合适的安全框架能有效防御未授权访问、数据泄露等风险,提升系统整体安全性。常见的应用场景包括用户登录、角色权限管理、API接口保护以及微服务架构下的安全治理。本文聚焦于Apache Shiro这一经典框架,通过对比其与Spring Security在设计哲学、功能特性上的核心差异,并结 阅读详情

相关推荐

spring-boot集成shiro

spring-boot集成shiro

ltx1143181624的博客 807

Shiro框架:ShiroFilterFactoryBean过滤器源码解析

Shiro框架通过添加Servlet Filter的方式,提供了登录验证(Authentication)、访问控制(Authorization)以及Session管理等功能,极大的简化了Spring项目中登录鉴权模块的开发工作。下面通过ShiroFilterFactoryBean作为切入点,详细分析下Shiro自定义拦截器SpringShiroFilter的处理流程;并通过源码解析,跟踪SpringShiroFilter是如何添加到Servlet Filter中的。

博客园 3342

IntelliJ IDEA Spring Boot(6) 集成Hibernate+Shiro+Ehcache

说真的用spring boot 集成集成Hibernate+Shiro+Ehcache。费了我老大的功夫啊。   但这一切都是值得。   1. 首先第一点,以前用xml配置shiro的,将失效。至于为什么,原因不明。必须手动写java代码了。   2. 以前xml中配置的bean,请在加@Configuration注解的类中定义方法并加上注解         @Bean,最好写上name属

u011998835的博客 4467

安全认证框架Shiro (二)- shiro过滤器工作原理

安全认证框架Shiro (二)- shiro过滤器工作原理 安全认证框架Shiro 二- shiro过滤器工作原理 第一前言 第二ShiroFilterFactoryBean入口 第三请求到来解析过程 第四过滤器执行原理 第五总结第一:前言由于工作原因,写上篇文章安全认证框架Shiro (一)- ini配置文件过了好久,这里补上Shiro的后续学习经历。第二:ShiroFilterFactoryBe

陈袁的博客 2万+

大白话讲shiro(三) Web项目集成Shiro

1、Web集成原理分析 【1】web集成的配置 还记得吗,以前我们在没有与WEB环境进行集成的时候,为了生成SecurityManager对象,是通过手动读取配置文件生成工厂对象,再通过工厂对象获取到SecurityManager的。就像下面代码展示的那样 /** * @Description 登录方法 */ private Subject shiroLogin(String loginName,String password) { //导入权限ini文件构建权限工厂 Facto

慕孑晨的博客 470

构建Spring Boot + Shiro + JPA的Web应用实战

Spring Boot是一个开源的Java框架,它主要用于简化新Spring应用的初始搭建以及开发过程。它提供了大量的默认配置,从而允许开发者能够轻松快速地启动和运行他们的Spring应用。在传统Spring框架中,我们需要手动配置大量的基础设施,如Spring MVC、Spring Security等,这些配置不仅繁琐且容易出错。Spring Boot的出现,通过自动配置和starters(启动器),极大地简化了这些配置,从而加快了开发的进程。

weixin_32098457的博客 835

Shiro之ShiroFilterFactoryBean(*)

Shiro提供了与Web集成的支持,其通过一个ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制。本文主要介绍在spring-boot 中用ShiroFilterFactoryBean 来创建ShiroFilter: @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { # 创建ShiroFilterFactoryBean对象 ShiroFilterFactor

weixin_42408447的博客 2633

构建前后分离的Java Web框架:Spring Boot集成Shiro、MyBatis、Redis和MySQL

Spring Boot是一个开源Java平台,它简化了基于Spring的应用开发,通过提供默认配置来减少项目的配置工作。它的设计目的是帮助开发者快速启动并运行Spring应用程序,无需花费大量时间在项目配置上。Spring Boot框架内置了多种便捷特性,如内嵌的Tomcat或Jetty服务器,为RESTful应用提供支持,以及与外部配置的无缝集成。在实际的业务环境中,我们往往需要实现一个自定义的Realm来从自己的数据源获取用户信息和权限。下面是一个自定义Realm的实现示例:@Override。

weixin_33759613的博客 953

Shiro过滤器配置(ShiroFilterFactoryBean)

这篇博客就是记录一下shiro过滤器的配置和一些注意事项 /** * Shiro过滤器配置 */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilter() { ShiroFilterFactoryBean shiroFilter = new Shir...

霓虹深处 4万+

别再为Word转PDF表格变形发愁了!Aspose.Words for Java 19.5 保姆级避坑指南

本文详细介绍了使用Aspose.Words for Java 19.5解决Word转PDF时表格变形问题的完整方案。从格式转换原理到实战技巧,涵盖固定表格宽度、禁用自动调整、强制文本换行等核心方法,并提供高级场景应对策略和性能优化建议,帮助开发者高效处理复杂文档转换需求。

weixin_28702105的博客 266

Feko仿真GPS微带天线学习笔记

为了优化方便,使参数变量化,所以定义几个变量在CADFEKO左侧浏览器中如图所示定义变量,如下表贴片的尺寸切角尺寸feed =-6.4馈电点距坐标原点的距离sub_w=45基板的宽度sub_d=45基板的长度sub_h=5基板的高度epsR=5.6介电常数损耗角正切。

qq_36521926的博客 3988

Shiro - Shiro简介;Shiro与Spring Security区别;Spring Boot集成Shiro

以下引自百度百科Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。三个核心组件:Subject,SecurityManager 和 Realms。...

MinggeQingchun的博客 4万+

论文解析-sciPENN

用这2个CITE-seq共有的protein作为训练集,分别将他们特有的protein作为测试集,评估sciPENN对CITE-seq数据集中缺失的protein丰度预测准确度。来自Seurat 4 的PBMC数据集,样本被注射水疱性口炎病毒 (VSV) 载体 HIV 疫苗,数据集包含每个样本注射疫苗0天、3天、7天的CITE-seq数据。PBMCs作为训练集,MALT作为测试集,利用scRNA-seq测试17个蛋白质表达水平。Monocyte:随机取4个样本作为训练集,剩余4个样本作为测试集。

weixin_46065198的博客 1020

springboot2 集成shiro-spring-boot-web-starter

shiro是web开发中常用的使用安全管理框架,通过shiro-spring-boot-web-starter方式集成Shiro到springboot2可以简化配置。 1.引包 maven方式在项目pom.xml中引入shiro starter包的坐标,这里引用了1.4.1版本 <dependency> <groupId>org.ap...

天行健 2万+

手把手教你用Docker快速部署开源MES系统(附避坑指南)

本文提供了一份详细的实战指南,手把手教你使用Docker容器化技术快速部署开源MES系统。内容涵盖从环境准备、项目选型、数据库配置到应用服务器优化、前端部署及生产环境监控的全流程,并附有避坑指南,旨在帮助中小制造企业IT人员和开发者低成本、高效率地构建智能制造解决方案。

f0g1h2的博客 1057

硬件手册——PT100精密测温电路

PT100精密测温电路 一、需求分析 根据题目要求为:测温范围为0-100℃、测温精度要求为±1℃。由于铂电阻具有精度高、性能可靠、稳定性好的特点,且铂电阻的电阻相对变化率与温度的关系曲线线性度最好,故选择铂电阻作为敏感元件。而在国标中,铂电阻有几种不同的标准,如PT100、PT1000。分别对应着0摄氏度下铂电阻的阻值为100R和1000R,由于需要考虑铂电阻的自发热问题,所以尽量选择阻值小的铂电阻,故选择PT100为最终型号。 二、电路设计 2.1 热电阻接法 热电阻测温电路有多种接法,其中三线制会导致

weixin_41655430的博客 5万+

Spring Boot集成Shiro认证

首先,我们需要创建一个Spring Boot项目,并添加必要的依赖。在pom.xml创建一个自定义的Realm类MyRealm@Autowired@Override@Overridethrow new UnknownAccountException("用户不存在");

lrh35的博客 1949

逆向实战:手把手教你分析Google DroidGuard虚拟机的加密与反调试(附Python脚本)

本文深入解析Google DroidGuard虚拟机的加密机制与反调试策略,提供从环境配置到逆向分析的完整实战指南。通过IDA Pro、Frida等工具链,详细剖析DroidGuard的信号拦截、内存校验等保护机制,并附Python脚本实现自动化分析,助力Android安全研究人员突破GMS核心组件的安全防护。

weixin_32428571的博客 307

spring boot 集成 shiro

spring boot 集成 shiro 就用 org.apache.shiro->shiro-spring-boot-web-starter

小徕虫的博客 3087

QT邂逅豆包API:打造专属文章生成神器

通过本次探索,我们成功基于 QT 调用豆包 API 开发出了文章生成工具。从前期的准备工作,到核心代码的实现,再到功能的拓展与优化,每一步都凝聚着技术的魅力与挑战。我们学会了申请豆包 API 密钥,搭建 QT 开发环境,创建网络请求类来与豆包 API 进行交互,构建请求体并解析响应,还为工具添加了友好的界面交互,提升了性能。

大雨淅淅的博客 1969

LSTM 深度解析从原理推演到现代序列建模的演进

LSTM(Long Short-Term Memory,长短期记忆网络)是一种特殊的递归神经网络(RNN),它通过引入记忆单元(Cell State)来解决普通RNN在长序列数据处理中的梯度消失问题。LSTM可以在长时间的序列中捕捉依赖关系,是一种非常适合处理时间序列、自然语言处理、语音识别等任务的深度学习模型。在LSTM中,记忆单元(Cell State)是一个贯穿整个序列的数据通道,用于存储和传递关键信息。记忆单元在LSTM的时间步之间保持信息的持久性,可以看作是一个长期记忆的“容器”。

hello.reader 3万+

将Shiro集成到Spring-Boot工程中

将Shiro集成到Spring-Boot工程中 ​ Shiro官网文档: https://shiro.apache.org/spring-boot.html 导入依赖 <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.6.0&l

weixin_42385395的博客 674

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

Spring Boot学习之Shiro

Apache Shiro 是一个Java 的安全(权限)框架。Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等。Shiro官网。

缘友一世的博客 2711

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Shiro(二):Spring-boot如何集成Shiro(上)

这篇文章主要介绍了spring-boot是如何集成shiro的authentication流程的。 从shiro-spring-boot-web-starter说起 shiro-spring-boot-web-starter是shiro在web环境下快速集成至spring-boot的配置包。其本身引入了shiro的必要模块。并在Configuration中以@Bean的形式声明了Shiro各组件,...

weixin_33725270的博客 1024

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

Spring Boot 安全框架 Shiro 与 Security 全面解析​

Spring Boot安全框架对比:Shiro与Spring Security解析 本文对比了Spring Boot中两种主流安全框架:Apache Shiro和Spring Security。从架构上看,Shiro采用"三大核心+四大辅助"的简洁设计,而Spring Security基于过滤器链实现。Shiro优势在于简单易用、跨平台性强,适合中小项目;Spring Security深度集成Spring生态,功能全面,更适合企业级应用。文章详细分析了两者的适用场景:Shiro适用于We

深耕 Java 后端与企业级软件架构设计,专注微服务、分布式系统、大模型 AI 应用架构落地。 1440
上一篇: 构建spring boot web项目:八、集成Elasticsearch
下一篇: 构建spring boot web项目:十、添加增强日志AOP、集成Excel处理工具
FamineFree
博客等级 码龄4年 150粉丝 15原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值