瀏覽代碼

集成 sa-token

Gaokun Wang 1 月之前
父節點
當前提交
59755841d5

+ 8 - 0
eco-bom/pom.xml

@@ -32,6 +32,7 @@
         <milvus.version>2.5.5</milvus.version>
         <jsqlparser.version>5.1</jsqlparser.version>
         <DmJdbcDriver18.version>8.1.3.140</DmJdbcDriver18.version>
+        <sa-token.version>1.44.0</sa-token.version>
     </properties>
 
     <!-- 全局的依赖配置-->
@@ -137,6 +138,13 @@
                 <version>${DmJdbcDriver18.version}</version>
             </dependency>
 
+            <!-- https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot-starter -->
+            <dependency>
+                <groupId>cn.dev33</groupId>
+                <artifactId>sa-token-spring-boot-starter</artifactId>
+                <version>${sa-token.version}</version>
+            </dependency>
+
         </dependencies>
     </dependencyManagement>
 

+ 39 - 0
eco-common/com-core/src/main/java/org/eco/vip/orm/factory/YmlPropertySourceFactory.java

@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.orm.factory;
+
+
+import org.eco.vip.orm.utils.StrUtils;
+import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.io.support.DefaultPropertySourceFactory;
+import org.springframework.core.io.support.EncodedResource;
+
+import java.io.IOException;
+import java.util.Objects;
+
+/**
+ * @description YmlPropertySourceFactory
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 15:43
+ */
+public class YmlPropertySourceFactory extends DefaultPropertySourceFactory {
+    @Override
+    @SuppressWarnings("NullableProblems")
+    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
+        String sourceName = resource.getResource().getFilename();
+        if (StrUtils.isNotBlank(sourceName) && StrUtils.endWithAny(sourceName, ".yml", ".yaml")) {
+            YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
+            factory.setResources(resource.getResource());
+            factory.afterPropertiesSet();
+            return new PropertiesPropertySource(sourceName, Objects.requireNonNull(factory.getObject()));
+        }
+        return super.createPropertySource(name, resource);
+    }
+
+}

+ 143 - 0
eco-common/com-core/src/main/java/org/eco/vip/orm/pojo/LoginUser.java

@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.orm.pojo;
+
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.eco.vip.orm.pojo.dto.RoleDTO;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @description LoginUser
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:13
+ */
+@Data
+@NoArgsConstructor
+public class LoginUser implements Serializable {
+
+    /**
+     * 租户ID
+     */
+    private String tenantId;
+
+    /**
+     * 用户ID
+     */
+    private String userId;
+
+    /**
+     * 组织ID
+     */
+    private String orgId;
+
+    /**
+     * 组织名
+     */
+    private String orgName;
+
+    /**
+     * 用户唯一标识
+     */
+    private String token;
+
+    /**
+     * 用户类型
+     */
+    private String userType;
+
+    /**
+     * 登录时间
+     */
+    private Long loginTime;
+
+    /**
+     * 过期时间
+     */
+    private Long expireTime;
+
+    /**
+     * 登录IP地址
+     */
+    private String ipaddr;
+
+    /**
+     * 登录地点
+     */
+    private String loginLocation;
+
+    /**
+     * 浏览器类型
+     */
+    private String browser;
+
+    /**
+     * 操作系统
+     */
+    private String os;
+
+    /**
+     * 菜单权限
+     */
+    private Set<String> menuPermission;
+
+    /**
+     * 角色权限
+     */
+    private Set<String> rolePermission;
+
+    /**
+     * 用户名
+     */
+    private String username;
+    /**
+     * 用户账号
+     */
+    private String account;
+
+    /**
+     * 用户昵称
+     */
+    private String nickname;
+
+    /**
+     * 角色对象
+     */
+    private List<RoleDTO> roles;
+
+    /**
+     * 数据权限 当前角色ID
+     */
+    private Long roleId;
+
+    /**
+     * 客户端
+     */
+    private String clientKey;
+
+    /**
+     * 设备类型
+     */
+    private String deviceType;
+
+    /**
+     * 获取登录id
+     */
+    public String getLoginId() {
+        if (userType == null) {
+            throw new IllegalArgumentException("用户类型不能为空");
+        }
+        if (userId == null) {
+            throw new IllegalArgumentException("用户ID不能为空");
+        }
+        return userType + ":" + userId;
+    }
+}

+ 16 - 0
eco-common/com-core/src/main/java/org/eco/vip/orm/pojo/dto/RoleDTO.java

@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.orm.pojo.dto;
+
+
+/**
+ * @description RoleDTO
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:14
+ */
+public class RoleDTO {
+}

+ 16 - 0
eco-common/com-core/src/main/java/org/eco/vip/orm/pojo/dto/UserOnlineDTO.java

@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.orm.pojo.dto;
+
+
+/**
+ * @description UserOnlineDTO
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:19
+ */
+public class UserOnlineDTO {
+}

