springboot2集成shiro认证鉴权(上篇)

springboot2集成shiro认证鉴权(上篇),第1张

springboot2集成shiro认证鉴权(上篇)

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

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

上篇-项目基础增删改查
    • 1、新建项目
    • 2、统一返回格式
    • 3、应用配置
      • (1)application.yml
      • (2)跨域配置
      • (3)mybatisplus配置
      • (4)knife4j配置
    • 4、表结构
    • 5、代码生成

1、新建项目

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



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.5.7
         
    
    com.ygr
    shiro-boot-session
    0.0.1-SNAPSHOT
    shiro-boot-session
    shiro-boot-session
    
        1.8
        3.4.3.4
        5.7.17
        3.0.3
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-validation
        
        
            com.github.xiaoymin
            knife4j-spring-boot-starter
            ${knife4j-spring-boot-starter.version}
        

        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        
        
            mysql
            mysql-connector-java
            runtime
        
        
            com.baomidou
            mybatis-plus-boot-starter
            ${mybatis-plus-boot-starter.version}
        
        
            cn.hutool
            hutool-all
            ${hutool-all.version}
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            org.projectlombok
                            lombok
                        
                    
                
            
        
    

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

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

2、统一返回格式

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

@Data
public class ApiResult {

    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  ApiResult ok() {
        return new ApiResult<>(null, HttpStatus.OK, null, true);
    }

    public static  ApiResult ok(T data) {
        return new ApiResult<>(data, HttpStatus.OK, null, true);
    }

    public static  ApiResult ok(T data, String message) {
        return new ApiResult<>(data, HttpStatus.OK, message, true);
    }

    public static  ApiResult ok(T data, HttpStatus status, String message) {
        return new ApiResult<>(data, status, message, true);
    }


    public static  ApiResult error(String message) {
        return new ApiResult<>(null, HttpStatus.INTERNAL_SERVER_ERROR, message, false);
    }

    public static  ApiResult error(HttpStatus status, String message) {
        return new ApiResult<>(null, status, message, false);
    }

    public static  ApiResult error(HttpStatus status, String message, T data) {
        return new ApiResult<>(null, status, message, false);
    }
}

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

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PageResp {

    private Boolean paging;

    private Long pageNum;

    private Long pageSize;

    private Long pageCount;

    private Long totalCount;

    private List 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%2B8
    username: ${MYSQL_USERNAME:root}
    password: ${MYSQL_PASSWORD:root}
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 5
      connection-test-query: SELECt 1 FROM DUAL
      maximum-pool-size: 20
      auto-commit: true
      idle-timeout: 30000
      pool-name: ShiroBootSessionHikariCP
      max-lifetime: 60000
      connection-timeout: 30000

  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    locale: zh
    time-zone: GMT+8
    serialization:
      WRITE_DATES_AS_TIMESTAMPS: false

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
  global-config:
    db-config:
      id-type: auto

logging:
  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: info
    com.ygr: debug
(2)跨域配置

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

@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 添加映射路径
        registry.addMapping("
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }

    
    @Bean
    public metaObjectHandler metaObjectHandler() {
        return new metaObjectHandler() {
            @Override
            public void insertFill(metaObject metaObject) {
                this.strictInsertFill(metaObject, "createTime", Date::new, Date.class);
            }

            @Override
            public void updateFill(metaObject metaObject) {
                this.strictUpdateFill(metaObject, "updateTime", Date::new, Date.class);
            }
        };
    }
}
(4)knife4j配置

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

@EnableSwagger2
@Configuration
public class Knife4jConfig {

    @Bean
    public 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 {
    
        @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;
    
        
        @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;
    
        
        @Override
        public Serializable pkVal() {
            return this.id;
        }
    
    }
    
  • mapper

    @Mapper
    public interface UaUserInfoMapper extends baseMapper {
    
    }
    
  • service

    public interface UaUserInfoService extends IService {
    
        
        String encryptPassword(String password);
    }
    
  • serviceImpl

    @Service
    public class UaUserInfoServiceImpl extends ServiceImpl implements UaUserInfoService {
    
        @Override
        public 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;
    
        
        @ApiOperation("列表查询")
        @GetMapping("/ua-user-info")
        public ApiResult> 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 params) {
            UaUserInfo entity = BeanUtil.mapToBean(params, UaUserInfo.class, false, new CopyOptions().setIgnoreCase(false).setIgnoreError(true));
            QueryWrapper queryWrapper = new QueryWrapper<>(entity);
            if (needPage) {
                if (pageNum <= 0 || pageSize <= 0) {
                    return ApiResult.error(HttpStatus.BAD_REQUEST, "分页参数错误!");
                }
                Page page = this.service.page(new Page<>(pageNum, pageSize), queryWrapper);
                PageResp resp = PageResp.builder()
                        .paging(true)
                        .pageNum(page.getCurrent())
                        .pageSize(page.getSize())
                        .pageCount(page.getPages())
                        .totalCount(page.getTotal())
                        .list(page.getRecords())
                        .build();
                return ApiResult.ok(resp);
            }
            PageResp resp = PageResp.builder()
                    .paging(false)
                    .list(this.service.list(queryWrapper))
                    .build();
            return ApiResult.ok(resp);
        }
    
    
        
        @ApiOperation("通过主键查询")
        @GetMapping("/ua-user-info/{id}")
        public ApiResult getOne(@PathVariable("id") Serializable id) {
            return ApiResult.ok(this.service.getById(id));
        }
    
        
        @ApiOperation("新增")
        @PostMapping("/ua-user-info")
        public ApiResult insert(@RequestBody @Valid UaUserInfo entity) {
            this.service.save(entity);
            return ApiResult.ok(entity);
        }
    
        
        @ApiOperation("通过主键更新")
        @PutMapping("/ua-user-info")
        public ApiResult update(@RequestBody @Valid UaUserInfo entity) {
            ApiResult 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()));
        }
    
        
        @ApiOperation("通过主键删除")
        @DeleteMapping("/ua-user-info/{id}")
        public ApiResult 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 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的集成过程。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5684372.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存