springboot简易集成mybatisPlus+多数据源+flyway+shiro+前后端分离之后端篇

写在前面:本文基于《springboot简易集成mybatisPlus+多数据源+flyway》上进行扩展,只是简单的进行认证以及权限判断,实现shiro权限认证以及redis缓存sessionId,做到前后端分离功能,暂不涉及密码加密、返回实体等封装优化,请读者根据自身需要进行完善。

项目源码:https://github.com/Blankwhiter/mybatisplus-springboot release 3.0
项目目录一览
springboot简易集成mybatisPlus+多数据源+flyway+shiro+前后端分离之后端篇_第1张图片

目录结构说明:

├─main
│  ├─java
│  │  └─com
│  │      └─example
│  │          └─mybatisplus
│  │              ├─config(mybatisplus配置文件以及shiro认证相关)
│  │              ├─controller(访问类)
│  │              ├─entity(实体类)
│  │              ├─exception(异常拦截器)
│  │              ├─filter(过滤器,存放着跨域过滤器)
│  │              ├─mapper(mapper类)
│  │              └─service(服务定义类)
│  │                  └─impl(服务实现类)
│  └─resources
│      └─db
│          └─migration(flyway数据库脚本文件)
└─test
    └─java
        └─com
            └─example
                └─mybatisplus(测试mybatisplus)

一、数据库脚本执行以及概述

脚本文件放置于resources/dbmigration下,读者可以手动执行,或者采用flyway工具执行(此步不再说明,请参考上篇文章)。

概述:逻辑线— 登陆用户主键 查找到 对应角色列表,可拿到对应的角色进行对该用户赋予角色后,再找到所有对应角色的菜单权限,并赋予该用户权限。

测试数据说明:1.Jone用户具有admin角色,admin角色具备访问/admin,/update的权限。2.Jack用户具有normal角色,normal角色具备访问/update权限

-- 逻辑线 登陆用户 查找到 对应角色,拿到对应的角色后,再找到所有对应角色的菜单权限
-- 用户表
CREATE TABLE user
(
	id VARCHAR(32) NOT NULL COMMENT '主键ID',
	name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
	password VARCHAR(30) NULL DEFAULT NULL COMMENT '密码',
	age INT(11) NULL DEFAULT NULL COMMENT '年龄',
	email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
	PRIMARY KEY (id)
);
INSERT INTO user (id, name, password, age, email) VALUES
(1, 'Jone', '123456', 18, '[email protected]'),
(2, 'Jack', '123456',  20, '[email protected]'),
(3, 'Tom', '123456', 28, '[email protected]'),
(4, 'Sandy', '123456',  21, '[email protected]'),
(5, 'Billie', '123456', 24, '[email protected]');

-- 角色表
CREATE TABLE role
(
	id VARCHAR(32) NOT NULL COMMENT '主键ID',
	role_name VARCHAR(30) NULL DEFAULT NULL COMMENT '角色名称',
	role_desc VARCHAR(50) NULL DEFAULT NULL COMMENT '角色描述',
	PRIMARY KEY (id)
);
INSERT INTO role (id, role_name, role_desc) VALUES
(1, 'admin', '管理员角色'),
(2, 'normal', '普通用户');

-- 用户所拥有的角色 关系表
CREATE TABLE user_role
(
	id VARCHAR(32) NOT NULL COMMENT '主键ID',
	role_id VARCHAR(30) NULL DEFAULT NULL COMMENT '角色id',
	user_id VARCHAR(50) NULL DEFAULT NULL COMMENT '用户id',
	PRIMARY KEY (id)
);
INSERT INTO user_role (id, role_id, user_id) VALUES
(1, '1', '1'),
(2, '2', '2');

-- 菜单
CREATE TABLE  menu
(
	id VARCHAR(32) NOT NULL COMMENT '主键ID',
	menu_name VARCHAR(30) NOT NULL COMMENT '菜单名称',
	perms VARCHAR(50) NOT NULL COMMENT '授权标识',
	url VARCHAR(80) NULL DEFAULT NULL COMMENT '链接地址',
	PRIMARY KEY (id)
);
INSERT INTO  menu (id,menu_name, perms, url) VALUES
(1,'用户添加','user:add','/add' ),
(2, '用户更新','user:update','/update' );

