百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 文章教程 > 正文

重磅推荐:一款使用简单、功能强大的权限认证框架

xsobi 2024-12-15 17:31 1 浏览

主要是Shiro、Security配置繁琐,这个简单易上手

官网地址

https://sa-token.dev33.cn/doc/index.html#/

主要是Shiro、Security配置繁琐,这个简单易上手

这是他的大致功能点,今天我们搞点基础的

springBoot 集成sa-token 并实现登录的验证和权限的鉴定

首先导入maven坐标

导入redis主要是sa-token使用内存来存取token的,使用redis第三方来做到重启项目token不丢,只需导入sa-token-redis的maven即可,不需要手动get,set

<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.25.0</version>
</dependency>

<!-- sa-token整合redis (使用jdk默认序列化方式) -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-dao-redis</artifactId>
    <version>1.25.0</version>
</dependency>

yml配置文件配一下

server:
  port: 8010

spring:
  servlet:
    multipart:
      enabled: true
      location: C:/var/guoheng/picture/
      max-file-size: 10MB
      max-request-size: 10MB

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/fire_control?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    #########  druid连接池配置  #########
    druid:
      # 连接池建立时创建的初始化连接数
      initial-size: 1
      # 连接池中最大的活跃连接数
      max-active: 20
      # 连接池中最小的活跃连接数
      min-idle: 1
      # 连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
      max-wait: 60000
      # 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
      pool-prepared-statements: false
      # 指定每个连接上PSCache的大小,要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100。
      max-pool-prepared-statement-per-connection-size: -1
      # 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。(不同数据库不同)
      validation-query: SELECT 'x'
      # 指定连接校验查询的超时时间,单位:秒。
      validation-query-timeout: 1
      # 是否在获得连接后检测其可用性,连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
      test-on-borrow: false
      # 是否在连接放回连接池后检测其可用性,做了这个配置会降低性能。
      test-on-return: false
      # 是否在连接空闲一段时间后检测其可用性,建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
      test-while-idle: true
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒。
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒。
      min-evictable-idle-time-millis: 300000
      # 登陆超时时间,单位是秒。
      login-timeout: 3
      # 查询超时时间,单位是秒。
      query-timeout: 3
      # 事务查询超时时间,单位是秒。
      transaction-query-timeout: 60
      # 异步关闭连接。
      async-close-connection-enable: true
      # 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:监控统计用的filter:stat,日志用的filter:log4j,防御sql注入的filter:wall
      filters: stat

      ##########  StatViewServlet监控配置  ##########
      stat-view-servlet:
        login-username: guoheng
        login-password: guoheng
        allow:
        deny:
  aop:
    auto: true

  ###################  redis配置  ###################
  redis:
    host: 127.0.0.1
    port: 6379
    password:
    jedis:
      pool:
        max-active: 8
        max-wait: -1
        max-idle: 8
        min-idle: 0
        time-between-eviction-runs: 30000

  ################### sa-token配置 ###################
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: satoken
  # token有效期,单位s 默认30天, -1代表永不过期
  timeout: 2592000
  # token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
  activity-timeout: 3600
  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
  is-concurrent: true
  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
  is-share: false
  # token风格
  token-style: simple-uuid
  # 是否输出操作日志
  is-log: false


mybatis:
  mapper-locations: classpath*:mapper/*.xml

接下来是很重要的两个sa-token的config(使用过滤器的路由鉴权)

PS:拦截器鉴权N多坑,不传satoken也能访问接口

特别注意路由一定要有区分性,例如:/user和/user/{id} 这种方式satoken框架认为是同一个路由!!导致路由鉴权将两个权限码合并认证

package com.demo.app.config.satoken;

import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import result.Result;

import java.util.Arrays;


/**
 * @program: fire
 * @description:
 * @author: fbl
 * @create: 2021-08-31 12:15
 **/
