使用shiro有段时间了,相比springsecurityshiro要更轻量化,虽说功能不及springsecurity那么强大,但也足够用了。本次将记录一下springboot2shiro的集成过程,将分为三篇来进行讲述,第一篇是项目的基础增删改查,第二篇则是使用session进行认证,第三篇则是去除session,采用无状态的jwt进行认证。由于水平有限,所以对于原理不会太深入讲解,有兴趣的大佬可自行上网搜索。

springboot2集成shiro上篇:项目基础环境搭建

1、新建项目

使用Spring Initializr快速新建maven项目,并添加相应的依赖,pom.xml文件如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.7</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.ygr</groupId><artifactId>shiro-boot-session</artifactId><version>0.0.1-SNAPSHOT</version><name>shiro-boot-session</name><description>shiro-boot-session</description><properties><java.version>1.8</java.version><mybatis-plus-boot-starter.version>3.4.3.4</mybatis-plus-boot-starter.version><hutool-all.version>5.7.17</hutool-all.version><knife4j-spring-boot-starter.version>3.0.3</knife4j-spring-boot-starter.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-spring-boot-starter</artifactId><version>${knife4j-spring-boot-starter.version}</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope><optional>true</optional></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis-plus-boot-starter.version}</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>${hutool-all.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><excludes><exclude><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></exclude></excludes></configuration></plugin></plugins></build>
</project>

简单解释一下上面用到的依赖,knife4j有些人可能不熟悉,但说到swagger应该都懂吧,注意,这个swagger说的不是Taiwan那个哈,这里说的是swagger-uiknife4j可以说是swagger-ui的美化增强版。至于其他的依赖,就不多解释了。

新建完成后建议设置一下SDKJDK1.8,新版的idea中似乎默认是JDK11,会出现找不到核心类库而爆红的情况,快捷键Ctrl + Shift + Alt + S

2、统一返回格式

在前后端分离趋势下,后端接口只需要返回约定格式的JSON即可,包路径为com.ygr.web,代码如下

@Data
public class ApiResult<T> {private boolean ok;private Integer code;private String message;private T data;private ApiResult() {this.code = HttpStatus.OK.value();}private ApiResult(T data, HttpStatus status, String message, boolean ok) {this.data = data;this.code = status.value();this.message = message;this.ok = ok;}public static <T> ApiResult<T> ok() {return new ApiResult<>(null, HttpStatus.OK, null, true);}public static <T> ApiResult<T> ok(T data) {return new ApiResult<>(data, HttpStatus.OK, null, true);}public static <T> ApiResult<T> ok(T data, String message) {return new ApiResult<>(data, HttpStatus.OK, message, true);}public static <T> ApiResult<T> ok(T data, HttpStatus status, String message) {return new ApiResult<>(data, status, message, true);}public static <T> ApiResult<T> error(String message) {return new ApiResult<>(null, HttpStatus.INTERNAL_SERVER_ERROR, message, false);}public static <T> ApiResult<T> error(HttpStatus status, String message) {return new ApiResult<>(null, status, message, false);}public static <T> ApiResult<T> error(HttpStatus status, String message, T data) {return new ApiResult<>(null, status, message, false);}
}

对于列表查询,因为可能涉及到分页,所以格式也需要进行约定,代码如下

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PageResp<T> {private Boolean paging;private Long pageNum;private Long pageSize;private Long pageCount;private Long totalCount;private List<T> list;
}

3、应用配置

(1)application.yml

配置很简单,不过多解释,代码如下

spring:datasource:url: jdbc:mysql://${MYSQL_HOST:127.0.0.1}:3306/shiro-boot-session?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=GMT%2B8username: ${MYSQL_USERNAME:root}password: ${MYSQL_PASSWORD:root}driver-class-name: com.mysql.cj.jdbc.Drivertype: com.zaxxer.hikari.HikariDataSourcehikari:minimum-idle: 5connection-test-query: SELECT 1 FROM DUALmaximum-pool-size: 20auto-commit: trueidle-timeout: 30000pool-name: ShiroBootSessionHikariCPmax-lifetime: 60000connection-timeout: 30000jackson:date-format: yyyy-MM-dd HH:mm:sslocale: zhtime-zone: GMT+8serialization:WRITE_DATES_AS_TIMESTAMPS: falsemybatis-plus:configuration:map-underscore-to-camel-case: trueglobal-config:db-config:id-type: autologging:pattern:console: '%date{yyyy-MM-dd HH:mm:ss.SSS} | %highlight(%5level) [%green(%16.16thread)] %clr(%-50.50logger{49}){cyan} %4line -| %highlight(%msg%n)'level:root: infocom.ygr: debug

(2)跨域配置

由于是前后端分离项目,所以跨域问题是必须要处理的,跨域的配置方式较多,这里选择如下方式进行配置

@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {// 添加映射路径registry.addMapping("/**")// 是否发送Cookie.allowCredentials(true)// 设置放行哪些原始域   SpringBoot2.4.4下低版本使用.allowedOrigins("*").allowedOriginPatterns("*")// 放行哪些请求方式// .allowedMethods("GET", "POST", "PUT", "DELETE", "PATCH","OPTIONS")// 放行全部.allowedMethods("*")// 放行哪些原始请求头部信息.allowedHeaders("*")// 暴露哪些原始请求头部信息.exposedHeaders("*")// 预检请求有效时间.maxAge(3600);}
}

(3)mybatisplus配置

mybatisplus需要配置的地方不多,在上面的application.yml中已经配了部分了,这里主要配置一下字段填充以及分页插件。字段填充需要配合注解使用,如@TableField(value = "create_time", fill = FieldFill.INSERT)@TableField(value = "update_time", fill = FieldFill.UPDATE, update = "current_timestamp")

@Configuration
public class MybatisPlusConfig {/*** 分页插件** @return mybatisPlusInterceptor*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}/*** 插入和更新时,自动填充时间字段** @return metaObjectHandler*/@Beanpublic MetaObjectHandler metaObjectHandler() {return new MetaObjectHandler() {@Overridepublic void insertFill(MetaObject metaObject) {this.strictInsertFill(metaObject, "createTime", Date::new, Date.class);}@Overridepublic void updateFill(MetaObject metaObject) {this.strictUpdateFill(metaObject, "updateTime", Date::new, Date.class);}};}
}

(4)knife4j配置

为了便于接口测试,引入了knife4j,配置方式与swagger没太大区别。配置扫描路径时,可以一次性将整个项目的controller都扫描出来,但个人建议还是按模块来进行扫描,有多个模块就配置多个Docket

@EnableSwagger2
@Configuration
public class Knife4jConfig {@Beanpublic Docket uaRestApi() {return new Docket(DocumentationType.SWAGGER_2).apiInfo(new ApiInfoBuilder().title("ua模块 api文档").description("shiro-boot-session api").version("1.0").build()).select().apis(RequestHandlerSelectors.basePackage("com.ygr.modules.ua.controller")).paths(PathSelectors.any()).build();}
}

4、表结构

认证授权使用的是经典的RBAC模型,涉及的表结构如下

create database if not exists `shiro-boot-session` default character set utf8mb4 collate utf8mb4_general_ci;
use `shiro-boot-session`;drop table if exists ua_user_info;
create table ua_user_info
(id           bigint auto_increment primary key,name         varchar(32)  not null unique comment '用户名',password     varchar(256) not null comment '密码',history_name varchar(1024) comment '历史名称',status       tinyint  default 1 comment '用户状态[1-正常,2-锁定]',phone        varchar(32) comment '电话',email        varchar(128) comment '邮箱',remark       varchar(1024) comment '备注',create_time  datetime default current_timestamp comment '创建时间',update_time  datetime comment '变更时间'
) comment '用户信息';drop table if exists ua_role_info;
create table ua_role_info
(id          bigint auto_increment primary key,code        varchar(32) not null unique comment '角色编号',name        varchar(64) not null comment '角色名称',status      tinyint     not null default 1 comment '角色状态[1-正常,2-禁用]',remark      varchar(1024) comment '备注',create_time datetime             default current_timestamp comment '创建时间',update_time datetime comment '变更时间'
) comment '角色信息';drop table if exists ua_authority_info;
create table ua_authority_info
(id          bigint auto_increment primary key,parent_id   bigint       not null default -1 comment '上级id',name        varchar(64)  not null comment '权限名称',uri         varchar(256) not null comment 'URI',type        tinyint      not null comment '类型[1-菜单,2-按钮/api]',perm_tag    varchar(64) comment '权限标识',group_name  varchar(32) comment '分组',status      tinyint      not null default 1 comment '状态',view        varchar(256) comment '视图',hide        bit          not null default 0 comment '掩藏',icon        varchar(64) comment '图标',sort        int                   default 0 comment '排序',remark      varchar(1024) comment '备注',create_time datetime              default current_timestamp comment '创建时间',update_time datetime comment '变更时间'
) comment '权限信息';drop table if exists ua_user_role_relation;
create table ua_user_role_relation
(id          bigint auto_increment primary key,user_id     bigint not null comment '用户id',role_id     bigint not null comment '角色id',create_time datetime default current_timestamp comment '创建时间'
) comment '用户角色关联关系';
create index ua_user_role_relation_user_id on ua_user_role_relation (user_id);
create index ua_user_role_relation_role_id on ua_user_role_relation (role_id);drop table if exists ua_role_authority_relation;
create table ua_role_authority_relation
(id           bigint auto_increment primary key,role_id      bigint not null comment '角色id',authority_id bigint not null comment '权限id',create_time  datetime default current_timestamp comment '创建时间'
) comment '角色权限关联关系';

5、代码生成

使用mybatisplus插件或其他代码生成插件生成相应表的实体类以及对应的增删改查代码,以ua_user_info表为例,代码如下

  • entity

    @Data
    @Accessors(chain = true)
    @EqualsAndHashCode(callSuper = true)
    @TableName("ua_user_info")
    @ApiModel(value = "UaUserInfo", description = "用户信息表实体类")
    public class UaUserInfo extends Model<UaUserInfo> {@TableId("id")private Long id;/*** 用户名*/@ApiModelProperty(value = "用户名")@TableField("name")private String name;/*** 密码*/@ApiModelProperty(value = "密码")@TableField("password")private String password;/*** 历史名称*/@ApiModelProperty(value = "历史名称")@TableField("history_name")private String historyName;/*** 用户状态[1-正常,2-锁定]*/@ApiModelProperty(value = "用户状态[1-正常,2-锁定]")@TableField("status")private Integer status;/*** 电话*/@ApiModelProperty(value = "电话")@TableField("phone")private String phone;/*** 邮箱*/@ApiModelProperty(value = "邮箱")@TableField("email")private String email;/*** 备注*/@ApiModelProperty(value = "备注")@TableField("remark")private String remark;/*** 创建时间*/@ApiModelProperty(value = "创建时间")@TableField(value = "create_time", fill = FieldFill.INSERT)private Date createTime;/*** 变更时间*/@ApiModelProperty(value = "变更时间")@TableField(value = "update_time", fill = FieldFill.UPDATE, update = "current_timestamp")private Date updateTime;/*** 获取主键值** @return 主键值*/@Overridepublic Serializable pkVal() {return this.id;}}
    
  • mapper

    @Mapper
    public interface UaUserInfoMapper extends BaseMapper<UaUserInfo> {}
    
  • service

    public interface UaUserInfoService extends IService<UaUserInfo> {/*** 获取加密后的密码** @param password 明文密码* @return 加密后的密码*/String encryptPassword(String password);
    }
    
  • serviceImpl

    @Service
    public class UaUserInfoServiceImpl extends ServiceImpl<UaUserInfoMapper, UaUserInfo> implements UaUserInfoService {@Overridepublic String encryptPassword(String password) {Sha256Hash hash = new Sha256Hash(password, AuthConstant.SECRET_SALT, 1024);return hash.toBase64();}
    }
    

    AuthConstant定义如下

    public interface AuthConstant {String SECRET_SALT = "my-secret-salt";
    }
    
  • controller

    @Api(tags = "用户信息")
    @Validated
    @RequiredArgsConstructor
    @RestController
    public class UaUserInfoController {private final UaUserInfoService service;/*** 列表查询用户信息** @param needPage 是否需要分页* @param pageSize 分页大小* @param pageNum  分页页码* @param params   查询条件* @return 查询结果*/@ApiOperation("列表查询")@GetMapping("/ua-user-info")public ApiResult<PageResp<UaUserInfo>> queryList(@RequestParam(value = "needPage", required = false, defaultValue = "false") boolean needPage,@RequestParam(value = "pageSize", required = false, defaultValue = "10") int pageSize,@RequestParam(value = "pageNum", required = false, defaultValue = "1") int pageNum,@RequestParam(required = false) Map<String, String> params) {UaUserInfo entity = BeanUtil.mapToBean(params, UaUserInfo.class, false, new CopyOptions().setIgnoreCase(false).setIgnoreError(true));QueryWrapper<UaUserInfo> queryWrapper = new QueryWrapper<>(entity);if (needPage) {if (pageNum <= 0 || pageSize <= 0) {return ApiResult.error(HttpStatus.BAD_REQUEST, "分页参数错误!");}Page<UaUserInfo> page = this.service.page(new Page<>(pageNum, pageSize), queryWrapper);PageResp<UaUserInfo> resp = PageResp.<UaUserInfo>builder().paging(true).pageNum(page.getCurrent()).pageSize(page.getSize()).pageCount(page.getPages()).totalCount(page.getTotal()).list(page.getRecords()).build();return ApiResult.ok(resp);}PageResp<UaUserInfo> resp = PageResp.<UaUserInfo>builder().paging(false).list(this.service.list(queryWrapper)).build();return ApiResult.ok(resp);}/*** 通过主键查询用户信息** @param id 主键* @return 单条数据*/@ApiOperation("通过主键查询")@GetMapping("/ua-user-info/{id}")public ApiResult<UaUserInfo> getOne(@PathVariable("id") Serializable id) {return ApiResult.ok(this.service.getById(id));}/*** 新增用户信息** @param entity 实体对象* @return 新增结果*/@ApiOperation("新增")@PostMapping("/ua-user-info")public ApiResult<UaUserInfo> insert(@RequestBody @Valid UaUserInfo entity) {this.service.save(entity);return ApiResult.ok(entity);}/*** 通过主键更新用户信息** @param entity 实体对象* @return 修改结果*/@ApiOperation("通过主键更新")@PutMapping("/ua-user-info")public ApiResult<UaUserInfo> update(@RequestBody @Valid UaUserInfo entity) {ApiResult<Void> checkPkVal = checkPkVal(entity);if (!checkPkVal.isOk()) {return ApiResult.error(HttpStatus.resolve(checkPkVal.getCode()), checkPkVal.getMessage());}UaUserInfo oldData = this.service.getById(entity.pkVal());if (oldData == null) {return ApiResult.error(HttpStatus.NOT_FOUND, "数据不存在!");}this.service.updateById(entity);return ApiResult.ok(this.service.getById(entity.pkVal()));}/*** 通过主键删除用户信息** @param id 主键* @return 删除结果*/@ApiOperation("通过主键删除")@DeleteMapping("/ua-user-info/{id}")public ApiResult<Void> delete(@PathVariable("id") Serializable id) {if (this.service.getById(id) == null) {return ApiResult.error(HttpStatus.NOT_FOUND, "数据不存在!");}this.service.removeById(id);return ApiResult.ok();}private ApiResult<Void> checkPkVal(UaUserInfo entity) {if (entity.pkVal() == null || "".equals(entity.pkVal().toString())) {return ApiResult.error(HttpStatus.BAD_REQUEST, "id不能为空!");}return ApiResult.ok();}
    }
    

其他的表的代码与用户表基本上一样,就不贴出来了。项目结构如下所示

到这里,项目的增删改查就OK了,启动项目,访问 http://localhost:8080/doc.html 后,可以看到如下界面,接下来就可以方便的进行接口测试了。

代码已上传至gitee,见master分支:https://gitee.com/yang-guirong/shiro-boot/tree/master/

下一篇将讲述shiro的集成过程。

springboot2集成shiro认证鉴权(上篇)相关推荐

  1. Springboot系列之Shiro、JWT、Redis 进行认证鉴权

    Springboot系列之Shiro.JWT.Redis 进行认证鉴权 Shiro架构 Apache Shiro是一个轻量级的安全框架 Shiro可以非常容易的开发出足够好的应用,其不仅可以用在Jav ...

  2. java鉴权_一个开箱即用的高效认证鉴权框架,专注于restful api的认证鉴权动态保护...

    作者:tomsun28 来源:SegmentFault 思否 写在开头 看了看这个专栏的最近一篇文章已经是两年前了,时间过得好快.应该是出学校后时间就很快了.两年前因为用shiro后,自己就按着想法开 ...

  3. 认证鉴权与API权限控制在微服务架构中的设计与实现

    引言: 本文系<认证鉴权与API权限控制在微服务架构中的设计与实现>系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现. 1. 背景 最近在做权限相关服务的开发, ...

  4. 【Blog.Core开源】网关自定义认证鉴权与传参

    书接上文,上回咱们说到了<[Blog.Core开源]网关统一集成下游服务文档>,已经将多个下游服务统一集成到了网关里,并且也把接口文档Swagger给集成了,那今天就说一下认证和鉴权相关的 ...

  5. 认证鉴权与API权限控制在微服务架构中的设计与实现(一)

    作者: [Aoho's Blog] 引言: 本文系<认证鉴权与API权限控制在微服务架构中的设计与实现>系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现. 1. ...

  6. Spring Cloud与微服务学习总结(3)——认证鉴权与API权限控制在微服务架构中的设计与实现(一)

    本文转载自(http://blueskykong.com/2017/10/19/security1/) 1. 背景 最近在做权限相关服务的开发,在系统微服务化后,原有的单体应用是基于session的安 ...

  7. api postmain 鉴权_认证鉴权与API权限控制在微服务架构中的设计与实现(一)

    引言: 本文系<认证鉴权与API权限控制在微服务架构中的设计与实现>系列的第一篇,本系列预计四篇文章讲解微服务下的认证鉴权与API权限控制的实现. 1. 背景 最近在做权限相关服务的开发, ...

  8. Spring Cloud与微服务学习总结(6)——认证鉴权与API权限控制在微服务架构中的设计与实现(四)

    本文转载自(http://blueskykong.com/2017/10/26/security4/) 1. 前文回顾 首先还是照例对前文进行回顾.在第一篇 认证鉴权与API权限控制在微服务架构中的设 ...

  9. Spring Cloud与微服务学习总结(5)——认证鉴权与API权限控制在微服务架构中的设计与实现(三)

    本文转载自( http://blueskykong.com/2017/10/24/security3/) 1. 前文回顾 在开始讲解这一篇文章之前,先对之前两篇文章进行回忆下.在第一篇 认证鉴权与AP ...

  10. Spring Cloud与微服务学习总结(4)——认证鉴权与API权限控制在微服务架构中的设计与实现(二)

    本文转载自(http://blueskykong.com/2017/10/19/security2/) 1. 系统概览 在上一篇 认证鉴权与API权限控制在微服务架构中的设计与实现(一)介绍了该项目的 ...

最新文章

  1. 通过蜜罐技术获取攻击者手机号、微信号【网络安全】
  2. python 利用 for ... else 跳出双层嵌套循环
  3. 第二届Byte Cup来袭,赢得2万美元奖金,登上字节跳动面试直通车
  4. WPF 基础控件之 DatePicker 样式
  5. dynamodb java_使用Java将项目插入DynamoDB表
  6. 对于 Netty ByteBuf 的零拷贝(Zero Copy) 的理解
  7. 4月14日,微软发布例行月度安全更新
  8. [poj 2796]单调栈
  9. ubuntu12.04下安装NVIDIA驱动
  10. 是清单 Dog List的子类 Animal ? 为什么Java泛型不是隐式多态的?
  11. Java基础知识笔记整理(零基础学Java)
  12. Mixgo CE初体验
  13. 华为云计算连接服务有这几个特点
  14. linux eclipse svn插件安装,Ubuntu16.04 Eclipse 安装 SVN 插件 subclipse 时 JavaHL 报错解决...
  15. next主题美化——背景图片、页面透明化、阴影、圆角、动画
  16. kaggle房价预测代码
  17. 3.3 测试实现标准的ZIO服务
  18. [转]ACM-ICPC比赛随想——刘汝佳
  19. “网络白痴”初学SEO成长经验心得
  20. 点击弹出窗口,在窗口中播放视频文…

热门文章

  1. html实现手机截屏,iPhone手机如何实现网页长截图?
  2. 深入WEP密码破解原理
  3. WIFI破解原理(WEP)
  4. Kinect+unity 实现体感格斗闯关小游戏
  5. 服务器上没有空闲位置星露谷,星露谷物语 献祭全房间收集攻略(上)
  6. win10程序员软件列表(持续更新中...)
  7. october php,php – 如何调用组件内的组件[OctoberCMS]
  8. linux下安装虚拟天文馆,如何在Ubuntu 18.04/17.10/16.04中安装Stellarium 0.17.0 虚拟天文馆...
  9. 数据分析03-(数值型描述统计及项目分析)
  10. 5个提供flickr高级图片搜索的网站