SaToken使用springboot+redis+satoken权限认证

Posted 符华-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SaToken使用springboot+redis+satoken权限认证相关的知识,希望对你有一定的参考价值。

前言

之前看到satoken(文档),感觉很方便。之前我用shiro+redis+jwt(或者session)遇到的一些问题,用这个感觉都不是问题,很轻易就能解决,比如:多端登录可以不用写realm、移动端保持长期登录、token自动刷新、超过系统空闲时间重新登录等。

功能还是比较全面的,下面主要是会写一些比较常用的。

一、大概需求

web登录,有个闲置时间设置:1 30分钟未发送请求,重新登录,0 无限制。
角色菜单权限控制。

一、框架搭建

1、引入依赖、yml文件

<properties>
    <java.version>1.8</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.5.6</spring-boot.version>
    <sa-token-version>1.29.0</sa-token-version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-api</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.apache.logging.log4j</groupId>
                <artifactId>log4j-to-slf4j</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <!-- mysql 驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.11</version>
        <scope>runtime</scope>
    </dependency>
    <!-- mybatis_plus -->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.4.3</version>
    </dependency>

    <!-- hutool工具类 -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.7.22</version>
    </dependency>

    <!-- 提供Redis连接池 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>

    <!-- pagehelper分页插件 -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.2.9</version>
        <exclusions>
            <exclusion>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <!-- sa-token权限认证框架 -->
    <dependency>
        <groupId>cn.dev33</groupId>
        <artifactId>sa-token-spring-boot-starter</artifactId>
        <version>$sa-token-version</version>
    </dependency>
    <!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
    <dependency>
        <groupId>cn.dev33</groupId>
        <artifactId>sa-token-dao-redis-jackson</artifactId>
        <version>$sa-token-version</version>
    </dependency>
    <!-- Sa-Token插件:权限缓存与业务缓存分离 -->
    <dependency>
        <groupId>cn.dev33</groupId>
        <artifactId>sa-token-alone-redis</artifactId>
        <version>$sa-token-version</version>
    </dependency>

</dependencies>

server:
  port: 8081

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/satoken_db?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
    username: root
    password: root
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver

  redis:
    host: "127.0.0.1"
    port: 6379
    timeout: 10s
    password: 123456
    database: 0
    lettuce:
      pool:
        max-active: -1
        max-wait: -1
        max-idle: 16
        min-idle: 8

  main:
    allow-bean-definition-overriding: true

  servlet:
    multipart:
      max-file-size: -1
      max-request-size: -1

  aop:
    auto: true

# Sa-Token配置
sa-token:
  # token名称 (同时也是cookie名称)
  token-name: sa-token-authorization
  # token有效期,单位s 默认30天, -1代表永不过期
  timeout: 3600
  # token风格
  token-style: random-32
  # 是否尝试从 header 里读取 Token
  is-read-head: true
  # 是否开启自动续签
  auto-renew: true
  # 临时有效期,单位s,例如将其配置为 1800 (30分钟),代表用户如果30分钟无操作,则此Token会立即过期
  activity-timeout: 1800
  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时同端互斥) 
  is-concurrent: true
  # 配置 Sa-Token 单独使用的 Redis 连接
  alone-redis:
    # Redis数据库索引(默认为0)
    database: 0
    # Redis服务器地址
    host: 127.0.0.1
    # Redis服务器连接端口
    port: 6379
    # Redis服务器连接密码(默认为空)
    password: 123456
    # 连接超时时间
    timeout: 10s


mybatis-plus:
  mapper-locations: classpath:mapper/*/*.xml
  type-aliases-package: com.entity.sys,;com.common.base
  global-config:
    db-config:
      id-type: auto
      field-strategy: NOT_EMPTY
      db-type: MYSQL
  configuration:
    map-underscore-to-camel-case: true
    call-setters-on-nulls: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

2、Config 和 Interceptor

@Configuration
@EnableWebMvc
public class GlobalCorsConfig implements WebMvcConfigurer 

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) 
        String path = System.getProperty("user.dir") + "\\\\upload\\\\";
        registry.addResourceHandler("/upload/**")
                .addResourceLocations("file:"+path).addResourceLocations("classpath:/resources/");
    

    // 注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) 
        // 注册Sa-Token的路由拦截器
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**")
                .excludePathPatterns("/sys/login","/sys/getCode","/sys/getKey","/api/favicon.ico","/upload/**");
        //registry.addInterceptor(new SaRouteInterceptor((req, resp, handler) -> 
        //    SaRouter.match("/sys/login","/sys/getCode","/sys/getKey","/favicon.ico","/upload/**");
        //)).addPathPatterns("/**");
    

    /**
     * 允许跨域调用的过滤器
     */
    @Bean
    public CorsFilter corsFilter() 
        CorsConfiguration config = new CorsConfiguration();
        //允许所有域名进行跨域调用
        config.addAllowedOriginPattern("*");
        //允许跨越发送cookie
        config.setAllowCredentials(true);
        //放行全部原始头信息
        config.addAllowedHeader("*");
        //允许所有请求方法跨域调用
        config.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    

    @Override
    public void addCorsMappings(CorsRegistry registry) 
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowCredentials(true)
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                .maxAge(3600)
        .exposedHeaders();
    