-- 角色所对应的菜单权限 关系表
CREATE TABLE role_menu
(
	id VARCHAR(32) NOT NULL COMMENT '主键ID',
	role_id VARCHAR(30) NULL DEFAULT NULL COMMENT '角色id',
	menu_id VARCHAR(50) NULL DEFAULT NULL COMMENT '权限id',
	PRIMARY KEY (id)
);

INSERT INTO role_menu (id, role_id, menu_id) VALUES
(1, '1', '1'),
(2, '1', '2'),
(3, '2', '2');


二、加入项目依赖,项目配置文件编写,以及启动类编写

1.pom.xml

<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.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.1.4.RELEASEversion>
        <relativePath/> 
    parent>
    <groupId>com.examplegroupId>
    <artifactId>mybatisplus-springbootartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>mybatisplus-springbootname>
    <description>Demo project for Spring Bootdescription>

    <properties>
        <java.version>1.8java.version>
        <mysql.version>6.0.6mysql.version>
        <mybatisplus.boot.version>3.1.0mybatisplus.boot.version>
        <druid.version>1.1.10druid.version>
        <dynamic.datasource.boot.version>2.5.4dynamic.datasource.boot.version>
        <shiro.version>1.4.0shiro.version>
        <shiro-redis.version>3.2.3shiro-redis.version>
    properties>

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <version>${mysql.version}version>
        dependency>

        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>${mybatisplus.boot.version}version>
        dependency>

        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druid-spring-boot-starterartifactId>
            <version>${druid.version}version>
        dependency>

        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>dynamic-datasource-spring-boot-starterartifactId>
            <version>${dynamic.datasource.boot.version}version>
        dependency>


        
        <dependency>
            <groupId>org.apache.shirogroupId>
            <artifactId>shiro-springartifactId>
            <version>${shiro.version}version>
        dependency>

        
        <dependency>
            <groupId>org.crazycakegroupId>
            <artifactId>shiro-redisartifactId>
            <version>${shiro-redis.version}version>
        dependency>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>

    dependencies>


    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
            
            <plugin>
                <groupId>org.flywaydbgroupId>
                <artifactId>flyway-maven-pluginartifactId>
                <version>5.2.4version>
                <configuration>
                    <url>jdbc:mysql://localhost:3306/test?serverTimezone=Hongkongurl>
                    <user>rootuser>
                    <password>111111password>
                configuration>
            plugin>

        plugins>
    build>

project>

2.application.yml


#数据库配置
spring:
  autoconfigure:
    exclude: com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceAutoConfigure  # 为了某些版本的springboot  @SpringBootApplication(exclude= {DataSourceAutoConfiguration.class}) 无法生效
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master,如果读者只是单数据源只需要注释掉slave相关配置即可,这里为了方便演示master与slave保持相同
      datasource:
        master:
          url: jdbc:mysql://localhost:3306/test?serverTimezone=Hongkong&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false  # serverTimezone=Hongkong 需要填上时区
          username: root
          password: 111111
          driverClassName:  com.mysql.cj.jdbc.Driver
        slave:
          url: jdbc:mysql://localhost:3306/test?serverTimezone=Hongkong&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false  # serverTimezone=Hongkong 需要填上时区
          username: root
          password: 111111
          driverClassName:  com.mysql.cj.jdbc.Driver
      initial-size: 10 # 以下是连接池配置
      max-active: 100
      min-idle: 10
      max-wait: 60000
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      #validation-query: SELECT 1
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        login-username: druid
        login-password: 123456
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: false
        wall:
          config:
            multi-statement-allow: true


