Bootstrap

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

目录

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

二、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));
    }

   

}

启动测试

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

;