@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {

    /**
     * 注册 [sa-token全局过滤器]
     */
    @Bean
    public SaServletFilter getSaServletFilter() {
        return new SaServletFilter()

                // 指定 [拦截路由] 与 [放行路由]
                .addInclude("/**").addExclude()

                // 认证函数: 每次请求执行
                .setAuth(r -> {
                    System.out.println("---------- sa全局认证");
                    SaRouter.match(Arrays.asList("/**"), Arrays.asList(
                            "/login",
                            "/druid/**",
                            "/default/**",
                            "/",
                            "/swagger-ui.html",
                            "/swagger-resources/**",
                            "swagger/**",
                            "/webjars/**",
                            "/swagger-ui.html/*",
                            "/swagger-resources",
                            "/*.html",
                            "/**/*.html",
                            "/**/*.css",
                            "/**/*.js",
                            "/**/*.svg",
                            "/**/*.ico",
                            "/**/*.png",
                            "/**/*.jpg",
                            "/**/*.xlsx",
                            "/**/*.docx",
                            "/**/*.pdf",
                            "/webSocket/**",
                            "/*/api-docs",
                            "/v2/api-docs-ext"
                    ), StpUtil::checkLogin);
     // 路由一定要有区分性
                    SaRouter.match("/user", () -> StpUtil.checkPermission("0001"));
                    SaRouter.match("/user/get/{id}", () -> StpUtil.checkPermission("001101"));

                })

                // 异常处理函数:每次认证函数发生异常时执行此函数
                .setError(e -> {
                    return Result.failure(e.getMessage());
                })

                // 前置函数:在每次认证函数之前执行
                .setBeforeAuth(r -> {
                    // ---------- 设置一些安全响应头 ----------
                    SaHolder.getResponse()
                            // 服务器名称
                            .setServer("sa-server")
                            // 是否可以在iframe显示视图:DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                            .setHeader("X-Frame-Options", "SAMEORIGIN")
                            // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
                            .setHeader("X-Frame-Options", "1; mode=block")
                            // 禁用浏览器内容嗅探
                            .setHeader("X-Content-Type-Options", "nosniff")
                    ;
                });

    }

}

这里是设置登录用户权限和角色的地方(从权限\角色表中查询放置),这里我只校验了权限,没有校验角色

package com.demo.app.config.satoken;

import cn.dev33.satoken.stp.StpInterface;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.demo.app.mapper.permission.PermissionMapper;
import com.demo.app.mapper.permission.RolePermissionMapper;
import com.demo.app.mapper.role.RoleMapper;
import com.demo.app.mapper.user.UserMapper;
import com.demo.app.mapper.user.UserRoleMapper;
import model.entity.sys.RolePermission;
import model.entity.sys.SysPermission;
import model.entity.sys.SysRole;
import model.entity.sys.UserRole;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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





/**
 * @program: fire
 * @description: 用户登录赋予相应权限
 * @author: fbl
 * @create: 2021-08-31 13:07
 **/
@Component
public class StpInterfaceImpl implements StpInterface {
    @Autowired
    UserMapper userMapper;

    @Autowired
    UserRoleMapper userRoleMapper;

    @Autowired
    RoleMapper roleMapper;

    @Autowired
    PermissionMapper permissionMapper;

    @Autowired
    RolePermissionMapper rolePermissionMapper;
    @Override
    public List<String> getPermissionList(Object userId, String s) {
        // 用户存在,查找角色
        QueryWrapper<UserRole> userRoleQueryWrapper = new QueryWrapper<>();
        userRoleQueryWrapper.eq("user_id", userId);
        List<UserRole> userRoles = userRoleMapper.selectList(userRoleQueryWrapper);

        // 角色查找权限
        QueryWrapper<RolePermission> rolePermissionQueryWrapper = new QueryWrapper<>();
        rolePermissionQueryWrapper.in("role_id", userRoles.stream().map(UserRole::getRoleId).collect(Collectors.toList()));
        List<RolePermission> rolePermissions = rolePermissionMapper.selectList(rolePermissionQueryWrapper);

        QueryWrapper<SysPermission> permissionQueryWrapper = new QueryWrapper<>();
        permissionQueryWrapper.in("id", rolePermissions.stream().map(RolePermission::getPermissionId).distinct().collect(Collectors.toList()));
        List<SysPermission> sysPermissions = permissionMapper.selectList(permissionQueryWrapper);

        List<String> permissions = sysPermissions.stream().map(SysPermission::getCode).distinct().collect(Collectors.toList());
        return permissions;

    }

    @Override
    public List<String> getRoleList(Object userId, String s) {
        // 用户存在,查找角色
        QueryWrapper<UserRole> userRoleQueryWrapper = new QueryWrapper<>();
        userRoleQueryWrapper.eq("user_id", userId);
        List<UserRole> userRoles = userRoleMapper.selectList(userRoleQueryWrapper);

        // 查询角色
        QueryWrapper<SysRole> sysRoleQueryWrapper = new QueryWrapper<SysRole>().in("id", userRoles.stream().map(UserRole::getRoleId).collect(Collectors.toList()));
        List<SysRole> sysRoles = roleMapper.selectList(sysRoleQueryWrapper);
        List<String> roleNames = sysRoles.stream().map(SysRole::getRoleName).distinct().collect(Collectors.toList());
        return roleNames;
    }
}

打这里认证鉴权就完成了,快把,赶紧来测试一下吧。

学习资料:Java进阶视频资源

还有一个配置文件冲突的问题,之前我在webMvc里面配置的有静态文件读取和跨域等,与satoken的配置起了冲突,我修改了自己的配置文件

