auth
This commit is contained in:
parent
74064a4dc4
commit
4a0b1499c7
@ -24,6 +24,11 @@
|
||||
<artifactId>wol-module-ai</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.agileboot</groupId>
|
||||
<artifactId>wol-auth</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
<module>wol-common-mybatis</module>
|
||||
<module>wol-common-redis</module>
|
||||
<module>wol-common-json</module>
|
||||
<module>wol-common-satoken</module>
|
||||
</modules>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
|
||||
@ -47,6 +47,11 @@
|
||||
<artifactId>wol-common-json</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.agileboot</groupId>
|
||||
<artifactId>wol-common-satoken</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
@ -85,6 +85,10 @@
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
</dependency>
|
||||
<!--ENC加密-->
|
||||
<dependency>
|
||||
<groupId>com.github.ulisesbocchio</groupId>
|
||||
|
||||
@ -45,11 +45,13 @@ public interface Constants {
|
||||
* 通用成功标识
|
||||
*/
|
||||
String SUCCESS = "0";
|
||||
String NORMAL = "0"; // 正常状态
|
||||
|
||||
/**
|
||||
* 通用失败标识
|
||||
*/
|
||||
String FAIL = "1";
|
||||
String DISABLE = "1"; // 异常/停用状态
|
||||
|
||||
/**
|
||||
* 登录成功
|
||||
@ -80,6 +82,45 @@ public interface Constants {
|
||||
* 顶级部门id
|
||||
*/
|
||||
Long TOP_PARENT_ID = 0L;
|
||||
/**
|
||||
* 超级管理员ID
|
||||
*/
|
||||
Long SUPER_ADMIN_ID = 1L;
|
||||
/**
|
||||
* 超级管理员角色 roleKey
|
||||
*/
|
||||
String SUPER_ADMIN_ROLE_KEY = "superadmin";
|
||||
|
||||
/**
|
||||
* 租户管理员角色 roleKey
|
||||
*/
|
||||
String TENANT_ADMIN_ROLE_KEY = "admin";
|
||||
|
||||
/**
|
||||
* 租户管理员角色名称
|
||||
*/
|
||||
String TENANT_ADMIN_ROLE_NAME = "管理员";
|
||||
|
||||
/**
|
||||
* 默认租户ID
|
||||
*/
|
||||
String DEFAULT_TENANT_ID = "000000";
|
||||
|
||||
interface Cache {
|
||||
/**
|
||||
* 全局 redis key (业务无关的key)
|
||||
*/
|
||||
String GLOBAL_REDIS_KEY = "global:";
|
||||
|
||||
/**
|
||||
* 登录账户密码错误次数 redis key
|
||||
*/
|
||||
String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
|
||||
/**
|
||||
* 验证码 redis key
|
||||
*/
|
||||
String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
package com.agileboot.common.core.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 登录类型
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum LoginType {
|
||||
|
||||
/**
|
||||
* 密码登录
|
||||
*/
|
||||
PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
|
||||
|
||||
/**
|
||||
* 短信登录
|
||||
*/
|
||||
SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
|
||||
|
||||
/**
|
||||
* 邮箱登录
|
||||
*/
|
||||
EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
|
||||
|
||||
/**
|
||||
* 小程序登录
|
||||
*/
|
||||
XCX("", "");
|
||||
|
||||
/**
|
||||
* 登录重试超出限制提示
|
||||
*/
|
||||
final String retryLimitExceed;
|
||||
|
||||
/**
|
||||
* 登录重试限制计数提示
|
||||
*/
|
||||
final String retryLimitCount;
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.agileboot.common.core.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* 用户类型
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum UserType {
|
||||
|
||||
/**
|
||||
* 后台系统用户
|
||||
*/
|
||||
SYS_USER("sys_user"),
|
||||
|
||||
/**
|
||||
* 移动客户端用户
|
||||
*/
|
||||
APP_USER("app_user");
|
||||
|
||||
/**
|
||||
* 用户类型标识(用于 token、权限识别等)
|
||||
*/
|
||||
private final String userType;
|
||||
|
||||
public static UserType getUserType(String str) {
|
||||
for (UserType value : values()) {
|
||||
if (StringUtils.contains(str, value.getUserType())) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("'UserType' not found By " + str);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.agileboot.common.core.utils;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.validation.ConstraintViolation;
|
||||
import jakarta.validation.ConstraintViolationException;
|
||||
import jakarta.validation.Validator;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Validator 校验框架工具
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class ValidatorUtils {
|
||||
|
||||
private static final Validator VALID = SpringUtil.getBean(Validator.class);
|
||||
|
||||
/**
|
||||
* 对给定对象进行参数校验,并根据指定的校验组进行校验
|
||||
*
|
||||
* @param object 要进行校验的对象
|
||||
* @param groups 校验组
|
||||
* @throws ConstraintViolationException 如果校验不通过,则抛出参数校验异常
|
||||
*/
|
||||
public static <T> void validate(T object, Class<?>... groups) {
|
||||
Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
|
||||
if (!validate.isEmpty()) {
|
||||
throw new ConstraintViolationException("参数校验异常", validate);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -39,6 +39,10 @@
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.agileboot</groupId>
|
||||
<artifactId>wol-common-satoken</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package com.agileboot.common.mybatis.config;
|
||||
|
||||
import com.agileboot.common.core.factory.YmlPropertySourceFactory;
|
||||
import com.agileboot.common.mybatis.handler.InjectionMetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
|
||||
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
|
||||
@ -29,4 +31,12 @@ public class MybatisPlusConfiguration {
|
||||
return interceptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* 元对象字段填充控制器
|
||||
*/
|
||||
@Bean
|
||||
public MetaObjectHandler metaObjectHandler() {
|
||||
return new InjectionMetaObjectHandler();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -22,9 +22,6 @@ public class BaseEntity implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 搜索值
|
||||
*/
|
||||
|
||||
@ -0,0 +1,110 @@
|
||||
package com.agileboot.common.mybatis.handler;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import com.agileboot.common.core.exception.BizException;
|
||||
import com.agileboot.common.mybatis.core.domain.BaseEntity;
|
||||
import com.agileboot.common.satoken.pojo.LoginUser;
|
||||
import com.agileboot.common.satoken.utils.LoginHelper;
|
||||
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* MP注入处理器
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
public class InjectionMetaObjectHandler implements MetaObjectHandler {
|
||||
|
||||
/**
|
||||
* 如果用户不存在默认注入-1代表无用户
|
||||
*/
|
||||
private static final Long DEFAULT_USER_ID = -1L;
|
||||
|
||||
/**
|
||||
* 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息
|
||||
*
|
||||
* @param metaObject 元对象,用于获取原始对象并进行填充
|
||||
*/
|
||||
@Override
|
||||
public void insertFill(MetaObject metaObject) {
|
||||
try {
|
||||
if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
|
||||
Date current = ObjectUtils.defaultIfNull(baseEntity.getCreateTime(), new Date());
|
||||
baseEntity.setCreateTime(current);
|
||||
baseEntity.setUpdateTime(current);
|
||||
baseEntity.setDeleted(0);
|
||||
|
||||
// 如果创建人为空,则填充当前登录用户的信息
|
||||
if (ObjectUtil.isNull(baseEntity.getCreateBy())) {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
if (ObjectUtil.isNotNull(loginUser)) {
|
||||
Long userId = loginUser.getUserId();
|
||||
// 填充创建人、更新人和创建部门信息
|
||||
baseEntity.setCreateBy(userId);
|
||||
baseEntity.setUpdateBy(userId);
|
||||
} else {
|
||||
// 填充创建人、更新人和创建部门信息
|
||||
baseEntity.setCreateBy(DEFAULT_USER_ID);
|
||||
baseEntity.setUpdateBy(DEFAULT_USER_ID);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Date date = new Date();
|
||||
this.strictInsertFill(metaObject, "createTime", Date.class, date);
|
||||
this.strictInsertFill(metaObject, "updateTime", Date.class, date);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new BizException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新填充方法,用于在更新数据时自动填充实体对象中的更新时间和更新人信息
|
||||
*
|
||||
* @param metaObject 元对象,用于获取原始对象并进行填充
|
||||
*/
|
||||
@Override
|
||||
public void updateFill(MetaObject metaObject) {
|
||||
try {
|
||||
if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
|
||||
// 获取当前时间作为更新时间,无论原始对象中的更新时间是否为空都填充
|
||||
Date current = new Date();
|
||||
baseEntity.setUpdateTime(current);
|
||||
|
||||
// 获取当前登录用户的ID,并填充更新人信息
|
||||
Long userId = LoginHelper.getUserId();
|
||||
if (ObjectUtil.isNotNull(userId)) {
|
||||
baseEntity.setUpdateBy(userId);
|
||||
} else {
|
||||
baseEntity.setUpdateBy(DEFAULT_USER_ID);
|
||||
}
|
||||
} else {
|
||||
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new BizException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*
|
||||
* @return 当前登录用户的信息,如果用户未登录则返回 null
|
||||
*/
|
||||
private LoginUser getLoginUser() {
|
||||
LoginUser loginUser;
|
||||
try {
|
||||
loginUser = LoginHelper.getLoginUser();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
}
|
||||
@ -27,7 +27,10 @@
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
<artifactId>caffeine</artifactId>
|
||||
|
||||
@ -42,7 +42,7 @@ public class RedissonConfiguration {
|
||||
@Autowired
|
||||
private RedissonProperties redissonProperties;
|
||||
|
||||
@Bean
|
||||
// @Bean
|
||||
public RedissonAutoConfigurationCustomizer redissonCustomizer() {
|
||||
return config -> {
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
|
||||
49
agileboot-common/wol-common-satoken/pom.xml
Normal file
49
agileboot-common/wol-common-satoken/pom.xml
Normal file
@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.agileboot</groupId>
|
||||
<artifactId>agileboot-common</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>wol-common-satoken</artifactId>
|
||||
|
||||
<properties>
|
||||
<satoken.version>1.44.0</satoken.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.agileboot</groupId>
|
||||
<artifactId>wol-common-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
<version>${satoken.version}</version>
|
||||
</dependency>
|
||||
<!-- Sa-Token 整合 jwt -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-jwt</artifactId>
|
||||
<version>${satoken.version}</version>
|
||||
</dependency>
|
||||
<!-- Sa-Token 整合 RedisTemplate -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>cn.dev33</groupId>-->
|
||||
<!-- <artifactId>sa-token-redis-template</artifactId>-->
|
||||
<!-- <version>${satoken.version}</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- 提供 Redis 连接池 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.apache.commons</groupId>-->
|
||||
<!-- <artifactId>commons-pool2</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,52 @@
|
||||
package com.agileboot.common.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import com.agileboot.common.core.factory.YmlPropertySourceFactory;
|
||||
import com.agileboot.common.satoken.handler.SaTokenExceptionHandler;
|
||||
import com.agileboot.common.satoken.service.SaPermissionImpl;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
/**
|
||||
* Sa-Token 配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@PropertySource(value = "classpath:common-satoken.yml", factory = YmlPropertySourceFactory.class)
|
||||
public class SaTokenConfiguration {
|
||||
|
||||
// Sa-Token 整合 jwt (Simple 简单模式)
|
||||
@Bean
|
||||
public StpLogic getStpLogicJwt() {
|
||||
return new StpLogicJwtForSimple();
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限接口实现(使用bean注入方便用户替换)
|
||||
*/
|
||||
@Bean
|
||||
public StpInterface stpInterface() {
|
||||
return new SaPermissionImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义dao层存储
|
||||
*/
|
||||
// @Bean
|
||||
// public SaTokenDao saTokenDao() {
|
||||
// return new PlusSaTokenDao();
|
||||
// }
|
||||
|
||||
/**
|
||||
* 异常处理器
|
||||
*/
|
||||
@Bean
|
||||
public SaTokenExceptionHandler saTokenExceptionHandler() {
|
||||
return new SaTokenExceptionHandler();
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
package com.agileboot.common.satoken.config;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.same.SaSameUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import com.agileboot.common.core.constant.HttpStatus;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
/**
|
||||
* 权限安全配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@AutoConfiguration
|
||||
public class SaTokenMvcConfiguration implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 注册sa-token的拦截器
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册路由拦截器,自定义验证规则
|
||||
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验是否从网关转发
|
||||
*/
|
||||
// @Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
.addInclude("/**")
|
||||
.addExclude("/actuator", "/actuator/**")
|
||||
.setAuth(obj -> {
|
||||
if (SaManager.getConfig().getCheckSameToken()) {
|
||||
SaSameUtil.checkCurrentRequestToken();
|
||||
}
|
||||
})
|
||||
.setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
|
||||
}
|
||||
|
||||
/**
|
||||
* 对 actuator 健康检查接口 做账号密码鉴权
|
||||
*/
|
||||
// @Bean
|
||||
// public SaServletFilter actuatorFilter() {
|
||||
// String username = SpringUtil.getProperty("spring.cloud.nacos.discovery.metadata.username");
|
||||
// String password = SpringUtil.getProperty("spring.cloud.nacos.discovery.metadata.userpassword");
|
||||
// return new SaServletFilter()
|
||||
// .addInclude("/actuator", "/actuator/**")
|
||||
// .setAuth(obj -> {
|
||||
// SaHttpBasicUtil.check(username + ":" + password);
|
||||
// })
|
||||
// .setError(e -> SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED));
|
||||
// }
|
||||
|
||||
}
|
||||
@ -0,0 +1,52 @@
|
||||
package com.agileboot.common.satoken.handler;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
import cn.hutool.http.HttpStatus;
|
||||
import com.agileboot.common.core.core.R;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* SaToken异常处理器
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class SaTokenExceptionHandler {
|
||||
|
||||
/**
|
||||
* 权限码异常
|
||||
*/
|
||||
@ExceptionHandler(NotPermissionException.class)
|
||||
public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
|
||||
return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
|
||||
}
|
||||
|
||||
/**
|
||||
* 角色权限异常
|
||||
*/
|
||||
@ExceptionHandler(NotRoleException.class)
|
||||
public R<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
|
||||
return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证失败
|
||||
*/
|
||||
@ExceptionHandler(NotLoginException.class)
|
||||
public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
|
||||
String requestURI = request.getRequestURI();
|
||||
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
|
||||
return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源");
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,151 @@
|
||||
package com.agileboot.common.satoken.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 用户信息
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class LoginUser implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 部门类别编码
|
||||
*/
|
||||
private String deptCategory;
|
||||
|
||||
/**
|
||||
* 部门名
|
||||
*/
|
||||
private String deptName;
|
||||
|
||||
/**
|
||||
* 用户唯一标识
|
||||
*/
|
||||
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 nickname;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 角色对象
|
||||
*/
|
||||
private List<RoleDTO> roles;
|
||||
|
||||
/**
|
||||
* 岗位对象
|
||||
*/
|
||||
private List<PostDTO> posts;
|
||||
|
||||
/**
|
||||
* 数据权限 当前角色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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package com.agileboot.common.satoken.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 岗位
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class PostDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 岗位ID
|
||||
*/
|
||||
private Long postId;
|
||||
|
||||
/**
|
||||
* 部门id
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 岗位编码
|
||||
*/
|
||||
private String postCode;
|
||||
|
||||
/**
|
||||
* 岗位名称
|
||||
*/
|
||||
private String postName;
|
||||
|
||||
/**
|
||||
* 岗位类别编码
|
||||
*/
|
||||
private String postCategory;
|
||||
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
package com.agileboot.common.satoken.pojo;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 角色
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class RoleDTO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
private Long roleId;
|
||||
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
private String roleName;
|
||||
|
||||
/**
|
||||
* 角色权限
|
||||
*/
|
||||
private String roleKey;
|
||||
|
||||
/**
|
||||
* 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限 6:部门及以下或本人数据权限)
|
||||
*/
|
||||
private String dataScope;
|
||||
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
package com.agileboot.common.satoken.service;
|
||||
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* sa-token 权限管理实现类
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public class SaPermissionImpl implements StpInterface {
|
||||
|
||||
/**
|
||||
* 获取菜单权限列表
|
||||
*/
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取角色权限列表
|
||||
*/
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,216 @@
|
||||
package com.agileboot.common.satoken.utils;
|
||||
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.agileboot.common.core.constant.Constants;
|
||||
import com.agileboot.common.core.enums.UserType;
|
||||
import com.agileboot.common.satoken.pojo.LoginUser;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* 登录鉴权助手
|
||||
* <p>
|
||||
* user_type 为 用户类型 同一个用户表 可以有多种用户类型 例如 pc,app
|
||||
* deivce 为 设备类型 同一个用户类型 可以有 多种设备类型 例如 web,ios
|
||||
* 可以组成 用户类型与设备类型多对多的 权限灵活控制
|
||||
* <p>
|
||||
* 多用户体系 针对 多种用户类型 但权限控制不一致
|
||||
* 可以组成 多用户类型表与多设备类型 分别控制权限
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@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 DEPT_KEY = "deptId";
|
||||
public static final String DEPT_NAME_KEY = "deptName";
|
||||
public static final String DEPT_CATEGORY_KEY = "deptCategory";
|
||||
public static final String CLIENT_KEY = "clientid";
|
||||
|
||||
/**
|
||||
* 登录系统 基于 设备类型
|
||||
* 针对相同用户体系不同设备
|
||||
*
|
||||
* @param loginUser 登录用户信息
|
||||
* @param model 配置参数
|
||||
*/
|
||||
public static void login(LoginUser loginUser, SaLoginParameter model) {
|
||||
model = ObjectUtil.defaultIfNull(model, new SaLoginParameter());
|
||||
StpUtil.login(loginUser.getLoginId(),
|
||||
model.setExtra(TENANT_KEY, loginUser.getTenantId())
|
||||
.setExtra(USER_KEY, loginUser.getUserId())
|
||||
.setExtra(USER_NAME_KEY, loginUser.getUsername())
|
||||
.setExtra(DEPT_KEY, loginUser.getDeptId())
|
||||
.setExtra(DEPT_NAME_KEY, loginUser.getDeptName())
|
||||
.setExtra(DEPT_CATEGORY_KEY, loginUser.getDeptCategory())
|
||||
);
|
||||
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户(多级缓存)
|
||||
*/
|
||||
@SuppressWarnings("unchecked cast")
|
||||
public static <T extends LoginUser> T getLoginUser() {
|
||||
SaSession session = StpUtil.getTokenSession();
|
||||
if (ObjectUtil.isNull(session)) {
|
||||
return null;
|
||||
}
|
||||
return (T) session.get(LOGIN_USER_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户基于token
|
||||
*/
|
||||
@SuppressWarnings("unchecked cast")
|
||||
public static <T extends LoginUser> T getLoginUser(String token) {
|
||||
SaSession session = StpUtil.getTokenSessionByToken(token);
|
||||
if (ObjectUtil.isNull(session)) {
|
||||
return null;
|
||||
}
|
||||
return (T) session.get(LOGIN_USER_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户id
|
||||
*/
|
||||
public static Long getUserId() {
|
||||
return Convert.toLong(getExtra(USER_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户id
|
||||
*/
|
||||
public static String getUserIdStr() {
|
||||
return Convert.toStr(getExtra(USER_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户账户
|
||||
*/
|
||||
public static String getUsername() {
|
||||
return Convert.toStr(getExtra(USER_NAME_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户ID
|
||||
*/
|
||||
public static String getTenantId() {
|
||||
return Convert.toStr(getExtra(TENANT_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门ID
|
||||
*/
|
||||
public static Long getDeptId() {
|
||||
return Convert.toLong(getExtra(DEPT_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门名
|
||||
*/
|
||||
public static String getDeptName() {
|
||||
return Convert.toStr(getExtra(DEPT_NAME_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门类别编码
|
||||
*/
|
||||
public static String getDeptCategory() {
|
||||
return Convert.toStr(getExtra(DEPT_CATEGORY_KEY));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 Token 的扩展信息
|
||||
*
|
||||
* @param key 键值
|
||||
* @return 对应的扩展数据
|
||||
*/
|
||||
private static Object getExtra(String key) {
|
||||
try {
|
||||
return StpUtil.getExtra(key);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户类型
|
||||
*/
|
||||
public static UserType getUserType() {
|
||||
String loginType = StpUtil.getLoginIdAsString();
|
||||
return UserType.getUserType(loginType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为超级管理员
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isSuperAdmin(Long userId) {
|
||||
return Constants.SUPER_ADMIN_ID.equals(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为超级管理员
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isSuperAdmin() {
|
||||
return isSuperAdmin(getUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为租户管理员
|
||||
*
|
||||
* @param rolePermission 角色权限标识组
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isTenantAdmin(Set<String> rolePermission) {
|
||||
if (CollectionUtils.isEmpty(rolePermission)) {
|
||||
return false;
|
||||
}
|
||||
return rolePermission.contains(Constants.TENANT_ADMIN_ROLE_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为租户管理员
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isTenantAdmin() {
|
||||
LoginUser loginUser = getLoginUser();
|
||||
if (loginUser == null) {
|
||||
return false;
|
||||
}
|
||||
return Convert.toBool(isTenantAdmin(loginUser.getRolePermission()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前用户是否已登录
|
||||
*
|
||||
* @return 结果
|
||||
*/
|
||||
public static boolean isLogin() {
|
||||
try {
|
||||
StpUtil.checkLogin();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,2 @@
|
||||
com.agileboot.common.satoken.config.SaTokenConfiguration
|
||||
com.agileboot.common.satoken.config.SaTokenMvcConfiguration
|
||||
@ -0,0 +1,13 @@
|
||||
# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖
|
||||
# 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"
|
||||
@ -5,6 +5,7 @@ import com.agileboot.common.web.handler.GlobalExceptionHandler;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.format.FormatterRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
@ -14,10 +15,12 @@ import java.util.Date;
|
||||
|
||||
/**
|
||||
* 通用配置
|
||||
* 注解EnableAspectJAutoProxy 表示通过aop框架暴露该代理对象,AopContext能够访问
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@AutoConfiguration
|
||||
@EnableAspectJAutoProxy(exposeProxy = true)
|
||||
public class ResourcesConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
package com.agileboot.system.config.pojo.entity;
|
||||
|
||||
import com.agileboot.common.mybatis.core.domain.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
@ -27,6 +29,9 @@ public class SysConfig extends BaseEntity {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
@ApiModelProperty("配置名称")
|
||||
@TableField("config_name")
|
||||
private String configName;
|
||||
|
||||
@ -1,105 +0,0 @@
|
||||
package com.agileboot.system.login.controller;
|
||||
|
||||
import com.agileboot.common.core.core.R;
|
||||
import com.agileboot.common.core.exception.BizException;
|
||||
import com.agileboot.common.core.exception.error.ErrorCode;
|
||||
import com.agileboot.system.config.pojo.dto.ConfigDTO;
|
||||
import com.agileboot.system.login.pojo.vo.CaptchaVO;
|
||||
import com.agileboot.system.login.service.LoginService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 登录相关接口
|
||||
*
|
||||
* @author valarchie
|
||||
*/
|
||||
@RestController
|
||||
@RequiredArgsConstructor
|
||||
public class LoginController {
|
||||
|
||||
private final LoginService loginService;
|
||||
|
||||
private final MenuAService menuService;
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
/**
|
||||
* 获取系统的内置配置
|
||||
*
|
||||
* @return 配置信息
|
||||
*/
|
||||
@GetMapping("/getConfig")
|
||||
public R<ConfigDTO> getConfig() {
|
||||
return R.ok(loginService.getConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
@GetMapping("/captchaImage")
|
||||
public R<CaptchaVO> getCaptchaImg() {
|
||||
return R.ok(loginService.generateCaptchaImg());
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录方法
|
||||
*
|
||||
* @param loginCommand 登录信息
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public R<TokenDTO> login(@RequestBody LoginCommand loginCommand) {
|
||||
// 生成令牌
|
||||
String token = loginService.login(loginCommand);
|
||||
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||
CurrentLoginUserDTO currentUserDTO = userService.getLoginUserInfo(loginUser);
|
||||
|
||||
return R.ok(new TokenDTO(token, currentUserDTO));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*
|
||||
* @return 用户信息
|
||||
*/
|
||||
@GetMapping("/getLoginUserInfo")
|
||||
public R<CurrentLoginUserDTO> getLoginUserInfo() {
|
||||
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||
|
||||
CurrentLoginUserDTO currentUserDTO = userService.getLoginUserInfo(loginUser);
|
||||
|
||||
return R.ok(currentUserDTO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户对应的菜单路由 用于动态生成路由
|
||||
* TODO 如果要在前端开启路由缓存的话 需要在ServerConfig.json 中 设置CachingAsyncRoutes=true 避免一直重复请求路由接口
|
||||
*
|
||||
* @return 路由信息
|
||||
*/
|
||||
@GetMapping("/getRouters")
|
||||
public R<List<RouterDTO>> getRouters() {
|
||||
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||
List<RouterDTO> routerTree = menuService.getRouterTree(loginUser);
|
||||
return R.ok(routerTree);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 注册接口
|
||||
*
|
||||
* @param command
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
public R<Void> register(@RequestBody AddUserCommand command) {
|
||||
throw new BizException(ErrorCode.Business.COMMON_UNSUPPORTED_OPERATION);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
package com.agileboot.system.login.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author valarchie
|
||||
*/
|
||||
@Data
|
||||
public class CaptchaVO {
|
||||
|
||||
private Boolean isCaptchaOn;
|
||||
private String captchaCodeKey;
|
||||
private String captchaCodeImg;
|
||||
|
||||
}
|
||||
@ -1,205 +0,0 @@
|
||||
package com.agileboot.system.login.service;
|
||||
|
||||
import cn.hutool.core.codec.Base64;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.img.ImgUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.crypto.asymmetric.KeyType;
|
||||
import cn.hutool.extra.servlet.ServletUtil;
|
||||
import com.agileboot.common.core.config.AgileBootConfig;
|
||||
import com.agileboot.common.core.enums.common.LoginStatusEnum;
|
||||
import com.agileboot.common.core.exception.BizException;
|
||||
import com.agileboot.common.core.exception.error.ErrorCode;
|
||||
import com.agileboot.common.core.utils.ServletHolderUtil;
|
||||
import com.agileboot.system.config.pojo.dto.ConfigDTO;
|
||||
import com.agileboot.system.enums.ConfigKeyEnum;
|
||||
import com.agileboot.system.login.pojo.vo.CaptchaVO;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.FastByteArrayOutputStream;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class LoginService {
|
||||
|
||||
private final TokenService tokenService;
|
||||
|
||||
private final RedisCacheService redisCache;
|
||||
|
||||
private final GuavaCacheService guavaCache;
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
@Resource(name = "captchaProducer")
|
||||
private Producer captchaProducer;
|
||||
|
||||
@Resource(name = "captchaProducerMath")
|
||||
private Producer captchaProducerMath;
|
||||
|
||||
/**
|
||||
* 登录验证
|
||||
*
|
||||
* @param loginCommand 登录参数
|
||||
* @return 结果
|
||||
*/
|
||||
public String login(LoginCommand loginCommand) {
|
||||
// 验证码开关
|
||||
if (isCaptchaOn()) {
|
||||
validateCaptcha(loginCommand.getUsername(), loginCommand.getCaptchaCode(), loginCommand.getCaptchaCodeKey());
|
||||
}
|
||||
// 用户验证
|
||||
Authentication authentication;
|
||||
String decryptPassword = decryptPassword(loginCommand.getPassword());
|
||||
try {
|
||||
// 该方法会去调用UserDetailsServiceImpl#loadUserByUsername 校验用户名和密码 认证鉴权
|
||||
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
|
||||
loginCommand.getUsername(), decryptPassword));
|
||||
} catch (BadCredentialsException e) {
|
||||
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL,
|
||||
MessageUtils.message("Business.LOGIN_WRONG_USER_PASSWORD")));
|
||||
throw new ApiException(e, ErrorCode.Business.LOGIN_WRONG_USER_PASSWORD);
|
||||
} catch (AuthenticationException e) {
|
||||
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, e.getMessage()));
|
||||
throw new ApiException(e, ErrorCode.Business.LOGIN_ERROR, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, e.getMessage()));
|
||||
throw new ApiException(e, Business.LOGIN_ERROR, e.getMessage());
|
||||
}
|
||||
// 把当前登录用户 放入上下文中
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
// 这里获取的loginUser是UserDetailsServiceImpl#loadUserByUsername方法返回的LoginUser
|
||||
SystemLoginUser loginUser = (SystemLoginUser) authentication.getPrincipal();
|
||||
recordLoginInfo(loginUser);
|
||||
// 生成token
|
||||
return tokenService.createTokenAndPutUserInCache(loginUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码 data
|
||||
*
|
||||
* @return {@link ConfigDTO}
|
||||
*/
|
||||
public ConfigDTO getConfig() {
|
||||
ConfigDTO configDTO = new ConfigDTO();
|
||||
|
||||
boolean isCaptchaOn = isCaptchaOn();
|
||||
configDTO.setIsCaptchaOn(isCaptchaOn);
|
||||
configDTO.setDictionary(MapCache.dictionaryCache());
|
||||
return configDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码 data
|
||||
*
|
||||
* @return 验证码
|
||||
*/
|
||||
public CaptchaVO generateCaptchaImg() {
|
||||
CaptchaVO captchaVO = new CaptchaVO();
|
||||
|
||||
boolean isCaptchaOn = isCaptchaOn();
|
||||
captchaVO.setIsCaptchaOn(isCaptchaOn);
|
||||
|
||||
if (isCaptchaOn) {
|
||||
String expression;
|
||||
String answer = null;
|
||||
BufferedImage image = null;
|
||||
|
||||
// 生成验证码
|
||||
String captchaType = AgileBootConfig.getCaptchaType();
|
||||
if (Captcha.MATH_TYPE.equals(captchaType)) {
|
||||
String capText = captchaProducerMath.createText();
|
||||
String[] expressionAndAnswer = capText.split("@");
|
||||
expression = expressionAndAnswer[0];
|
||||
answer = expressionAndAnswer[1];
|
||||
image = captchaProducerMath.createImage(expression);
|
||||
}
|
||||
|
||||
if (Captcha.CHAR_TYPE.equals(captchaType)) {
|
||||
expression = answer = captchaProducer.createText();
|
||||
image = captchaProducer.createImage(expression);
|
||||
}
|
||||
|
||||
if (image == null) {
|
||||
throw new BizException(ErrorCode.Internal.LOGIN_CAPTCHA_GENERATE_FAIL);
|
||||
}
|
||||
|
||||
// 保存验证码信息
|
||||
String imgKey = IdUtil.simpleUUID();
|
||||
|
||||
redisCache.captchaCache.set(imgKey, answer);
|
||||
// 转换流信息写出
|
||||
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
|
||||
ImgUtil.writeJpg(image, os);
|
||||
|
||||
captchaVO.setCaptchaCodeKey(imgKey);
|
||||
captchaVO.setCaptchaCodeImg(Base64.encode(os.toByteArray()));
|
||||
|
||||
}
|
||||
|
||||
return captchaVO;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param captchaCode 验证码
|
||||
* @param captchaCodeKey 验证码对应的缓存key
|
||||
*/
|
||||
public void validateCaptcha(String username, String captchaCode, String captchaCodeKey) {
|
||||
String captcha = redisCache.captchaCache.getObjectById(captchaCodeKey);
|
||||
redisCache.captchaCache.delete(captchaCodeKey);
|
||||
if (captcha == null) {
|
||||
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.LOGIN_FAIL,
|
||||
ErrorCode.Business.LOGIN_CAPTCHA_CODE_EXPIRE.message()));
|
||||
throw new BizException(ErrorCode.Business.LOGIN_CAPTCHA_CODE_EXPIRE);
|
||||
}
|
||||
if (!captchaCode.equalsIgnoreCase(captcha)) {
|
||||
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.LOGIN_FAIL,
|
||||
ErrorCode.Business.LOGIN_CAPTCHA_CODE_WRONG.message()));
|
||||
throw new BizException(ErrorCode.Business.LOGIN_CAPTCHA_CODE_WRONG);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录信息
|
||||
* @param loginUser 登录用户
|
||||
*/
|
||||
public void recordLoginInfo(SystemLoginUser loginUser) {
|
||||
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginUser.getUsername(), LoginStatusEnum.LOGIN_SUCCESS,
|
||||
LoginStatusEnum.LOGIN_SUCCESS.getDesc()));
|
||||
|
||||
SysUserEntity entity = redisCache.userCache.getObjectById(loginUser.getUserId());
|
||||
|
||||
entity.setLoginIp(ServletUtil.getClientIP(ServletHolderUtil.getRequest()));
|
||||
entity.setLoginDate(DateUtil.date());
|
||||
entity.updateById();
|
||||
}
|
||||
|
||||
public String decryptPassword(String originalPassword) {
|
||||
byte[] decryptBytes = SecureUtil.rsa(AgileBootConfig.getRsaPrivateKey(), null)
|
||||
.decrypt(Base64.decode(originalPassword), KeyType.PrivateKey);
|
||||
|
||||
return StrUtil.str(decryptBytes, CharsetUtil.CHARSET_UTF_8);
|
||||
}
|
||||
|
||||
private boolean isCaptchaOn() {
|
||||
return Convert.toBool(guavaCache.configCache.get(ConfigKeyEnum.CAPTCHA.getValue()));
|
||||
}
|
||||
|
||||
}
|
||||
@ -12,6 +12,7 @@
|
||||
<modules>
|
||||
<module>wol-auth</module>
|
||||
<module>agileboot-system-base</module>
|
||||
<module>wol-gateway</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
|
||||
@ -16,6 +16,18 @@
|
||||
<groupId>com.agileboot</groupId>
|
||||
<artifactId>wol-common-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.agileboot</groupId>
|
||||
<artifactId>wol-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.agileboot</groupId>
|
||||
<artifactId>wol-common-satoken</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.agileboot</groupId>
|
||||
<artifactId>wol-common-redis</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
package com.agileboot.auth.captcha;
|
||||
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.math.Calculator;
|
||||
import cn.hutool.core.util.CharUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 无符号计算生成器
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public class UnsignedMathGenerator implements CodeGenerator {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = -5514819971774091076L;
|
||||
|
||||
private static final String OPERATORS = "+-*";
|
||||
|
||||
/**
|
||||
* 参与计算数字最大长度
|
||||
*/
|
||||
private final int numberLength;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public UnsignedMathGenerator() {
|
||||
this(2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param numberLength 参与计算最大数字位数
|
||||
*/
|
||||
public UnsignedMathGenerator(int numberLength) {
|
||||
this.numberLength = numberLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generate() {
|
||||
final int limit = getLimit();
|
||||
int a = RandomUtil.randomInt(limit);
|
||||
int b = RandomUtil.randomInt(limit);
|
||||
String max = Integer.toString(Math.max(a, b));
|
||||
String min = Integer.toString(Math.min(a, b));
|
||||
max = StringUtils.rightPad(max, this.numberLength, CharUtil.SPACE);
|
||||
min = StringUtils.rightPad(min, this.numberLength, CharUtil.SPACE);
|
||||
|
||||
return max + RandomUtil.randomChar(OPERATORS) + min + '=';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String code, String userInputCode) {
|
||||
int result;
|
||||
try {
|
||||
result = Integer.parseInt(userInputCode);
|
||||
} catch (NumberFormatException e) {
|
||||
// 用户输入非数字
|
||||
return false;
|
||||
}
|
||||
|
||||
final int calculateResult = (int) Calculator.conversion(code);
|
||||
return result == calculateResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取验证码长度
|
||||
*
|
||||
* @return 验证码长度
|
||||
*/
|
||||
public int getLength() {
|
||||
return this.numberLength * 2 + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据长度获取参与计算数字最大值
|
||||
*
|
||||
* @return 最大值
|
||||
*/
|
||||
private int getLimit() {
|
||||
return Integer.parseInt("1" + StringUtils.repeat('0', this.numberLength));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
package com.agileboot.auth.config;
|
||||
|
||||
import cn.hutool.captcha.CaptchaUtil;
|
||||
import cn.hutool.captcha.CircleCaptcha;
|
||||
import cn.hutool.captcha.LineCaptcha;
|
||||
import cn.hutool.captcha.ShearCaptcha;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* 验证码配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
public class CaptchaConfig {
|
||||
|
||||
private static final int WIDTH = 160;
|
||||
private static final int HEIGHT = 60;
|
||||
private static final Color BACKGROUND = Color.LIGHT_GRAY;
|
||||
private static final Font FONT = new Font("Arial", Font.BOLD, 48);
|
||||
|
||||
/**
|
||||
* 圆圈干扰验证码
|
||||
*/
|
||||
@Lazy
|
||||
@Bean
|
||||
public CircleCaptcha circleCaptcha() {
|
||||
CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(WIDTH, HEIGHT);
|
||||
captcha.setBackground(BACKGROUND);
|
||||
captcha.setFont(FONT);
|
||||
return captcha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 线段干扰的验证码
|
||||
*/
|
||||
@Lazy
|
||||
@Bean
|
||||
public LineCaptcha lineCaptcha() {
|
||||
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(WIDTH, HEIGHT);
|
||||
captcha.setBackground(BACKGROUND);
|
||||
captcha.setFont(FONT);
|
||||
return captcha;
|
||||
}
|
||||
|
||||
/**
|
||||
* 扭曲干扰验证码
|
||||
*/
|
||||
@Lazy
|
||||
@Bean
|
||||
public ShearCaptcha shearCaptcha() {
|
||||
ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(WIDTH, HEIGHT);
|
||||
captcha.setBackground(BACKGROUND);
|
||||
captcha.setFont(FONT);
|
||||
return captcha;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
package com.agileboot.auth.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.agileboot.auth.pojo.dto.LoginBody;
|
||||
import com.agileboot.auth.pojo.dto.RegisterBody;
|
||||
import com.agileboot.auth.pojo.vo.LoginVO;
|
||||
import com.agileboot.auth.pojo.vo.SysClientVO;
|
||||
import com.agileboot.auth.service.IAuthStrategy;
|
||||
import com.agileboot.auth.service.ISysClientService;
|
||||
import com.agileboot.auth.service.SysLoginService;
|
||||
import com.agileboot.common.core.constant.Constants;
|
||||
import com.agileboot.common.core.core.R;
|
||||
import com.agileboot.common.core.utils.ValidatorUtils;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 认证
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@SaIgnore
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("")
|
||||
public class AuthController {
|
||||
|
||||
private final SysLoginService loginService;
|
||||
private final ISysClientService sysClientService;
|
||||
|
||||
/**
|
||||
* 登录方法
|
||||
*
|
||||
* @param body 登录信息
|
||||
* @return 结果
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public R<?> login(@RequestBody String body) {
|
||||
LoginBody loginBody = JSONObject.parseObject(body, LoginBody.class);
|
||||
ValidatorUtils.validate(loginBody);
|
||||
String clientId = loginBody.getClientId();
|
||||
String grantType = loginBody.getGrantType();
|
||||
SysClientVO clientVo = sysClientService.queryByClientId(clientId);
|
||||
|
||||
if (ObjectUtil.isNull(clientVo) || !StringUtils.contains(clientVo.getGrantType(), grantType)) {
|
||||
log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
|
||||
return R.fail("auth.grant.type.error");
|
||||
} else if (!Constants.NORMAL.equals(clientVo.getStatus())) {
|
||||
return R.fail("auth.grant.type.blocked");
|
||||
}
|
||||
// 登录
|
||||
LoginVO loginVo = IAuthStrategy.login(body, clientVo, grantType);
|
||||
|
||||
return R.ok(loginVo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
@PostMapping("/logout")
|
||||
public R<Void> logout() {
|
||||
loginService.logout();
|
||||
return R.ok("退出成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
@PostMapping("/register")
|
||||
public R<Void> register(@Validated @RequestBody RegisterBody user) {
|
||||
loginService.register(user);
|
||||
return R.ok();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
package com.agileboot.auth.controller;
|
||||
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.agileboot.auth.enums.CaptchaType;
|
||||
import com.agileboot.auth.pojo.vo.CaptchaVO;
|
||||
import com.agileboot.auth.properties.CaptchaProperties;
|
||||
import com.agileboot.common.core.constant.Constants;
|
||||
import com.agileboot.common.core.core.R;
|
||||
import com.agileboot.common.redis.utils.RedisUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.aop.framework.AopContext;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* 验证码操作处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
public class CaptchaController {
|
||||
|
||||
private final CaptchaProperties captchaProperties;
|
||||
// private final RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
@GetMapping("/code")
|
||||
public R<CaptchaVO> getCode() {
|
||||
CaptchaVO captchaVo = new CaptchaVO();
|
||||
boolean captchaEnabled = captchaProperties.getEnabled();
|
||||
if (!captchaEnabled) {
|
||||
captchaVo.setCaptchaEnabled(false);
|
||||
return R.ok(captchaVo);
|
||||
}
|
||||
return R.ok(this.getCodeImpl());
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
* 独立方法避免验证码关闭之后仍然走限流
|
||||
*/
|
||||
// @RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
|
||||
public CaptchaVO getCodeImpl() {
|
||||
// 保存验证码信息
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String verifyKey = Constants.Cache.CAPTCHA_CODE_KEY + uuid;
|
||||
// 生成验证码
|
||||
CaptchaType captchaType = captchaProperties.getType();
|
||||
boolean isMath = CaptchaType.MATH == captchaType;
|
||||
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
|
||||
CodeGenerator codeGenerator = ReflectUtil.newInstance(captchaType.getClazz(), length);
|
||||
AbstractCaptcha captcha = SpringUtil.getBean(captchaProperties.getCategory().getClazz());
|
||||
captcha.setGenerator(codeGenerator);
|
||||
captcha.createCode();
|
||||
// 如果是数学验证码,使用SpEL表达式处理验证码结果
|
||||
String code = captcha.getCode();
|
||||
if (isMath) {
|
||||
ExpressionParser parser = new SpelExpressionParser();
|
||||
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
|
||||
code = exp.getValue(String.class);
|
||||
}
|
||||
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
// redisTemplate.opsForValue().set(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
CaptchaVO captchaVo = new CaptchaVO();
|
||||
captchaVo.setUuid(uuid);
|
||||
captchaVo.setImg(captcha.getImageBase64());
|
||||
captchaVo.setCode(code);
|
||||
return captchaVo;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.agileboot.auth.enums;
|
||||
|
||||
import cn.hutool.captcha.AbstractCaptcha;
|
||||
import cn.hutool.captcha.CircleCaptcha;
|
||||
import cn.hutool.captcha.LineCaptcha;
|
||||
import cn.hutool.captcha.ShearCaptcha;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 验证码类别
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CaptchaCategory {
|
||||
|
||||
/**
|
||||
* 线段干扰
|
||||
*/
|
||||
LINE(LineCaptcha.class),
|
||||
|
||||
/**
|
||||
* 圆圈干扰
|
||||
*/
|
||||
CIRCLE(CircleCaptcha.class),
|
||||
|
||||
/**
|
||||
* 扭曲干扰
|
||||
*/
|
||||
SHEAR(ShearCaptcha.class);
|
||||
|
||||
private final Class<? extends AbstractCaptcha> clazz;
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.agileboot.auth.enums;
|
||||
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import com.agileboot.auth.captcha.UnsignedMathGenerator;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 验证码类型
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum CaptchaType {
|
||||
|
||||
/**
|
||||
* 数字
|
||||
*/
|
||||
MATH(UnsignedMathGenerator.class),
|
||||
|
||||
/**
|
||||
* 字符
|
||||
*/
|
||||
CHAR(RandomGenerator.class);
|
||||
|
||||
private final Class<? extends CodeGenerator> clazz;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.agileboot.auth.mapper;
|
||||
|
||||
import com.agileboot.auth.pojo.entity.SysClient;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
public interface SysClientMapper extends BaseMapper<SysClient> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.agileboot.auth.mapper;
|
||||
|
||||
import com.agileboot.auth.pojo.entity.SysUser;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
public interface SysUserMapper extends BaseMapper<SysUser> {
|
||||
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.agileboot.auth.pojo.entity.SysClient">
|
||||
|
||||
</mapper>
|
||||
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.agileboot.auth.pojo.entity.SysUser">
|
||||
|
||||
</mapper>
|
||||
@ -0,0 +1,48 @@
|
||||
package com.agileboot.auth.pojo.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 用户登录对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
|
||||
@Data
|
||||
public class LoginBody implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 客户端id
|
||||
*/
|
||||
@NotBlank(message = "Auth clientid cannot be blank")
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 授权类型
|
||||
*/
|
||||
@NotBlank(message = "Auth grant type cannot be blank")
|
||||
private String grantType;
|
||||
|
||||
/**
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 唯一标识
|
||||
*/
|
||||
private String uuid;
|
||||
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
package com.agileboot.auth.pojo.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
/**
|
||||
* 用户注册对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class RegisterBody extends LoginBody {
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "{user.username.not.blank}")
|
||||
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 用户密码
|
||||
*/
|
||||
@NotBlank(message = "{user.password.not.blank}")
|
||||
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
|
||||
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 用户类型 sys_user app_user
|
||||
*/
|
||||
private String userType;
|
||||
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
package com.agileboot.auth.pojo.entity;
|
||||
|
||||
import com.agileboot.common.mybatis.core.domain.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 授权管理对象 sys_client
|
||||
*
|
||||
* @author Michelle.Chung
|
||||
* @date 2023-05-15
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_client")
|
||||
public class SysClient extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@TableId(type = IdType.ASSIGN_ID)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 客户端id
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 客户端key
|
||||
*/
|
||||
private String clientKey;
|
||||
|
||||
/**
|
||||
* 客户端秘钥
|
||||
*/
|
||||
private String clientSecret;
|
||||
|
||||
/**
|
||||
* 授权类型
|
||||
*/
|
||||
private String grantType;
|
||||
|
||||
/**
|
||||
* 设备类型
|
||||
*/
|
||||
private String deviceType;
|
||||
|
||||
/**
|
||||
* token活跃超时时间
|
||||
*/
|
||||
private Long activeTimeout;
|
||||
|
||||
/**
|
||||
* token固定超时时间
|
||||
*/
|
||||
private Long timeout;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
}
|
||||
@ -0,0 +1,112 @@
|
||||
package com.agileboot.auth.pojo.entity;
|
||||
|
||||
import com.agileboot.common.mybatis.core.domain.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("sys_user")
|
||||
public class SysUser extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@TableId(value = "user_id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 租户编号
|
||||
*/
|
||||
private String tenantId;
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 用户账号
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
* 用户类型(sys_user系统用户)
|
||||
*/
|
||||
private String userType;
|
||||
|
||||
/**
|
||||
* 用户邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 手机号码
|
||||
*/
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* 用户性别
|
||||
*/
|
||||
private String sex;
|
||||
|
||||
/**
|
||||
* 用户头像
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@TableField(
|
||||
insertStrategy = FieldStrategy.NOT_EMPTY,
|
||||
updateStrategy = FieldStrategy.NOT_EMPTY,
|
||||
whereStrategy = FieldStrategy.NOT_EMPTY
|
||||
)
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 帐号状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 最后登录IP
|
||||
*/
|
||||
private String loginIp;
|
||||
|
||||
/**
|
||||
* 最后登录时间
|
||||
*/
|
||||
private Date loginDate;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 职位id
|
||||
*/
|
||||
private Long postId;
|
||||
|
||||
/**
|
||||
* 角色id
|
||||
*/
|
||||
private Long roleId;
|
||||
|
||||
/**
|
||||
* 超级管理员标志(1是,0否)
|
||||
*/
|
||||
private Integer isAdmin;
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
package com.agileboot.auth.pojo.form;
|
||||
|
||||
import com.agileboot.auth.pojo.dto.LoginBody;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 邮件登录对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class EmailLoginBody extends LoginBody {
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@NotBlank(message = "{user.email.not.blank}")
|
||||
@Email(message = "{user.email.not.valid}")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 邮箱code
|
||||
*/
|
||||
@NotBlank(message = "{email.code.not.blank}")
|
||||
private String emailCode;
|
||||
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.agileboot.auth.pojo.form;
|
||||
|
||||
import com.agileboot.auth.pojo.dto.LoginBody;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 三方登录对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MiniappLoginBody extends LoginBody {
|
||||
|
||||
/**
|
||||
* 小程序id(多个小程序时使用)
|
||||
*/
|
||||
private String appid;
|
||||
|
||||
/**
|
||||
* 小程序code
|
||||
*/
|
||||
@NotBlank(message = "{xcx.code.not.blank}")
|
||||
private String xcxCode;
|
||||
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package com.agileboot.auth.pojo.form;
|
||||
|
||||
import com.agileboot.auth.pojo.dto.LoginBody;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
/**
|
||||
* 密码登录对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PasswordLoginBody extends LoginBody {
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "Username cannot be blank")
|
||||
@Length(min = 2, max = 30, message = "user.username.length.valid")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 用户密码
|
||||
*/
|
||||
@NotBlank(message = "Password cannot be empty")
|
||||
@Length(min = 5, max = 30, message = "user.password.length.valid")
|
||||
private String password;
|
||||
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
package com.agileboot.auth.pojo.form;
|
||||
|
||||
import com.agileboot.auth.pojo.dto.LoginBody;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.hibernate.validator.constraints.Length;
|
||||
|
||||
/**
|
||||
* 用户注册对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class RegisterBody extends LoginBody {
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "{user.username.not.blank}")
|
||||
@Length(min = 2, max = 30, message = "{user.username.length.valid}")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 用户密码
|
||||
*/
|
||||
@NotBlank(message = "{user.password.not.blank}")
|
||||
@Length(min = 5, max = 30, message = "{user.password.length.valid}")
|
||||
// @Pattern(regexp = RegexConstants.PASSWORD, message = "{user.password.format.valid}")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 用户类型
|
||||
*/
|
||||
private String userType;
|
||||
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.agileboot.auth.pojo.form;
|
||||
|
||||
import com.agileboot.auth.pojo.dto.LoginBody;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 短信登录对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class SmsLoginBody extends LoginBody {
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@NotBlank(message = "{user.phonenumber.not.blank}")
|
||||
private String phonenumber;
|
||||
|
||||
/**
|
||||
* 短信code
|
||||
*/
|
||||
@NotBlank(message = "{sms.code.not.blank}")
|
||||
private String smsCode;
|
||||
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
package com.agileboot.auth.pojo.form;
|
||||
|
||||
import com.agileboot.auth.pojo.dto.LoginBody;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
/**
|
||||
* 三方登录对象
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class SocialLoginBody extends LoginBody {
|
||||
|
||||
/**
|
||||
* 第三方登录平台
|
||||
*/
|
||||
@NotBlank(message = "{social.source.not.blank}")
|
||||
private String source;
|
||||
|
||||
/**
|
||||
* 第三方登录code
|
||||
*/
|
||||
@NotBlank(message = "{social.code.not.blank}")
|
||||
private String socialCode;
|
||||
|
||||
/**
|
||||
* 第三方登录socialState
|
||||
*/
|
||||
@NotBlank(message = "{social.state.not.blank}")
|
||||
private String socialState;
|
||||
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
package com.agileboot.auth.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 验证码信息
|
||||
*
|
||||
* @author Michelle.Chung
|
||||
*/
|
||||
@Data
|
||||
public class CaptchaVO {
|
||||
|
||||
/**
|
||||
* 是否开启验证码
|
||||
*/
|
||||
private Boolean captchaEnabled = true;
|
||||
|
||||
private String uuid;
|
||||
|
||||
/**
|
||||
* 验证码图片
|
||||
*/
|
||||
private String img;
|
||||
|
||||
private String code;
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
package com.agileboot.auth.pojo.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 登录验证信息
|
||||
*
|
||||
* @author Michelle.Chung
|
||||
*/
|
||||
@Data
|
||||
public class LoginVO {
|
||||
|
||||
/**
|
||||
* 授权令牌
|
||||
*/
|
||||
@JsonProperty("access_token")
|
||||
private String accessToken;
|
||||
|
||||
/**
|
||||
* 刷新令牌
|
||||
*/
|
||||
@JsonProperty("refresh_token")
|
||||
private String refreshToken;
|
||||
|
||||
/**
|
||||
* 授权令牌 access_token 的有效期
|
||||
*/
|
||||
@JsonProperty("expire_in")
|
||||
private Long expireIn;
|
||||
|
||||
/**
|
||||
* 刷新令牌 refresh_token 的有效期
|
||||
*/
|
||||
@JsonProperty("refresh_expire_in")
|
||||
private Long refreshExpireIn;
|
||||
|
||||
/**
|
||||
* 应用id
|
||||
*/
|
||||
@JsonProperty("client_id")
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 令牌权限
|
||||
*/
|
||||
private String scope;
|
||||
|
||||
/**
|
||||
* 用户 openid
|
||||
*/
|
||||
private String openid;
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package com.agileboot.auth.pojo.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class SysClientVO implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* id
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 客户端id
|
||||
*/
|
||||
private String clientId;
|
||||
|
||||
/**
|
||||
* 客户端key
|
||||
*/
|
||||
private String clientKey;
|
||||
|
||||
/**
|
||||
* 客户端秘钥
|
||||
*/
|
||||
private String clientSecret;
|
||||
|
||||
/**
|
||||
* 授权类型
|
||||
*/
|
||||
private List<String> grantTypeList;
|
||||
|
||||
/**
|
||||
* 授权类型
|
||||
*/
|
||||
private String grantType;
|
||||
|
||||
/**
|
||||
* 设备类型
|
||||
*/
|
||||
private String deviceType;
|
||||
|
||||
/**
|
||||
* token活跃超时时间
|
||||
*/
|
||||
private Long activeTimeout;
|
||||
|
||||
/**
|
||||
* token固定超时时间
|
||||
*/
|
||||
private Long timeout;
|
||||
|
||||
/**
|
||||
* 状态(0正常 1停用)
|
||||
*/
|
||||
private String status;
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package com.agileboot.auth.properties;
|
||||
|
||||
import com.agileboot.auth.enums.CaptchaCategory;
|
||||
import com.agileboot.auth.enums.CaptchaType;
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 验证码配置
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "security.captcha")
|
||||
public class CaptchaProperties {
|
||||
|
||||
/**
|
||||
* 验证码类型
|
||||
*/
|
||||
private CaptchaType type;
|
||||
|
||||
/**
|
||||
* 验证码类别
|
||||
*/
|
||||
private CaptchaCategory category;
|
||||
|
||||
/**
|
||||
* 数字验证码位数
|
||||
*/
|
||||
private Integer numberLength;
|
||||
|
||||
/**
|
||||
* 字符验证码长度
|
||||
*/
|
||||
private Integer charLength;
|
||||
|
||||
/**
|
||||
* 验证码开关
|
||||
*/
|
||||
private Boolean enabled;
|
||||
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
package com.agileboot.auth.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* 用户密码配置
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "user.password")
|
||||
public class UserPasswordProperties {
|
||||
|
||||
/**
|
||||
* 密码最大错误次数
|
||||
*/
|
||||
private Integer maxRetryCount;
|
||||
|
||||
/**
|
||||
* 密码锁定时间(默认10分钟)
|
||||
*/
|
||||
private Integer lockTime;
|
||||
|
||||
}
|
||||
@ -0,0 +1,46 @@
|
||||
package com.agileboot.auth.service;
|
||||
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import com.agileboot.auth.pojo.vo.LoginVO;
|
||||
import com.agileboot.auth.pojo.vo.SysClientVO;
|
||||
import com.agileboot.common.core.exception.ServiceException;
|
||||
|
||||
/**
|
||||
* 授权策略
|
||||
*
|
||||
* @author Michelle.Chung
|
||||
*/
|
||||
public interface IAuthStrategy {
|
||||
|
||||
String BASE_NAME = "AuthStrategy";
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
* @param body 登录对象
|
||||
* @param client 授权管理视图对象
|
||||
* @param grantType 授权类型
|
||||
* @return 登录验证信息
|
||||
*/
|
||||
static LoginVO login(String body, SysClientVO client, String grantType) {
|
||||
// 授权类型和客户端id
|
||||
String beanName = grantType + BASE_NAME;
|
||||
if (!SpringUtil.getBeanFactory().containsBean(beanName)) {
|
||||
throw new ServiceException("授权类型不正确!");
|
||||
}
|
||||
|
||||
IAuthStrategy instance = SpringUtil.getBean(beanName);
|
||||
return instance.login(body, client);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
* @param body 登录对象
|
||||
* @param client 授权管理视图对象
|
||||
* @return 登录验证信息
|
||||
*/
|
||||
LoginVO login(String body, SysClientVO client);
|
||||
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.agileboot.auth.service;
|
||||
|
||||
import com.agileboot.auth.pojo.vo.SysClientVO;
|
||||
|
||||
public interface ISysClientService {
|
||||
SysClientVO queryByClientId(String clientId);
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
package com.agileboot.auth.service;
|
||||
|
||||
import com.agileboot.auth.pojo.entity.SysUser;
|
||||
import com.agileboot.common.satoken.pojo.LoginUser;
|
||||
|
||||
public interface ISysUserService {
|
||||
LoginUser getUserInfo(String username);
|
||||
|
||||
boolean registerUserInfo(SysUser sysUser);
|
||||
}
|
||||
@ -0,0 +1,145 @@
|
||||
package com.agileboot.auth.service;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import com.agileboot.auth.pojo.dto.RegisterBody;
|
||||
import com.agileboot.auth.pojo.entity.SysUser;
|
||||
import com.agileboot.auth.properties.CaptchaProperties;
|
||||
import com.agileboot.auth.properties.UserPasswordProperties;
|
||||
import com.agileboot.common.core.constant.Constants;
|
||||
import com.agileboot.common.core.enums.LoginType;
|
||||
import com.agileboot.common.core.enums.UserType;
|
||||
import com.agileboot.common.core.exception.BizException;
|
||||
import com.agileboot.common.redis.utils.RedisUtils;
|
||||
import com.agileboot.common.satoken.pojo.LoginUser;
|
||||
import com.agileboot.common.satoken.utils.LoginHelper;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* 登录校验方法
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
@Service
|
||||
public class SysLoginService {
|
||||
|
||||
private final UserPasswordProperties userPasswordProperties;
|
||||
private final CaptchaProperties captchaProperties;
|
||||
|
||||
private final ISysUserService sysUserService;
|
||||
// private final RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
/**
|
||||
* 登录校验
|
||||
*/
|
||||
public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
|
||||
String errorKey = Constants.Cache.PWD_ERR_CNT_KEY + username;
|
||||
String loginFail = Constants.LOGIN_FAIL;
|
||||
Integer maxRetryCount = userPasswordProperties.getMaxRetryCount();
|
||||
Integer lockTime = userPasswordProperties.getLockTime();
|
||||
|
||||
// 获取用户登录错误次数,默认为0 (可自定义限制策略 例如: key + username + ip)
|
||||
int errorNumber = ObjectUtil.defaultIfNull(RedisUtils.getCacheObject(errorKey), 0);
|
||||
// 锁定时间内登录 则踢出
|
||||
if (errorNumber >= maxRetryCount) {
|
||||
throw new BizException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
||||
}
|
||||
|
||||
if (supplier.get()) {
|
||||
// 错误次数递增
|
||||
errorNumber++;
|
||||
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
|
||||
// 达到规定错误次数 则锁定登录
|
||||
if (errorNumber >= maxRetryCount) {
|
||||
throw new BizException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
|
||||
} else {
|
||||
// 未达到规定错误次数
|
||||
throw new BizException(loginType.getRetryLimitCount(), errorNumber);
|
||||
}
|
||||
}
|
||||
|
||||
// 登录成功 清空错误次数
|
||||
RedisUtils.deleteObject(errorKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
public void logout() {
|
||||
try {
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (ObjectUtil.isNull(loginUser)) {
|
||||
return;
|
||||
}
|
||||
log.info(JSONObject.toJSONString(loginUser));
|
||||
} catch (NotLoginException ignored) {
|
||||
} finally {
|
||||
try {
|
||||
StpUtil.logout();
|
||||
} catch (NotLoginException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
public void register(RegisterBody registerBody) {
|
||||
String tenantId = registerBody.getTenantId();
|
||||
String username = registerBody.getUsername();
|
||||
String password = registerBody.getPassword();
|
||||
|
||||
// 校验用户类型是否存在
|
||||
String userType = UserType.getUserType(registerBody.getUserType()).getUserType();
|
||||
|
||||
// 验证码开关
|
||||
if (captchaProperties.getEnabled()) {
|
||||
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
|
||||
}
|
||||
|
||||
// 注册用户信息
|
||||
SysUser sysUser = new SysUser();
|
||||
sysUser.setTenantId(tenantId);
|
||||
sysUser.setUsername(username);
|
||||
sysUser.setNickname(username);
|
||||
sysUser.setPassword(BCrypt.hashpw(password));
|
||||
sysUser.setUserType(userType);
|
||||
|
||||
boolean regFlag = sysUserService.registerUserInfo(sysUser);
|
||||
if (!regFlag) {
|
||||
throw new BizException("user.register.error");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param code 验证码
|
||||
* @param uuid 唯一标识
|
||||
*/
|
||||
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
|
||||
String verifyKey = Constants.Cache.CAPTCHA_CODE_KEY + StringUtils.defaultIfBlank(uuid, "");
|
||||
String captcha = RedisUtils.getCacheObject(verifyKey);
|
||||
// String captcha = redisTemplate.opsForValue().get(verifyKey);
|
||||
RedisUtils.deleteObject(verifyKey);
|
||||
if (captcha == null) {
|
||||
throw new BizException("user.captcha.expire");
|
||||
}
|
||||
if (!code.equalsIgnoreCase(captcha)) {
|
||||
throw new BizException("user.captcha.error");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
package com.agileboot.auth.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import com.agileboot.auth.mapper.SysClientMapper;
|
||||
import com.agileboot.auth.pojo.entity.SysClient;
|
||||
import com.agileboot.auth.pojo.vo.SysClientVO;
|
||||
import com.agileboot.auth.service.ISysClientService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SysClientServiceImpl extends ServiceImpl<SysClientMapper, SysClient> implements ISysClientService {
|
||||
@Override
|
||||
public SysClientVO queryByClientId(String clientId) {
|
||||
SysClient client = super.baseMapper.selectOne(new LambdaQueryWrapper<SysClient>().eq(SysClient::getClientId, clientId));
|
||||
return BeanUtil.copyProperties(client, SysClientVO.class);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package com.agileboot.auth.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.agileboot.auth.mapper.SysUserMapper;
|
||||
import com.agileboot.auth.pojo.entity.SysUser;
|
||||
import com.agileboot.auth.service.ISysUserService;
|
||||
import com.agileboot.common.core.constant.Constants;
|
||||
import com.agileboot.common.core.exception.BizException;
|
||||
import com.agileboot.common.satoken.pojo.LoginUser;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements ISysUserService {
|
||||
|
||||
@Override
|
||||
public LoginUser getUserInfo(String username) {
|
||||
SysUser sysUser = super.baseMapper.selectOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, username));
|
||||
if (ObjectUtil.isNull(sysUser)) {
|
||||
throw new BizException("user.not.exists", username);
|
||||
}
|
||||
if (Constants.DISABLE.equals(sysUser.getStatus())) {
|
||||
throw new BizException("user.blocked", username);
|
||||
}
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setUserId(sysUser.getUserId());
|
||||
loginUser.setDeptId(sysUser.getDeptId());
|
||||
loginUser.setPassword(sysUser.getPassword());
|
||||
loginUser.setUserType(sysUser.getUserType());
|
||||
loginUser.setUsername(sysUser.getUsername());
|
||||
loginUser.setNickname(sysUser.getNickname());
|
||||
loginUser.setPassword(sysUser.getPassword());
|
||||
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean registerUserInfo(SysUser sysUser) {
|
||||
// if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) {
|
||||
// throw new BizException("当前系统没有开启注册功能");
|
||||
// }
|
||||
if (super.baseMapper.exists(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUsername, sysUser.getUsername()))) {
|
||||
throw new BizException("user.register.save.error", sysUser.getUsername());
|
||||
}
|
||||
sysUser.setCreateBy(0L);
|
||||
sysUser.setUpdateBy(0L);
|
||||
return baseMapper.insert(sysUser) > 0;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package com.agileboot.auth.service.strategy;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
|
||||
import cn.hutool.crypto.digest.BCrypt;
|
||||
import com.agileboot.auth.pojo.form.PasswordLoginBody;
|
||||
import com.agileboot.auth.pojo.vo.LoginVO;
|
||||
import com.agileboot.auth.pojo.vo.SysClientVO;
|
||||
import com.agileboot.auth.properties.CaptchaProperties;
|
||||
import com.agileboot.auth.service.IAuthStrategy;
|
||||
import com.agileboot.auth.service.ISysUserService;
|
||||
import com.agileboot.auth.service.SysLoginService;
|
||||
import com.agileboot.common.core.enums.LoginType;
|
||||
import com.agileboot.common.core.utils.ValidatorUtils;
|
||||
import com.agileboot.common.satoken.pojo.LoginUser;
|
||||
import com.agileboot.common.satoken.utils.LoginHelper;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
* 密码认证策略
|
||||
*
|
||||
* @author Michelle.Chung
|
||||
*/
|
||||
@Slf4j
|
||||
@Service("password" + IAuthStrategy.BASE_NAME)
|
||||
@RequiredArgsConstructor
|
||||
public class PasswordAuthStrategy implements IAuthStrategy {
|
||||
|
||||
private final ISysUserService userService;
|
||||
private final SysLoginService loginService;
|
||||
private final CaptchaProperties captchaProperties;
|
||||
|
||||
@Override
|
||||
public LoginVO login(String body, SysClientVO client) {
|
||||
PasswordLoginBody loginBody = JSONObject.parseObject(body, PasswordLoginBody.class);
|
||||
ValidatorUtils.validate(loginBody);
|
||||
String username = loginBody.getUsername();
|
||||
String password = loginBody.getPassword();
|
||||
String code = loginBody.getCode();
|
||||
String uuid = loginBody.getUuid();
|
||||
|
||||
// 验证码开关
|
||||
if (captchaProperties.getEnabled()) {
|
||||
loginService.validateCaptcha(null, null, code, uuid);
|
||||
}
|
||||
|
||||
|
||||
LoginUser loginUser = userService.getUserInfo(username);
|
||||
loginService.checkLogin(LoginType.PASSWORD, null, loginUser.getUsername(), () -> !BCrypt.checkpw(password, loginUser.getPassword()));
|
||||
|
||||
loginUser.setClientKey(client.getClientKey());
|
||||
loginUser.setDeviceType(client.getDeviceType());
|
||||
SaLoginParameter model = new SaLoginParameter();
|
||||
model.setDeviceType(client.getDeviceType());
|
||||
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
|
||||
// 例如: 后台用户30分钟过期 app用户1天过期
|
||||
model.setTimeout(client.getTimeout());
|
||||
model.setActiveTimeout(client.getActiveTimeout());
|
||||
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
|
||||
// 生成token
|
||||
LoginHelper.login(loginUser, model);
|
||||
|
||||
LoginVO loginVo = new LoginVO();
|
||||
loginVo.setAccessToken(StpUtil.getTokenValue());
|
||||
loginVo.setExpireIn(StpUtil.getTokenTimeout());
|
||||
loginVo.setClientId(client.getClientId());
|
||||
return loginVo;
|
||||
}
|
||||
}
|
||||
129
agileboot-system/wol-auth/src/main/resources/application-dev.yml
Normal file
129
agileboot-system/wol-auth/src/main/resources/application-dev.yml
Normal file
@ -0,0 +1,129 @@
|
||||
# 数据源配置
|
||||
spring:
|
||||
datasource:
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
driverClassName: com.mysql.cj.jdbc.Driver
|
||||
druid:
|
||||
webStatFilter:
|
||||
enabled: true
|
||||
statViewServlet:
|
||||
enabled: true
|
||||
# 设置白名单,不填则允许所有访问
|
||||
allow:
|
||||
url-pattern: /druid/*
|
||||
# 控制台管理用户名和密码
|
||||
login-username: agileboot
|
||||
login-password: 123456
|
||||
filter:
|
||||
stat:
|
||||
enabled: true
|
||||
# 慢SQL记录
|
||||
log-slow-sql: true
|
||||
slow-sql-millis: 1000
|
||||
merge-sql: true
|
||||
wall:
|
||||
config:
|
||||
multi-statement-allow: true
|
||||
dynamic:
|
||||
primary: master
|
||||
strict: false
|
||||
druid:
|
||||
# 初始连接数
|
||||
initialSize: 5
|
||||
# 最小连接池数量
|
||||
minIdle: 10
|
||||
# 最大连接池数量
|
||||
maxActive: 20
|
||||
# 配置获取连接等待超时的时间
|
||||
maxWait: 60000
|
||||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
|
||||
timeBetweenEvictionRunsMillis: 60000
|
||||
# 配置一个连接在池中最小生存的时间,单位是毫秒
|
||||
minEvictableIdleTimeMillis: 300000
|
||||
# 配置一个连接在池中最大生存的时间,单位是毫秒
|
||||
maxEvictableIdleTimeMillis: 900000
|
||||
# 配置检测连接是否有效
|
||||
validationQuery: SELECT 1 FROM DUAL
|
||||
testWhileIdle: true
|
||||
testOnBorrow: false
|
||||
testOnReturn: false
|
||||
datasource:
|
||||
master:
|
||||
url: jdbc:mysql://mysql2.sqlpub.com:3307/agileboot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&sslMode=REQUIRED
|
||||
username: ENC(s4kjpEsplGGLeV3YRNvJpJhDSOAO0tEf)
|
||||
password: ENC(hg/hxmducWsI8u83/eXgAi8yHBDFbB5z0xzwNtBejPc=)
|
||||
data:
|
||||
redis:
|
||||
database: 1
|
||||
host: 47.94.178.59
|
||||
port: 6379
|
||||
password: 'jjt@1234'
|
||||
jasypt:
|
||||
encryptor:
|
||||
password: ${JASYPT_ENCRYPTOR_PASSWORD:}
|
||||
|
||||
user:
|
||||
password:
|
||||
# 密码最大错误次数
|
||||
maxRetryCount: 5
|
||||
# 密码锁定时间(默认10分钟)
|
||||
lockTime: 10
|
||||
|
||||
# 安全配置
|
||||
security:
|
||||
# 验证码
|
||||
captcha:
|
||||
# 是否开启验证码
|
||||
enabled: false
|
||||
# 验证码类型 math 数组计算 char 字符验证
|
||||
type: CHAR
|
||||
# line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
|
||||
category: CIRCLE
|
||||
# 数字验证码位数
|
||||
numberLength: 0
|
||||
# 字符验证码长度
|
||||
charLength: 5
|
||||
|
||||
|
||||
# redisson 配置
|
||||
#redisson:
|
||||
# # redis key前缀
|
||||
# keyPrefix:
|
||||
# # 线程池数量
|
||||
# threads: 4
|
||||
# # Netty线程池数量
|
||||
# nettyThreads: 8
|
||||
# # 单节点配置
|
||||
# singleServerConfig:
|
||||
# # 客户端名称
|
||||
# clientName: ${spring.application.name}
|
||||
# # 最小空闲连接数
|
||||
# connectionMinimumIdleSize: 8
|
||||
# # 连接池大小
|
||||
# connectionPoolSize: 32
|
||||
# # 连接空闲超时,单位:毫秒
|
||||
# idleConnectionTimeout: 10000
|
||||
# # 命令等待超时,单位:毫秒
|
||||
# timeout: 3000
|
||||
# # 发布和订阅连接池大小
|
||||
# subscriptionConnectionPoolSize: 50
|
||||
|
||||
# 分布式锁 lock4j 全局配置
|
||||
#lock4j:
|
||||
# # 获取分布式锁超时时间,默认为 3000 毫秒
|
||||
# acquire-timeout: 3000
|
||||
# # 分布式锁的超时时间,默认为 30 秒
|
||||
# expire: 30000
|
||||
|
||||
# Sa-Token配置
|
||||
sa-token:
|
||||
# token名称 (同时也是cookie名称)
|
||||
token-name: Authorization
|
||||
# 开启内网服务调用鉴权(不允许越过gateway访问内网服务 保障服务安全)
|
||||
check-same-token: true
|
||||
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
||||
is-share: false
|
||||
# jwt秘钥
|
||||
jwt-secret-key: abcdefghijklmnopqrstuvwxyz
|
||||
@ -4,4 +4,6 @@ server:
|
||||
context-path: /auth
|
||||
spring:
|
||||
application:
|
||||
name: wol-auth
|
||||
name: wol-auth
|
||||
profiles:
|
||||
active: dev
|
||||
20
agileboot-system/wol-gateway/pom.xml
Normal file
20
agileboot-system/wol-gateway/pom.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.agileboot</groupId>
|
||||
<artifactId>agileboot-system</artifactId>
|
||||
<version>1.0.0</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>wol-gateway</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.agileboot</groupId>
|
||||
<artifactId>wol-common-satoken</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@ -0,0 +1,20 @@
|
||||
package com.agileboot.gateway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
|
||||
|
||||
/**
|
||||
* 网关启动程序
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class WolGatewayApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication application = new SpringApplication(WolGatewayApplication.class);
|
||||
application.setApplicationStartup(new BufferingApplicationStartup(2048));
|
||||
application.run(args);
|
||||
System.out.println("(♥◠‿◠)ノ゙ 网关启动成功 ლ(´ڡ`ლ)゙ ");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,63 @@
|
||||
package com.agileboot.gateway.filter;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.router.SaRouter;
|
||||
import cn.dev33.satoken.servlet.model.SaRequestForServlet;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import com.agileboot.common.core.constant.HttpStatus;
|
||||
import com.agileboot.common.satoken.utils.LoginHelper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 拦截器
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Configuration
|
||||
public class AuthFilter {
|
||||
|
||||
/**
|
||||
* 注册 Sa-Token 全局过滤器
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
// 拦截地址
|
||||
.addInclude("/**")
|
||||
.addExclude("/favicon.ico", "/actuator", "/actuator/**", "/resource/sse")
|
||||
// 鉴权方法:每次访问进入
|
||||
.setAuth(obj -> {
|
||||
// 登录校验 -- 拦截所有路由
|
||||
SaRouter.match("/**")
|
||||
// .notMatch()
|
||||
.check(r -> {
|
||||
SaRequestForServlet req = (SaRequestForServlet) obj;
|
||||
ServerHttpRequest request = (ServerHttpRequest) req.getSource();
|
||||
// 检查是否登录 是否有token
|
||||
StpUtil.checkLogin();
|
||||
|
||||
// 检查 header 与 param 里的 clientid 与 token 里的是否一致
|
||||
String headerCid = request.getHeaders().getFirst(LoginHelper.CLIENT_KEY);
|
||||
String paramCid = request.getQueryParams().getFirst(LoginHelper.CLIENT_KEY);
|
||||
String clientId = StpUtil.getExtra(LoginHelper.CLIENT_KEY).toString();
|
||||
if (!StringUtils.equalsAny(clientId, headerCid, paramCid)) {
|
||||
// token 无效
|
||||
throw NotLoginException.newInstance(StpUtil.getLoginType(),
|
||||
"-100", "客户端ID与Token不匹配",
|
||||
StpUtil.getTokenValue());
|
||||
}
|
||||
});
|
||||
}).setError(e -> {
|
||||
if (e instanceof NotLoginException) {
|
||||
return SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
return SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
# Tomcat
|
||||
server:
|
||||
port: 8080
|
||||
servlet:
|
||||
context-path: /
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: wol-gateway
|
||||
7
pom.xml
7
pom.xml
@ -43,6 +43,7 @@
|
||||
<redisson.version>3.50.0</redisson.version>
|
||||
<lock4j.version>2.2.7</lock4j.version>
|
||||
<guava.version>31.0.1-jre</guava.version>
|
||||
<fastjson2.version>2.0.58</fastjson2.version>
|
||||
|
||||
|
||||
<!-- 插件版本 -->
|
||||
@ -265,7 +266,11 @@
|
||||
<artifactId>therapi-runtime-javadoc</artifactId>
|
||||
<version>${therapi-javadoc.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.alibaba.fastjson2</groupId>
|
||||
<artifactId>fastjson2</artifactId>
|
||||
<version>${fastjson2.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user