LoginInterceptor 登录拦截

/**
 * 登录拦截器
 */
public class LoginInterceptor implements HandlerInterceptor 

    /**
     * 删除redis缓存
     */
    private void delCache(String tokenValue,String loginId)
        RedisUtil redisUtil= SpringUtil.getBean(RedisUtil.class);
        String lastActivity = BaseConstant.cachePrefix+"last-activity:"+tokenValue;
        String session = BaseConstant.cachePrefix+"session:"+loginId;
        String token = BaseConstant.tokenCachePrefix+tokenValue;
        if (redisUtil.hasKey(lastActivity))
            redisUtil.del(lastActivity);
        
        if (redisUtil.hasKey(session))
            redisUtil.del(session);
        
        if (redisUtil.hasKey(token))
            redisUtil.del(token);
        
    

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception 
        response.setHeader("Access-Control-Allow-Origin", (request).getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        // 获取当前token
        String tokenValue = StpUtil.getTokenValue();
        //判断token是否过期:如果token的有效期还没到,但是activity-timeout已经过了,那么token就会失效
        if (!StpUtil.isLogin())
            ResultVo resultVo = new ResultVo();
            resultVo.setCode(1003);
            resultVo.setMessage("用户未登录,请进行登录");
            response.getWriter().write(JSONUtil.toJsonStr(resultVo));
            // 根据token获取用户id
            String loginId = (String) StpUtil.getLoginIdByToken(tokenValue);
            //token已经过期,但是redis中可能还存在,所以要删除
            delCache(tokenValue,loginId);
            return false;
        
		//判断token的创建时间是否大于2小时,如果是的话则需要刷新token
		/*
        long time = System.currentTimeMillis() - StpUtil.getSession().getCreateTime();
        long hour = time/1000/(60 * 60);
        System.out.println(hour);
        if (hour>2)
        	// 本来这里我是想要生成一个新token,然后我找了下没有直接生成token的方法;
        	// 之后我看了它的需求墙,作者说每次调用登录方法都会自动刷新并生成新的token。
        	// 但是我这里调用登录方法,发现token并没有改变,还是原来的那个token,不知道是我方式用错了还是少了代码。所以这个地方存疑,有知道的小伙伴可以评论区留言。
        	StpUtil.login(loginId);
            System.err.println("生成的新的token:"+StpUtil.getTokenValue());
            response.setHeader("sa-token-authorization", StpUtil.getTokenValue());
        */

        // 获取过期时间
        long tokenTimeout = StpUtil.getTokenTimeout();
        //token没过期,过期时间不是-1的时候,每次请求都刷新过期时间
        if (tokenTimeout != -1)
            SaTokenDao saTokenDao = SaManager.getSaTokenDao();
            saTokenDao.updateSessionTimeout(StpUtil.getSession().getId(),3600);
            saTokenDao.updateTimeout(BaseConstant.tokenCachePrefix+tokenValue,3600);
            saTokenDao.updateTimeout(BaseConstant.cachePrefix+"last-activity:"+tokenValue,3600);
        
        // 检查通过后继续续签
        //StpUtil.updateLastActivityToNow();
        return true;
    

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,ModelAndView modelAndView) throws Exception 
    

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception 
    


PermissionInterface 权限验证

/**
 * 自定义权限验证接口扩展
 */
@Component
public class PermissionInterface implements StpInterface 

    @Resource
    private SysMenuService sysMenuService;

    /**
     * 返回一个账号所拥有的权限码集合
     */
    @Override
    public List<String> getPermissionList(Object loginId, String loginType) 
        List<String> list = new ArrayList<String>();
        // 2. 遍历角色列表,查询拥有的权限码
        for (String roleId : getRoleList(loginId, loginType)) 
            SysQuery queryVo = new SysQuery();
            queryVo.setId(roleId);
            //查询角色和权限(这里根据业务自行查询)
            List<SysMenu> menuList = sysMenuService.selectPermsByRoleId(queryVo);
            List<String> collect = menuList.stream().map以上是关于SaToken使用springboot+redis+satoken权限认证的主要内容,如果未能解决你的问题,请参考以下文章

SaToken使用SpringBoot整合SaToken关于数据权限

SaToken使用SpringBoot整合SaToken关于数据权限

SaToken使用SpringBoot整合SaToken关于数据权限

Springboot 使用 SaToken 进行登录认证权限管理以及路由规则接口拦截

SaToken使用SpringBoot整合SaTokentoken自动续期+token定期刷新+注解鉴权

SaToken使用SpringBoot整合SaTokentoken自动续期+token定期刷新+注解鉴权