写在前面:本文基于《springboot简易集成mybatisPlus+多数据源+flyway》上进行扩展,只是简单的进行认证以及权限判断,实现shiro权限认证以及redis缓存sessionId,做到前后端分离功能,暂不涉及密码加密、返回实体等封装优化,请读者根据自身需要进行完善。
项目源码:https://github.com/Blankwhiter/mybatisplus-springboot release 3.0
项目目录一览
目录结构说明:
├─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');
<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>
#数据库配置
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
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);
}
}
1.entity目录下
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;
}
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;
}
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;
}
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;
}
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目录下
package com.example.mybatisplus.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatisplus.entity.Menu;
public interface MenuMapper extends BaseMapper<Menu> {
}
package com.example.mybatisplus.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatisplus.entity.Role;
public interface RoleMapper extends BaseMapper<Role> {
}
package com.example.mybatisplus.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatisplus.entity.RoleMenu;
public interface RoleMenuMapper extends BaseMapper<RoleMenu> {
}
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> {
}
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目录下
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);
}
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);
}
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);
}
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);
}
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目录下
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());
}
}
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());
}
}
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());
}
}
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());
}
}
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目录下
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目录下
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();
}
}
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,修改认证的值
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;
}
}
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目录下
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目录下
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);
}
}
1.1 登陆
1.2 /admin 具备该权限
1.3 /update 具备该权限