#mybatis plus配置
mybatis-plus:
  mapper-locations: classpath:mapper/**/*.xml
  #实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: com.example.mybatisplus.entity
  check-config-location: true
  configuration:
    #是否开启自动驼峰命名规则(camel case)映射
    map-underscore-to-camel-case: true
    #全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存
    cache-enabled: false
    call-setters-on-nulls: true
    #配置JdbcTypeForNull, oracle数据库必须配置
    jdbc-type-for-null: 'null'
    #MyBatis 自动映射时未知列或未知属性处理策略 NONE:不做任何处理 (默认值), WARNING:以日志的形式打印相关警告信息, FAILING:当作映射失败处理,并抛出异常和详细信息
    auto-mapping-unknown-column-behavior: warning
  global-config:
    banner: false
    db-config:
      #主键类型  0:"数据库ID自增", 1:"未设置主键类型",2:"用户输入ID (该类型可以通过自己注册自动填充插件进行填充)", 3:"全局唯一ID (idWorker), 4:全局唯一ID (UUID), 5:字符串全局唯一ID (idWorker 的字符串表示)";
      id-type: UUID
      #字段验证策略 IGNORED:"忽略判断", NOT_NULL:"非NULL判断", NOT_EMPTY:"非空判断", DEFAULT 默认的,一般只用于注解里(1. 在全局里代表 NOT_NULL,2. 在注解里代表 跟随全局)
      field-strategy: NOT_EMPTY
      #数据库大写下划线转换
      capital-mode: true
      #逻辑删除值
      logic-delete-value: 0
      #逻辑未删除值
      logic-not-delete-value: 1

server:
  port: 7000

shiro:
  redis:
    host: 127.0.0.1:6379
    database: 7
#    如果没有密码 请注释掉password
#    password:
    timeOut: 60000
  session:
#    session 超时时间 -- 设置的时间单位是:ms,但是Shiro会把这个时间转成:s,而且是会舍掉小数部分,这样我设置的是-1ms,转成s后就是0s,马上就过期了。所有要是除以1000以后还是负数,必须设置小于-1000
    timeOut: 60000

3.MybatisplusSpringbootApplication.class
package com.example.mybatisplus;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;


/**
 * 启动类
 */
@SpringBootApplication(exclude= {DataSourceAutoConfiguration.class}) //去掉springboot 默认的数据源配置
@MapperScan("com.example.mybatisplus.mapper") //扫描mapper的包,或者读者可以在对应的mapper上加上@Mapper的注解
public class MybatisplusSpringbootApplication {
    /**
     * 配合ConfigurationProperties使用,用于springboot1.5以后
     * @return
     */
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
    public static void main(String[] args) {
        SpringApplication.run(MybatisplusSpringbootApplication.class, args);
    }

}

三、mvc编写,以及shiro相关以及mybatis配置

mvc编写(编写认证权限业务逻辑)

1.entity目录下

1.1 Menu.java
package com.example.mybatisplus.entity;

import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;

/**
 * 菜单实体类
 */
@Data
public class Menu extends Model<Menu> {
    String id;
    String menuName;
    String perms;
    String url;
}

1.2 Role.java
package com.example.mybatisplus.entity;

import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;

/**
 * 角色实体类
 */
@Data
public class Role extends Model<Role> {
    String id;
    String role_name;
    String role_desc;
}

1.3 RoleMenu.java
package com.example.mybatisplus.entity;

import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;

/**
 * 角色菜单关系实体
 */
@Data
public class RoleMenu extends Model<RoleMenu> {
    String id;
    String roleId;
    String menuId;
}

1.4 User.java
package com.example.mybatisplus.entity;

import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;

/**
 *  user 实体类,并序列化
 */
@Data
public class User  extends Model<User> {
    private String id;
    private String name;
    private String password;
    private Integer age;
    private String email;
}

1.5 UserRole.java
package com.example.mybatisplus.entity;

import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data;

/**
 * 用户角色关系实体类
 */
@Data
public class UserRole extends Model<UserRole> {
    String id;
    String roleId;
    String userId;
}

2.mapper目录下

2.1 MenuMapper.java
package com.example.mybatisplus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatisplus.entity.Menu;

public interface MenuMapper extends BaseMapper<Menu> {
}


2.2 RoleMapper.java
package com.example.mybatisplus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatisplus.entity.Role;

public interface RoleMapper extends BaseMapper<Role> {
}