+ 1 - 0
eco-common/com-orm/src/main/java/org/eco/vip/orm/decipher/Decipher.java

@@ -16,6 +16,7 @@ import com.mybatisflex.core.datasource.DataSourceProperty;
  * @date 2025/3/9 20:01
  */
 public class Decipher implements DataSourceDecipher {
+    @Override
     public String decrypt(DataSourceProperty dataSourceProperty, String value) {
 
         //解密数据源URL、用户名、密码,通过编码支持任意加密方式的解密

+ 22 - 0
eco-common/com-security/pom.xml

@@ -0,0 +1,22 @@
+<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 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.eco.vip</groupId>
+        <artifactId>eco-common</artifactId>
+        <version>${revision}</version>
+    </parent>
+    <artifactId>com-security</artifactId>
+    <name>${project.artifactId}</name>
+    <packaging>jar</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>org.eco.vip</groupId>
+            <artifactId>com-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>cn.dev33</groupId>
+            <artifactId>sa-token-spring-boot-starter</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 22 - 0
eco-common/com-security/src/main/java/org/eco/vip/security/config/SaTokenConfig.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.security.config;
+
+
+import org.eco.vip.orm.factory.YmlPropertySourceFactory;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * @description SaTokenConfig
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 15:41
+ */
+@PropertySource(value = "classpath:satoken-common.yml", factory = YmlPropertySourceFactory.class)
+@AutoConfiguration
+public class SaTokenConfig {
+}

+ 71 - 0
eco-common/com-security/src/main/java/org/eco/vip/security/config/SecurityConfig.java

@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.security.config;
+
+
+import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.interceptor.SaInterceptor;
+import cn.dev33.satoken.router.SaRouter;
+import cn.dev33.satoken.stp.StpUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.eco.vip.orm.utils.ServletUtils;
+import org.eco.vip.orm.utils.SpringUtils;
+import org.eco.vip.orm.utils.StrUtils;
+import org.eco.vip.security.config.properties.SecurityProperties;
+import org.eco.vip.security.handler.AllUrlHandler;
+import org.eco.vip.security.utils.LoginHelper;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+/**
+ * @description SecurityConfig
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:02
+ */
+@Slf4j
+@AutoConfiguration
+@EnableConfigurationProperties(SecurityProperties.class)
+@RequiredArgsConstructor
+public class SecurityConfig implements WebMvcConfigurer {
+    private final SecurityProperties securityProperties;
+
+    /**
+     * 注册 Sa-Token 路由拦截器
+     */
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        // 注册路由拦截器,自定义验证规则
+        registry.addInterceptor(new SaInterceptor(handler -> {
+                    AllUrlHandler allUrlHandler = SpringUtils.getBean(AllUrlHandler.class);
+                    // 登录验证 -- 排除多个路径
+                    SaRouter
+                            // 获取所有的
+                            .match(allUrlHandler.getUrls())  // 拦截的 path 列表
+                            .check(() -> {
+                                // 检查是否登录 是否有token
+                                StpUtil.checkLogin();
+
+                                // 检查 header 与 param 里的 clientId 与 token 里的是否一致
+                                String headerCid = ServletUtils.getRequest().getHeader(LoginHelper.CLIENT_KEY);
+                                String paramCid = ServletUtils.getParameter(LoginHelper.CLIENT_KEY);
+                                String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
+                                if (!StrUtils.equalsAny(clientId, headerCid, paramCid)) {
+                                    // token 无效
+                                    throw NotLoginException.newInstance(StpUtil.getLoginType(),
+                                            "-100", "客户端ID与Token不匹配",
+                                            StpUtil.getTokenValue());
+                                }
+
+                            });
+                })).addPathPatterns("/**")
+                // 排除不需要拦截的路径
+                .excludePathPatterns(securityProperties.getExcludes());
+    }
+}

+ 25 - 0
eco-common/com-security/src/main/java/org/eco/vip/security/config/properties/SecurityProperties.java

@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.security.config.properties;
+
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * @description SecurityProperties
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:01
+ */
+@Data
+@ConfigurationProperties(prefix = "security")
+public class SecurityProperties {
+    /**
+     * 排除路径
+     */
+    private String[] excludes;
+}

+ 52 - 0
eco-common/com-security/src/main/java/org/eco/vip/security/handler/AllUrlHandler.java

@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.security.handler;
+
+
+import cn.hutool.core.util.ReUtil;
+import lombok.Data;
+import org.eco.vip.orm.utils.SpringUtils;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.web.method.HandlerMethod;
+import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * @description AllUrlHandler
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:03
+ */
+@Data
+public class AllUrlHandler implements InitializingBean {
+
+    private static final Pattern PATTERN = Pattern.compile("\\{(.*?)}");
+
+    private List<String> urls = new ArrayList<>();
+
+    @Override
+    public void afterPropertiesSet() {
+        Set<String> set = new HashSet<>();
+        RequestMappingHandlerMapping mapping = SpringUtils.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
+        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
+        map.keySet().forEach(info -> {
+            // 获取注解上边的 path 替代 path variable 为 *
+            if (info.getPathPatternsCondition() != null) {
+                Objects.requireNonNull(info.getPathPatternsCondition().getPatterns())
+                        .forEach(url -> set.add(ReUtil.replaceAll(url.getPatternString(), PATTERN, "*")));
+            }
+        });
+        urls.addAll(set);
+    }
+}

+ 60 - 0
eco-common/com-security/src/main/java/org/eco/vip/security/utils/LoginHelper.java

@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2025 GaoKunW
+ *
+ */
+
+package org.eco.vip.security.utils;
+
+
+import cn.dev33.satoken.context.SaHolder;
+import cn.dev33.satoken.context.model.SaStorage;
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.util.ObjectUtil;
+import lombok.AccessLevel;
+import lombok.NoArgsConstructor;
+import org.eco.vip.orm.pojo.LoginUser;
+
+/**
+ * @description LoginHelper
+ *
+ * @author GaoKunW
+ * @date 2025/7/2 16:10
+ */
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+public class LoginHelper {
+    public static final String LOGIN_USER_KEY = "loginUser";
+    public static final String TENANT_KEY = "tenantId";
+    public static final String USER_KEY = "userId";
+    public static final String USER_NAME_KEY = "userName";
+    public static final String USER_ACCOUNT_KEY = "account";
+    public static final String ORG_KEY = "orgId";
+    public static final String ORG_NAME_KEY = "orgName";
+    public static final String CLIENT_KEY = "clientId";
+
+    /**
+     * 登录系统 基于 设备类型
+     * 针对相同用户体系不同设备
+     *
+     * @param loginUser 登录用户信息
+     * @param model     配置参数
+     */
+    public static void login(LoginUser loginUser, SaLoginParameter model) {
+        SaStorage storage = SaHolder.getStorage();
+        storage.set(LOGIN_USER_KEY, loginUser);
+        storage.set(TENANT_KEY, loginUser.getTenantId());
+        storage.set(USER_KEY, loginUser.getUserId());
+        storage.set(ORG_KEY, loginUser.getOrgId());
+        model = ObjectUtil.defaultIfNull(model, new SaLoginParameter());
+        // 登录,生成token
+        StpUtil.login(loginUser.getLoginId(),
+                model.setExtra(TENANT_KEY, loginUser.getTenantId())
+                        .setExtra(USER_KEY, loginUser.getUserId())
+                        .setExtra(USER_NAME_KEY, loginUser.getUsername())
+                        .setExtra(USER_ACCOUNT_KEY, loginUser.getAccount())
+                        .setExtra(ORG_KEY, loginUser.getOrgId())
+                        .setExtra(ORG_NAME_KEY, loginUser.getOrgName())
+        );
+        StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
+    }
+}

+ 1 - 0
eco-common/com-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -0,0 +1 @@
+

+ 12 - 0
eco-common/com-security/src/main/resources/satoken-common.yml

@@ -0,0 +1,12 @@
+# Sa-Token配置 默认配置 勿动
+sa-token:
+  # 允许动态设置 token 有效期
+  dynamic-active-timeout: true
+  # 允许从 请求参数 读取 token
+  is-read-body: true
+  # 允许从 header 读取 token
+  is-read-header: true
+  # 关闭 cookie 鉴权 从根源杜绝 csrf 漏洞风险
+  is-read-cookie: false
+  # token前缀
+  token-prefix: "Bearer"

+ 1 - 0
eco-common/pom.xml

@@ -16,5 +16,6 @@
         <module>com-web</module>
         <module>com-core</module>
         <module>com-orm</module>
+        <module>com-security</module>
     </modules>
 </project>

+ 21 - 0
eco-start/src/main/resources/application.yml

@@ -43,6 +43,27 @@ server:
     # 应用的访问路径
     context-path: /
 
+
+# Sa-Token配置
+sa-token:
+  # token名称 (同时也是cookie名称)
+  token-name: Authorization
+  # token固定超时 设为七天 (必定过期) 单位: 秒
+  timeout: 604800
+  # 多端不同 token 有效期 可查看 LoginHelper.loginByDevice 方法自定义
+  # token最低活跃时间 (指定时间无操作就过期) 单位: 秒
+  active-timeout: 1800
+  # 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
+  is-concurrent: true
+  # 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
+  is-share: false
+  # token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
+  token-style: uuid
+  # 是否输出操作日志
+  is-log: false
+  # jwt秘钥
+  jwt-secret-key: uWqxTNKHjmIfDohOgZCGwElMdJ
+
 # MyBatisFlex公共配置
 mybatis-flex:
   # 搜索指定包别名