package com.demo.app.config.webmvc;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 类功能描述: CorsConfig
 *
 * @author Eternal
 * @date 2019-11-26 15:11
 */
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Value("${spring.servlet.multipart.location}")
    private String uploadFileUrl;

    /**
     * 跨域配置
     *
     * @param registry
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("POST", "GET", "PUT", "DELETE", "OPTIONS")
                .maxAge(3600)
                // 是否允许发送Cookie
                .allowCredentials(true)
                .allowedHeaders("*");
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        // 静态文件
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        // swagger
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");

        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
        // 上传文件
        registry.addResourceHandler("/file/**").addResourceLocations("file:/" + uploadFileUrl);
    }
}

测试一下

拿到token放进header里取请求需要权限的接口

没有权限

拥有权限

还在用Spring Security?推荐你一款使用简单、功能强大的权限认证框架

相关推荐

js向对象中添加元素(对象,数组) js对象里面添加元素

一、添加一个元素对象名["属性名"]=值(值:可以是一个值,可以是一个对象,也可以是一个数组)这样添加进去的元素,就是一个值或对象或数组...

JS小技巧,如何去重对象数组?(一)

大家好,关于数组对象去重的业务场景,想必大家都遇到过类似的需求吧,这对这样的需求你是怎么做的呢。下面我就先和大家分享下如果是基于对象的1个属性是怎么去重实现的。方法一:使用.filter()和....

「C/C++」之数组、vector对象和array对象的比较

数组学习过C语言的,对数组应该都不会陌生,于是这里就不再对数组进行展开介绍。模板类vector模板类vector类似于string,也是一种动态数组。能够在运行阶段设置vector对象的长度,可以在末...

如何用sessionStorage保存对象和数组

背景:在工作中,我将[{},{}]对象数组形式,存储到sessionStorage,然后ta变成了我看不懂的形式,然后我想取之用之,发现不可能了~记录这次深刻的教训。$clickCouponIndex...

JavaScript Array 对象 javascript的array对象

Array对象Array对象用于在变量中存储多个值:varcars=["Saab","Volvo","BMW"];第一个数组元素的索引值为0,第二个索引值为1,以此类推。更多有...

JavaScript中的数组Array(对象) js array数组

1:数组Array:-数组也是一个对象-数组也是用来存储数据的-和object不同,数组中可以存储一组有序的数据,-数组中存储的数据我们称其为元素(element)-数组中的每一个元素都有一...

数组和对象方法&amp;数组去重 数组去重的5种方法前端

列举一下JavaScript数组和对象有哪些原生方法?数组:arr.concat(arr1,arr2,arrn);--合并两个或多个数组。此方法不会修改原有数组,而是返回一个新数组...

C++ 类如何定义对象数组?初始化数组?linux C++第43讲

对象数组学过C语言的读者对数组的概念应该很熟悉了。数组的元素可以是int类型的变量,例如int...

ElasticSearch第六篇:复合数据类型-数组,对象

在ElasticSearch中,使用JSON结构来存储数据,一个Key/Value对是JSON的一个字段,而Value可以是基础数据类型,也可以是数组,文档(也叫对象),或文档数组,因此,每个JSON...

第58条:区分数组对象和类数组对象

示例设想有两个不同类的API。第一个是位向量:有序的位集合varbits=newBitVector;bits.enable(4);bits.enable([1,3,8,17]);b...

八皇后问题解法(Common Lisp实现)

如何才能在一张国际象棋的棋盘上摆上八个皇后而不致使她们互相威胁呢?这个著名的问题可以方便地通过一种树搜索方法来解决。首先,我们需要写一个函数来判断棋盘上的两个皇后是否互相威协。在国际象棋中,皇后可以沿...

visual lisp修改颜色的模板函数 怎么更改visual studio的配色

(defunBF-yansemokuai(tuyuanyanse/ss)...

用中望CAD加载LISP程序技巧 中望cad2015怎么加载燕秀

1、首先请加载lisp程序,加载方法如下:在菜单栏选择工具——加载应用程序——添加,选择lisp程序然后加载,然后选择添加到启动组。2、然后是添加自定义栏以及图标,方法如下(以...

图的深度优先搜索和广度优先搜索(Common Lisp实现)

为了便于描述,本文中的图指的是下图所示的无向图。搜索指:搜索从S到F的一条路径。若存在,则以表的形式返回路径;若不存在,则返回nil。...

两个有助于理解Common Lisp宏的例子

在Lisp中,函数和数据具有相同的形式。这是Lisp语言的一个重大特色。一个Lisp函数可以分析另一个Lisp函数;甚至可以和另一个Lisp函数组成一个整体,并加以利用。Lisp的宏,是实现上述特色的...