2.3 RoleMenuMapper.java
package com.example.mybatisplus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatisplus.entity.RoleMenu;

public interface RoleMenuMapper extends BaseMapper<RoleMenu> {
}


2.4 UserMapper.java
package com.example.mybatisplus.mapper;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatisplus.entity.User;

/**
 *  用户mapper 这里 @DS 是配置数据源注解,默认是master
 */
@DS("slave")
public interface UserMapper extends BaseMapper<User> {
}


2.5 UserRoleMapper.java
package com.example.mybatisplus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatisplus.entity.UserRole;

public interface UserRoleMapper extends BaseMapper<UserRole> {
}


3.service目录下

3.1 MenuMapper.java
package com.example.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mybatisplus.entity.Menu;

import java.util.List;

/**
 * menuService
 */
public interface MenuService extends IService<Menu> {
    /**
     * 通过菜单id列表获得权限列表
     * @param menuIds
     * @return
     */
    List<String> findPermsByMenuIds(List<String> menuIds);
}


3.2 RoleMapper.java
package com.example.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mybatisplus.entity.RoleMenu;

import java.util.List;
import java.util.Set;

/**
 * roleMenuService
 */
public interface RoleMenuService extends IService<RoleMenu> {

    /**
     * 通过角色id列表查找出所有对应的菜单id
     * @param roleIds
     * @return
     */
    List<String> findMenuIdsByRoleIds(Set<String> roleIds);
}

3.3 RoleService.java
package com.example.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mybatisplus.entity.Role;

import java.util.List;

/**
 * roleService
 */
public interface RoleService extends IService<Role> {

    /**
     * 通过角色id查找出对应所有的用户具备角色名称
     * @param ids
     * @return
     */
     List<String> findRoleNameByRoleIds(List<String> ids);
}

3.4 UserRoleService.java
package com.example.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mybatisplus.entity.UserRole;

import java.util.List;

/**
 *  userRoleService
 */
public interface UserRoleService   extends IService<UserRole> {
    /**
     * 通过用户id 查找出具备的角色id
     * @param userId
     * @return
     */
    List<String> findRoleIdsByUserId(String userId);
}

3.5 UserService.java
package com.example.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mybatisplus.entity.User;

/**
 * userService 接口定义
 */
public interface UserService extends IService<User> {
    /**
     * 通过用户名 查找用户
     * @param name
     * @return
     */
    User findUserByName(String name);
}


4.service/impl目录下

4.1 MenuServiceImpl.java (通过权限id列表找到授权编码,与LoginController中的@RequiresPermissions保持一致)
package com.example.mybatisplus.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mybatisplus.entity.Menu;
import com.example.mybatisplus.mapper.MenuMapper;
import com.example.mybatisplus.service.MenuService;
import org.springframework.stereotype.Service;

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

@Service
public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements MenuService {
    @Override
    public List<String> findPermsByMenuIds(List<String> menuIds) {
        QueryWrapper<Menu> wrapper = new QueryWrapper<>();
        wrapper.in("id",menuIds);
        return baseMapper.selectList(wrapper).stream().map(Menu::getPerms).collect(Collectors.toList());
    }
}

4.2 RoleMenuServiceImpl.java (通过角色id列表查找出所有菜单id列表)
package com.example.mybatisplus.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mybatisplus.entity.RoleMenu;
import com.example.mybatisplus.mapper.RoleMenuMapper;
import com.example.mybatisplus.service.RoleMenuService;
import org.springframework.stereotype.Service;

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

@Service
public class RoleMenuServiceImpl extends ServiceImpl<RoleMenuMapper, RoleMenu> implements RoleMenuService {
    @Override
    public List<String> findMenuIdsByRoleIds(Set<String> roleIds) {
        QueryWrapper<RoleMenu> wrapper = new QueryWrapper<>();
        wrapper.in("role_id",roleIds);
        List<RoleMenu> roleMenuList = baseMapper.selectList(wrapper);
        return roleMenuList.stream().map(RoleMenu::getMenuId).collect(Collectors.toList());
    }
}


