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

>>强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!

点击关注公众号,利用碎片时间学习

来源:blog.csdn.net/qq_40058629/article/

details/116692302

我们先看一下官网介绍,sa-token有什么功能

官网地址

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

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

还在用Spring 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);
    }
}

测试一下

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

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

没有权限

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

拥有权限

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

最后一点:用户登录过后,header中不用传satoken也能进行鉴权(自动拿的是登录的用户的satoken),多个用户登录拿的是最后一个用户登录的satoken,好扯,也就是说必须要传satoken

推荐:

主流Java进阶技术(学习资料分享)

还在用Spring Security?推荐你一款使用简单、功能强大的权限认证框架
PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。“在看”支持我们吧!

原文始发于微信公众号(Java笔记虾):还在用Spring Security?推荐你一款使用简单、功能强大的权限认证框架