4.3 RoleServiceImpl.java (通过角色id列表查找出对应的角色名称编码)
package com.example.mybatisplus.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mybatisplus.entity.Role;
import com.example.mybatisplus.mapper.RoleMapper;
import com.example.mybatisplus.service.RoleService;
import org.springframework.stereotype.Service;

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

@Service
public class RoleServiceImpl extends ServiceImpl<RoleMapper, Role> implements RoleService {

    @Override
    public List<String> findRoleNameByRoleIds(List<String> ids) {
        QueryWrapper<Role> wrapper = new QueryWrapper<>();
        wrapper.in("id",ids);
        List<Role> roleList = baseMapper.selectList(wrapper);
        return roleList.stream().map(Role::getRole_name).collect(Collectors.toList());
    }
}

4.4 UserRoleServiceImpl.java (通过用户id查找出对应的角色id列表)
package com.example.mybatisplus.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mybatisplus.entity.UserRole;
import com.example.mybatisplus.mapper.UserRoleMapper;
import com.example.mybatisplus.service.UserRoleService;
import org.springframework.stereotype.Service;

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

@Service
public class UserRoleServiceImpl extends ServiceImpl<UserRoleMapper, UserRole> implements UserRoleService {
    @Override
    public List<String> findRoleIdsByUserId(String userId) {
        QueryWrapper<UserRole> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id",userId);
        List<UserRole> roleList = baseMapper.selectList(wrapper);
        return roleList.stream().map(UserRole::getRoleId).collect(Collectors.toList());
    }
}

4.5 UserRoleMapper.java
package com.example.mybatisplus.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mybatisplus.entity.User;
import com.example.mybatisplus.mapper.UserMapper;
import com.example.mybatisplus.service.UserService;
import org.springframework.stereotype.Service;


/**
 * userService实现类
 */
@Service
public class UserServiceImpl  extends ServiceImpl<UserMapper,User> implements UserService {


    @Override
    public User findUserByName(String name) {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("name",name);
        return baseMapper.selectOne(userQueryWrapper);
    }
}

5.controller目录下

5.1 LoginController.java
package com.example.mybatisplus.controller;

import com.example.mybatisplus.entity.User;
import com.example.mybatisplus.exception.GenericExceptionHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;


/**
 * 登录类
 */
@Slf4j
@RestController
public class LoginController  {

    @Value("${shiro.session.timeOut}")
    private Long sessionTimeOut;

    /**
     * 登陆验证
     * @param user
     * @return
     */
    @PostMapping("/login")
    public String login(User user){
        //获得subject
        Subject subject = SecurityUtils.getSubject();
        //封装用户数据
        UsernamePasswordToken token = new UsernamePasswordToken(user.getName(),user.getPassword());
        //登录验证。没有异常则表示登录成功,发生异常表示登录失败
        try {
            subject.login(token);
            //session 设置超时时间
            subject.getSession().setTimeout(sessionTimeOut);
            log.info("sessionId : {}",subject.getSession().getId().toString());
            log.info("登录完成");
            return subject.getSession().getId().toString();
        }catch (UnknownAccountException e){
            log.info("用户名不存在");
            return "用户名不存在";
        }catch (IncorrectCredentialsException e){
            log.info("密码错误");
            return "密码错误";
        }finally {
            log.info("执行完毕");
        }

    }


    /**
     * 未登录, 会重定向到此处
     * @return
     */
    @RequestMapping(value = "/unAuthentication")
    @ResponseBody
    public Object unAuthentication() {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("code", "101");
        map.put("msg", "未登录");
        return map;
    }


    /**
     * 测试数据配置了给只有admin角色权限可以访问
     * @return
     */
    @GetMapping("/add")
    @RequiresPermissions("user:add")
    public String addUser(){
      log.info(" run add user");
      return "add user success";
    }


    /**
     * 具有admin normal角色都可以访问
     * @return
     */
    @GetMapping("/update")
    @RequiresPermissions("user:update")
    public String updateUser(){
      log.info(" run update user");
      return "update user success";
    }
}


注:此处/add,/update只是为了验证权限功能,不写业务逻辑代码。

6.config目录下

6.1 MybatisPlusConfig.java
package com.example.mybatisplus.config;


import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * mybatis-plus配置
 */
@Configuration
public class MybatisPlusConfig {

    /**
     * 分页插件
     * https://mp.baomidou.com/guide/page.html
     *
     * @return PaginationInterceptor
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

}


6.2 ShiroConfig.java
package com.example.mybatisplus.config;

import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
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.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import java.util.LinkedHashMap;

/**
 * shiro配置类
 */
@Configuration
public class ShiroConfig {


    /**
     * 创建shiroFilterFactoryBean
     * @param defaultWebSecurityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
        //当用户未登陆时 进入到自定义路径
        shiroFilterFactoryBean.setLoginUrl("/unAuthentication");
        /**
         * shiro内置拦截器,实现权限拦截
         *    种类:anon ---- 无需认证即可访问
         *          authc ------ 必须认证才能访问
         *          user ------- 如果使用rememberMe的功能可以直接访问
         *          perms ------ 该资源必须得到资源权限才可访问
         *          role ------- 改权限必须得到角色权限才可访问
         */
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        //将登录请求放行, 拦截判断按照添加顺序依次进行判断
        map.put("/druid/**", "anon");
        map.put("/login","anon");
        map.put("/*","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /**
     *  创建DefaultWebSecurityManager,注入自定义realm
     * @param userRealm
     * @return
     */
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm, RedisCacheManager redisCacheManager,SessionManager sessionManager){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //加入权限认证
        defaultWebSecurityManager.setRealm(userRealm);
        // 自定义cache管理  使用redis缓存
        defaultWebSecurityManager.setCacheManager(redisCacheManager);
        // 自定义session管理 使用redis缓存
        defaultWebSecurityManager.setSessionManager(sessionManager);
        return  defaultWebSecurityManager;

    }

    /**
     * cacheManager 缓存 redis实现
     * 使用的是shiro-redis开源插件
     * @return
     */
    @Bean
    public RedisCacheManager redisCacheManager(RedisManager redisManager) {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager);
        return redisCacheManager;
    }
    /**
     * RedisSessionDAO config sessionDao层 使用redis实现
     * @param redisManager
     * @return
     */
    @Bean
    public RedisSessionDAO redisSessionDAO(RedisManager redisManager) {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager);
        return redisSessionDAO;
    }

    /**
     * 设置session管理器
     * @param redisSessionDAO
     * @return
     */
    @Bean
    public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(true);
        sessionManager.setSessionIdCookieEnabled(true);
        sessionManager.setSessionDAO(redisSessionDAO);
        return sessionManager;
    }



    /**
     *  Shiro生命周期处理器
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

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

    /**
     *  配合开启注解
     * @param defaultWebSecurityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);
        return authorizationAttributeSourceAdvisor;
    }


}


注:这里DefaultWebSessionManager 里面默认的验证key是JESSIONID,故在最后一步验证时候需指定JESSIONID,读者可以自定义DefaultWebSessionManager,修改认证的值

6.3 ShiroRedisConfig.java
package com.example.mybatisplus.config;

import lombok.Data;
import org.crazycake.shiro.RedisManager;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;

import org.springframework.stereotype.Component;

/**
 * 配置redis环境 供shiro-redis插件使用
 */
@Data
@Component
@ConfigurationProperties(prefix = "shiro.redis")
public class ShiroRedisConfig {

    private String host;

    private int database;

    private String password;

    private int timeOut;

    @Bean
    public RedisManager redisManager(){
        RedisManager redisManager = new RedisManager();
        redisManager.setDatabase(database);
        redisManager.setHost(host);
        redisManager.setPassword(password);
        redisManager.setTimeout(timeOut);
        return redisManager;
    }
}


6.4 UserRealm.java
package com.example.mybatisplus.config;

import com.example.mybatisplus.entity.User;
import com.example.mybatisplus.service.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;

/**
 * 用户权限realm ,认证授权最终都在此处实现,用于安全框架dao
 */
@Slf4j
@Component
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private RoleService roleService;

    @Autowired
    private UserRoleService userRoleService;

    @Autowired
    private RoleMenuService roleMenuService;

    @Autowired
    private MenuService menuService;
    /**
     * 执行认证逻辑
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("UserRealm.doGetAuthenticationInfo  认证");
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();
        log.info("username : {}", username);
        char[] password = token.getPassword();
        log.info("password : {}", password);

        //1.判断用户名是否存在
        User user = userService.findUserByName(username);
        if(user == null){
            //返回null 会抛出 UnknownAccountException
            return null;
        }else {
            //2.判断密码是否正确,密码则不需要自己判断 ,通过SimpleAuthenticationInfo,第二参数判断是否一致即可,第一个参数是为了在赋予权限时候拿到对应的用户
            return new SimpleAuthenticationInfo(user,user.getPassword(),"");
        }

    }

    /**
     * 执行赋予授权逻辑
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("UserRealm.doGetAuthorizationInfo  授权");
        SimpleAuthorizationInfo info =  new SimpleAuthorizationInfo();
        //从认证中获得用户  return new SimpleAuthenticationInfo(user,user.getPassword(),"");
        User user = (User)principals.getPrimaryPrincipal();
        String userId = user.getId();
        //1.通过用户id查找出用户角色
        List<String> roleIdsList = userRoleService.findRoleIdsByUserId(userId);
        List<String> roleNamesList = roleService.findRoleNameByRoleIds(roleIdsList);
        HashSet<String> roleSet = new HashSet<>(roleNamesList);
        info.setRoles(roleSet);
        //2.通过角色找到对应的权限

        HashSet<String> roleIdsSet = new HashSet<>(roleIdsList);
        List<String> menuIds = roleMenuService.findMenuIdsByRoleIds(roleIdsSet);
        List<String> perms = menuService.findPermsByMenuIds(menuIds);
        info.addStringPermissions(perms);
        return info;
    }
}

7.exception目录下

7.1 GenericExceptionHandler.java
package com.example.mybatisplus.exception;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;


/**
 *  全局异常捕捉
 *  controller层 要使用@RestController与RestControllerAdvice对应,如果使用@Controller 则使用@ControllerAdvice与之对应
 */
@RestControllerAdvice
public class GenericExceptionHandler {

    /**
     *  拦截没有权限的异常
     * @param e
     * @return
     */
    @ExceptionHandler(UnauthorizedException.class)
    public Map<String, Object> handleUnAuthorizedException(UnauthorizedException e) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", "102");
        map.put("msg", "未授权");
        return map;
    }

    /**
     * 拦截异常
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public Map<String, Object> handleException(Exception e) {
        Map<String, Object> map = new HashMap<>();
        map.put("code", "100");
        map.put("msg", e.getMessage());
        return map;
    }
}

8.filter目录下

8.1 CorsFilter.java (前后端分离处理,允许跨域请求)
package com.example.mybatisplus.filter;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 *  跨域处理
 */
@Configuration
public class CorsFilter implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                .allowedHeaders("x-requested-with")
                .maxAge(3600);
    }
}


四、使用postman进行测试

1.使用Jone账号进行登陆,并权限验证

1.1 登陆
springboot简易集成mybatisPlus+多数据源+flyway+shiro+前后端分离之后端篇_第2张图片
1.2 /admin 具备该权限
springboot简易集成mybatisPlus+多数据源+flyway+shiro+前后端分离之后端篇_第3张图片
1.3 /update 具备该权限
springboot简易集成mybatisPlus+多数据源+flyway+shiro+前后端分离之后端篇_第4张图片

2.使用Jack账号进行登陆,并权限验证

2.1 登陆
springboot简易集成mybatisPlus+多数据源+flyway+shiro+前后端分离之后端篇_第5张图片
2.2 /admin 不具备该权限 被GenericExceptionHandler所拦截
springboot简易集成mybatisPlus+多数据源+flyway+shiro+前后端分离之后端篇_第6张图片

2.3 /update 具备该权限
springboot简易集成mybatisPlus+多数据源+flyway+shiro+前后端分离之后端篇_第7张图片

你可能感兴趣的:(springboot,shiro)