sso-system dubbo服务实现

This commit is contained in:
Github_lizhw 2024-11-07 23:02:47 +08:00
parent b789426363
commit 8bb82902c0
49 changed files with 2797 additions and 902 deletions

View File

@ -62,7 +62,7 @@
<maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison> <maven-compiler-plugin.verison>3.11.0</maven-compiler-plugin.verison>
<maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version> <maven-surefire-plugin.version>3.1.2</maven-surefire-plugin.version>
<flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version> <flatten-maven-plugin.version>1.3.0</flatten-maven-plugin.version>
<dubbo.version>3.3.0</dubbo.version> <dubbo.version>3.2.14</dubbo.version>
</properties> </properties>
<profiles> <profiles>
@ -422,6 +422,12 @@
<version>${dubbo.version}</version> <version>${dubbo.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-metadata-report-redis</artifactId>
<version>${dubbo.version}</version>
</dependency>
<!-- <dependency> <!-- <dependency>
<groupId>org.apache.dubbo</groupId> <groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId> <artifactId>dubbo</artifactId>

View File

@ -12,7 +12,7 @@
<artifactId>ruoyi-api-system</artifactId> <artifactId>ruoyi-api-system</artifactId>
<description> <description>
ruoyi-api-system系统接口模块 ruoyi-api-system系统接口模块 TODO 记得改名 ruoyi-api-system-saas
</description> </description>
<dependencies> <dependencies>

View File

@ -0,0 +1,71 @@
package org.dromara.system.api.domain;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 当前在线会话
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class SysUserOnline implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 会话编号
*/
private String tokenId;
/**
* 部门名称
*/
private String deptName;
/**
* 用户名称
*/
private String userName;
/**
* 客户端
*/
private String clientKey;
/**
* 设备类型
*/
private String deviceType;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地址
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 登录时间
*/
private Long loginTime;
}

View File

@ -17,11 +17,6 @@
<dependencies> <dependencies>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-satoken</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
<artifactId>ruoyi-common-json</artifactId> <artifactId>ruoyi-common-json</artifactId>

View File

@ -0,0 +1,11 @@
package org.dromara.common.log.aspect;
import org.dromara.common.core.domain.model.LoginUser;
public interface CommonLogTenantIntercept {
Object getTenantId();
String getUsername();
String getDeptName();
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.lang.Dict;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -13,7 +14,6 @@ import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Before;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.utils.ServletUtils; import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
@ -21,7 +21,7 @@ import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.log.annotation.Log; import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessStatus; import org.dromara.common.log.enums.BusinessStatus;
import org.dromara.common.log.event.OperLogEvent; import org.dromara.common.log.event.OperLogEvent;
import org.dromara.common.satoken.utils.LoginHelper; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
@ -41,10 +41,23 @@ import java.util.StringJoiner;
@AutoConfiguration @AutoConfiguration
public class LogAspect { public class LogAspect {
@Autowired(required = false)
private CommonLogTenantIntercept commonLogTenantIntercept;
@PostConstruct
public void init() {
if (commonLogTenantIntercept != null) {
log.info("CommonLogTenantIntercept 有实现对象: " + commonLogTenantIntercept.getClass().getName());
} else {
System.exit(0);
throw new RuntimeException("CommonLogTenantIntercept 未实现, 请实现 CommonLogTenantIntercept 接口");
}
}
/** /**
* 排除敏感属性字段 * 排除敏感属性字段
*/ */
public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; public static final String[] EXCLUDE_PROPERTIES = {"password", "oldPassword", "newPassword", "confirmPassword"};
/** /**
@ -88,15 +101,18 @@ public class LogAspect {
// *========数据库日志=========*// // *========数据库日志=========*//
OperLogEvent operLog = new OperLogEvent(); OperLogEvent operLog = new OperLogEvent();
operLog.setTenantId(LoginHelper.getTenantId()); if (commonLogTenantIntercept != null) {
operLog.setTenantId(commonLogTenantIntercept.getTenantId().toString());
}
operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
// 请求的地址 // 请求的地址
String ip = ServletUtils.getClientIP(); String ip = ServletUtils.getClientIP();
operLog.setOperIp(ip); operLog.setOperIp(ip);
operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255));
LoginUser loginUser = LoginHelper.getLoginUser(); if (commonLogTenantIntercept != null) {
operLog.setOperName(loginUser.getUsername()); operLog.setOperName(commonLogTenantIntercept.getUsername());
operLog.setDeptName(loginUser.getDeptName()); operLog.setDeptName(commonLogTenantIntercept.getDeptName());
}
if (e != null) { if (e != null) {
operLog.setStatus(BusinessStatus.FAIL.ordinal()); operLog.setStatus(BusinessStatus.FAIL.ordinal());

View File

@ -59,20 +59,59 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- satoken --> <!-- RuoYi Common Redis-->
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
<artifactId>ruoyi-common-satoken</artifactId> <artifactId>ruoyi-common-redis</artifactId>
<version>${revision}</version> </dependency>
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
</dependency> </dependency>
<!-- 安全模块 --> <!-- Sa-Token 整合 jwt -->
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>cn.dev33</groupId>
<artifactId>ruoyi-common-security</artifactId> <artifactId>sa-token-jwt</artifactId>
<version>${revision}</version>
</dependency> </dependency>
<!-- <dependency>-->
<!-- <groupId>com.github.ben-manes.caffeine</groupId>-->
<!-- <artifactId>caffeine</artifactId>-->
<!-- </dependency>-->
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-jackson</artifactId>
<version>1.38.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Sa-Token 插件整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>1.38.0</version>
</dependency>
<!-- Sa-Token 插件整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>1.38.0</version>
</dependency>
<!-- &lt;!&ndash; 安全模块 &ndash;&gt;-->
<!-- <dependency>-->
<!-- <groupId>org.dromara</groupId>-->
<!-- <artifactId>ruoyi-common-security</artifactId>-->
<!-- <version>${revision}</version>-->
<!-- </dependency>-->
<!-- 租户模块 --> <!-- 租户模块 -->
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
@ -80,26 +119,12 @@
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<!-- 远程调用模块 --> <!-- 远程调用模块 TODO rename -->
<dependency> <dependency>
<groupId>org.dromara</groupId> <groupId>org.dromara</groupId>
<artifactId>ruoyi-api-system</artifactId> <artifactId>ruoyi-api-system</artifactId>
</dependency> </dependency>
<!-- Sa-Token 插件整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>1.38.0</version>
</dependency>
<!-- Sa-Token 插件整合SSO -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-sso</artifactId>
<version>1.38.0</version>
</dependency>
<!-- Apache Dubbo 配置 --> <!-- Apache Dubbo 配置 -->
<dependency> <dependency>
<groupId>org.apache.dubbo</groupId> <groupId>org.apache.dubbo</groupId>
@ -111,6 +136,46 @@
<artifactId>dubbo-nacos-spring-boot-starter</artifactId> <artifactId>dubbo-nacos-spring-boot-starter</artifactId>
</dependency> </dependency>
<!-- JustAuth 的依赖配置-->
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>1.16.6</version>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-log</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-ratelimiter</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-captcha</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-social</artifactId>
</dependency>
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-encrypt</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.apache.dubbo</groupId>-->
<!-- <artifactId>dubbo-metadata-report-redis</artifactId>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>redis.clients</groupId>-->
<!-- <artifactId>jedis</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
</dependencies> </dependencies>

View File

@ -0,0 +1,88 @@
package com.pj.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.dromara.common.core.utils.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));
}
}

View File

@ -0,0 +1,62 @@
package com.pj.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;
}
}

View File

@ -0,0 +1,78 @@
package com.pj.auth.controller;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import com.pj.auth.domain.vo.CaptchaVo;
import com.pj.auth.enums.CaptchaType;
import com.pj.auth.properties.CaptchaProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.ratelimiter.enums.LimitType;
import org.dromara.common.redis.utils.RedisUtils;
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;
/**
* 生成验证码
*/
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
@GetMapping("/code")
public R<CaptchaVo> getCode() {
CaptchaVo captchaVo = new CaptchaVo();
boolean captchaEnabled = captchaProperties.getEnabled();
if (!captchaEnabled) {
captchaVo.setCaptchaEnabled(false);
return R.ok(captchaVo);
}
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
CaptchaType captchaType = captchaProperties.getType();
boolean isMath = CaptchaType.MATH == captchaType;
Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength();
CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length);
AbstractCaptcha captcha = SpringUtils.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));
captchaVo.setUuid(uuid);
captchaVo.setImg(captcha.getImageBase64());
return R.ok(captchaVo);
}
}

View File

@ -0,0 +1,225 @@
package com.pj.auth.controller;
import cn.dev33.satoken.exception.NotLoginException;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.pj.auth.domain.vo.LoginTenantVo;
import com.pj.auth.domain.vo.LoginVo;
import com.pj.auth.domain.vo.TenantListVo;
import com.pj.auth.form.RegisterBody;
import com.pj.auth.form.SocialLoginBody;
import com.pj.auth.service.IAuthStrategy;
import com.pj.auth.service.SysLoginService;
import com.pj.helper.LoginSaasHelper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginBody;
import org.dromara.common.core.utils.*;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.social.config.properties.SocialLoginConfigProperties;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.api.RemoteClientService;
import org.dromara.system.api.RemoteConfigService;
import org.dromara.system.api.RemoteSocialService;
import org.dromara.system.api.RemoteTenantService;
import org.dromara.system.api.domain.vo.RemoteClientVo;
import org.dromara.system.api.domain.vo.RemoteTenantVo;
import org.springframework.web.bind.annotation.*;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
/**
* token 控制
*
* @author Lion Li
*/
@Slf4j
@RequiredArgsConstructor
@RestController
public class TokenController {
private final SocialProperties socialProperties;
private final SysLoginService sysLoginService;
private final ScheduledExecutorService scheduledExecutorService;
@DubboReference
private RemoteConfigService remoteConfigService;
@DubboReference
private RemoteTenantService remoteTenantService;
@DubboReference
private RemoteClientService remoteClientService;
@DubboReference
private RemoteSocialService remoteSocialService;
/**
* 登录方法
*
* @param body 登录信息
* @return 结果
*/
@ApiEncrypt
@PostMapping("/login")
public R<LoginVo> login(@RequestBody String body) {
LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
ValidatorUtils.validate(loginBody);
// 授权类型和客户端id
String clientId = loginBody.getClientId();
String grantType = loginBody.getGrantType();
RemoteClientVo clientVo = remoteClientService.queryByClientId(clientId);
// 查询不到 client client 内不包含 grantType
if (ObjectUtil.isNull(clientVo) || !StringUtils.contains(clientVo.getGrantType(), grantType)) {
log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
return R.fail(MessageUtils.message("auth.grant.type.error"));
} else if (!UserConstants.NORMAL.equals(clientVo.getStatus())) {
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
}
// 校验租户
sysLoginService.checkTenant(loginBody.getTenantId());
// 登录
LoginVo loginVo = IAuthStrategy.login(body, clientVo, grantType);
Long userId = LoginSaasHelper.getUserId();
log.info(userId + "欢迎登录RuoYi-Cloud-Plus微服务管理系统");
return R.ok(loginVo);
}
/**
* 第三方登录请求
*
* @param source 登录来源
* @return 结果
*/
@GetMapping("/binding/{source}")
public R<String> authBinding(@PathVariable("source") String source,
@RequestParam String tenantId, @RequestParam String domain) {
SocialLoginConfigProperties obj = socialProperties.getType().get(source);
if (ObjectUtil.isNull(obj)) {
return R.fail(source + "平台账号暂不支持");
}
AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties);
Map<String, String> map = new HashMap<>();
map.put("tenantId", tenantId);
map.put("domain", domain);
map.put("state", AuthStateUtils.createState());
String authorizeUrl = authRequest.authorize(Base64.encode(JsonUtils.toJsonString(map), StandardCharsets.UTF_8));
return R.ok("操作成功", authorizeUrl);
}
/**
* 第三方登录回调业务处理 绑定授权
*
* @param loginBody 请求体
* @return 结果
*/
@PostMapping("/social/callback")
public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) {
// 获取第三方登录信息
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
AuthUser authUserData = response.getData();
// 判断授权响应是否成功
if (!response.ok()) {
return R.fail(response.getMsg());
}
sysLoginService.socialRegister(authUserData);
return R.ok();
}
/**
* 取消授权
*
* @param socialId socialId
*/
@DeleteMapping(value = "/unlock/{socialId}")
public R<Void> unlockSocial(@PathVariable Long socialId) {
Boolean rows = remoteSocialService.deleteWithValidById(socialId);
return rows ? R.ok() : R.fail("取消授权失败");
}
/**
* 登出方法
*/
@PostMapping("logout")
public R<Void> logout() {
sysLoginService.logout();
return R.ok();
}
/**
* 用户注册
*/
@ApiEncrypt
@PostMapping("register")
public R<Void> register(@RequestBody RegisterBody registerBody) {
if (!remoteConfigService.selectRegisterEnabled(registerBody.getTenantId())) {
return R.fail("当前系统没有开启注册功能!");
}
// 用户注册
sysLoginService.register(registerBody);
return R.ok();
}
/**
* 登录页面租户下拉框
*
* @return 租户列表
*/
@GetMapping("/tenant/list")
public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception {
// 返回对象
LoginTenantVo result = new LoginTenantVo();
boolean enable = TenantHelper.isEnable();
result.setTenantEnabled(enable);
// 如果未开启租户这直接返回
if (!enable) {
return R.ok(result);
}
List<RemoteTenantVo> tenantList = remoteTenantService.queryList();
List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class);
try {
// 如果只超管返回所有租户
if (LoginSaasHelper.isSuperAdmin()) {
result.setVoList(voList);
return R.ok(result);
}
} catch (NotLoginException ignored) {
}
// 获取域名
String host;
String referer = request.getHeader("referer");
if (StringUtils.isNotBlank(referer)) {
// 这里从referer中取值是为了本地使用hosts添加虚拟域名方便本地环境调试
host = referer.split("//")[1].split("/")[0];
} else {
host = new URL(request.getRequestURL().toString()).getHost();
}
// 根据域名进行筛选
List<TenantListVo> list = StreamUtils.filter(voList, vo ->
StringUtils.equals(vo.getDomain(), host));
result.setVoList(CollUtil.isNotEmpty(list) ? list : voList);
return R.ok(result);
}
}

View File

@ -0,0 +1,16 @@
package com.pj.auth.domain.convert;
import io.github.linpeilie.BaseMapper;
import com.pj.auth.domain.vo.TenantListVo;
import org.dromara.system.api.domain.vo.RemoteTenantVo;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
/**
* 租户vo转换器
* @author zhujie
*/
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface TenantVoConvert extends BaseMapper<RemoteTenantVo, TenantListVo> {
}

View File

@ -1,4 +1,4 @@
package com.pj.model.vo; package com.pj.auth.domain.vo;
import lombok.Data; import lombok.Data;

View File

@ -0,0 +1,25 @@
package com.pj.auth.domain.vo;
import lombok.Data;
import java.util.List;
/**
* 登录租户对象
*
* @author Michelle.Chung
*/
@Data
public class LoginTenantVo {
/**
* 租户开关
*/
private Boolean tenantEnabled;
/**
* 租户对象列表
*/
private List<TenantListVo> voList;
}

View File

@ -1,4 +1,4 @@
package com.pj.model.vo; package com.pj.auth.domain.vo;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data; import lombok.Data;

View File

@ -0,0 +1,28 @@
package com.pj.auth.domain.vo;
import lombok.Data;
/**
* 租户列表
*
* @author zhujie
*/
@Data
public class TenantListVo {
/**
* 租户编号
*/
private String tenantId;
/**
* 企业名称
*/
private String companyName;
/**
* 域名
*/
private String domain;
}

View File

@ -0,0 +1,35 @@
package com.pj.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;
}

View File

@ -0,0 +1,29 @@
package com.pj.auth.enums;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import com.pj.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;
}

View File

@ -0,0 +1,31 @@
package com.pj.auth.form;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.domain.model.LoginBody;
/**
* 邮件登录对象
*
* @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;
}

View File

@ -0,0 +1,34 @@
package com.pj.auth.form;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.domain.model.LoginBody;
import org.hibernate.validator.constraints.Length;
import static org.dromara.common.core.constant.UserConstants.*;
/**
* 密码登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class PasswordLoginBody extends LoginBody {
/**
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
private String password;
}

View File

@ -0,0 +1,39 @@
package com.pj.auth.form;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.domain.model.LoginBody;
import org.hibernate.validator.constraints.Length;
import static org.dromara.common.core.constant.UserConstants.*;
/**
* 用户注册对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RegisterBody extends LoginBody {
/**
* 用户名
*/
@NotBlank(message = "{user.username.not.blank}")
@Length(min = USERNAME_MIN_LENGTH, max = USERNAME_MAX_LENGTH, message = "{user.username.length.valid}")
private String username;
/**
* 用户密码
*/
@NotBlank(message = "{user.password.not.blank}")
@Length(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH, message = "{user.password.length.valid}")
private String password;
/**
* 用户类型
*/
private String userType;
}

View File

@ -0,0 +1,29 @@
package com.pj.auth.form;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.domain.model.LoginBody;
/**
* 短信登录对象
*
* @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;
}

View File

@ -0,0 +1,35 @@
package com.pj.auth.form;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.domain.model.LoginBody;
/**
* 三方登录对象
*
* @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;
}

View File

@ -0,0 +1,28 @@
package com.pj.auth.form;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.core.domain.model.LoginBody;
/**
* 三方登录对象
*
* @author Lion Li
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class XcxLoginBody extends LoginBody {
/**
* 小程序id(多个小程序时使用)
*/
private String appid;
/**
* 小程序code
*/
@NotBlank(message = "{xcx.code.not.blank}")
private String xcxCode;
}

View File

@ -0,0 +1,169 @@
package com.pj.auth.listener;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.listener.SaTokenListener;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.pj.helper.LoginSaasHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.ServletUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.ip.AddressUtils;
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.api.RemoteUserService;
import org.dromara.system.api.domain.SysUserOnline;
import org.springframework.stereotype.Component;
import java.time.Duration;
/**
* 用户行为 侦听器的实现
*
* @author Lion Li
*/
@RequiredArgsConstructor
@Component
@Slf4j
public class UserActionListener implements SaTokenListener {
private final SaTokenConfig tokenConfig;
@DubboReference
private RemoteUserService remoteUserService;
// @DubboReference
// private RemoteMessageService remoteMessageService;
/**
* 每次登录时触发
*/
@Override
public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
String ip = ServletUtils.getClientIP();
SysUserOnline userOnline = new SysUserOnline();
userOnline.setIpaddr(ip);
userOnline.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
userOnline.setBrowser(userAgent.getBrowser().getName());
userOnline.setOs(userAgent.getOs().getName());
userOnline.setLoginTime(System.currentTimeMillis());
userOnline.setTokenId(tokenValue);
String username = (String) loginModel.getExtra(LoginSaasHelper.USER_NAME_KEY);
String tenantId = (String) loginModel.getExtra(LoginSaasHelper.TENANT_KEY);
userOnline.setUserName(username);
userOnline.setClientKey((String) loginModel.getExtra(LoginSaasHelper.CLIENT_KEY));
userOnline.setDeviceType(loginModel.getDevice());
userOnline.setDeptName((String) loginModel.getExtra(LoginSaasHelper.DEPT_NAME_KEY));
TenantHelper.dynamic(tenantId, () -> {
if (tokenConfig.getTimeout() == -1) {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, userOnline);
} else {
RedisUtils.setCacheObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue, userOnline, Duration.ofSeconds(tokenConfig.getTimeout()));
}
});
// 记录登录日志
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(Constants.LOGIN_SUCCESS);
logininforEvent.setMessage(MessageUtils.message("user.login.success"));
SpringUtils.context().publishEvent(logininforEvent);
// 更新登录信息
remoteUserService.recordLoginInfo((Long) loginModel.getExtra(LoginSaasHelper.USER_KEY), ip);
log.info("user doLogin, useId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次注销时触发
*/
@Override
public void doLogout(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginSaasHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doLogout, useId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次被踢下线时触发
*/
@Override
public void doKickout(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginSaasHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doLogoutByLoginId, useId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次被顶下线时触发
*/
@Override
public void doReplaced(String loginType, Object loginId, String tokenValue) {
String tenantId = Convert.toStr(StpUtil.getExtra(tokenValue, LoginSaasHelper.TENANT_KEY));
TenantHelper.dynamic(tenantId, () -> {
RedisUtils.deleteObject(CacheConstants.ONLINE_TOKEN_KEY + tokenValue);
});
log.info("user doReplaced, useId:{}, token:{}", loginId, tokenValue);
}
/**
* 每次被封禁时触发
*/
@Override
public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
}
/**
* 每次被解封时触发
*/
@Override
public void doUntieDisable(String loginType, Object loginId, String service) {
}
/**
* 每次打开二级认证时触发
*/
@Override
public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
}
/**
* 每次创建Session时触发
*/
@Override
public void doCloseSafe(String loginType, String tokenValue, String service) {
}
/**
* 每次创建Session时触发
*/
@Override
public void doCreateSession(String id) {
}
/**
* 每次注销Session时触发
*/
@Override
public void doLogoutSession(String id) {
}
/**
* 每次Token续期时触发
*/
@Override
public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
}
}

View File

@ -0,0 +1,46 @@
package com.pj.auth.properties;
import lombok.Data;
import com.pj.auth.enums.CaptchaCategory;
import com.pj.auth.enums.CaptchaType;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* 验证码配置
*
* @author ruoyi
*/
@Data
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "security.captcha")
public class CaptchaProperties {
/**
* 验证码类型
*/
private CaptchaType type;
/**
* 验证码类别
*/
private CaptchaCategory category;
/**
* 数字验证码位数
*/
private Integer numberLength;
/**
* 字符验证码长度
*/
private Integer charLength;
/**
* 验证码开关
*/
private Boolean enabled;
}

View File

@ -0,0 +1,29 @@
package com.pj.auth.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Configuration;
/**
* 用户密码配置
*
* @author Lion Li
*/
@Data
@Configuration
@RefreshScope
@ConfigurationProperties(prefix = "user.password")
public class UserPasswordProperties {
/**
* 密码最大错误次数
*/
private Integer maxRetryCount;
/**
* 密码锁定时间默认10分钟
*/
private Integer lockTime;
}

View File

@ -1,10 +1,9 @@
package com.pj.service; package com.pj.auth.service;
import com.pj.auth.domain.vo.LoginVo;
import com.pj.model.vo.LoginVo;
import com.pj.model.vo.SysClientVo;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.system.api.domain.vo.RemoteClientVo;
/** /**
* 授权策略 * 授权策略
@ -23,9 +22,9 @@ public interface IAuthStrategy {
* @param grantType 授权类型 * @param grantType 授权类型
* @return 登录验证信息 * @return 登录验证信息
*/ */
static LoginVo login(String body, SysClientVo client, String grantType) { static LoginVo login(String body, RemoteClientVo client, String grantType) {
// 授权类型和客户端id // 授权类型和客户端id
String beanName = grantType + IAuthStrategy.BASE_NAME; String beanName = grantType + BASE_NAME;
if (!SpringUtils.containsBean(beanName)) { if (!SpringUtils.containsBean(beanName)) {
throw new ServiceException("授权类型不正确!"); throw new ServiceException("授权类型不正确!");
} }
@ -40,6 +39,6 @@ public interface IAuthStrategy {
* @param client 授权管理视图对象 * @param client 授权管理视图对象
* @return 登录验证信息 * @return 登录验证信息
*/ */
LoginVo login(String body, SysClientVo client); LoginVo login(String body, RemoteClientVo client);
} }

View File

@ -0,0 +1,269 @@
package com.pj.auth.service;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.lock.annotation.Lock4j;
import com.pj.auth.form.RegisterBody;
import com.pj.auth.properties.CaptchaProperties;
import com.pj.auth.properties.UserPasswordProperties;
import com.pj.helper.LoginSaasHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthUser;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.constant.CacheConstants;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.enums.TenantStatus;
import org.dromara.common.core.enums.UserType;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.CaptchaException;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.log.event.LogininforEvent;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.tenant.exception.TenantException;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.api.RemoteSocialService;
import org.dromara.system.api.RemoteTenantService;
import org.dromara.system.api.RemoteUserService;
import org.dromara.system.api.domain.bo.RemoteSocialBo;
import org.dromara.system.api.domain.bo.RemoteUserBo;
import org.dromara.system.api.domain.vo.RemoteSocialVo;
import org.dromara.system.api.domain.vo.RemoteTenantVo;
import org.dromara.system.api.model.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.function.Supplier;
/**
* 登录校验方法
*
* @author ruoyi
*/
@RequiredArgsConstructor
@Service
@Slf4j
public class SysLoginService {
@DubboReference
private RemoteUserService remoteUserService;
@DubboReference
private RemoteTenantService remoteTenantService;
@DubboReference
private RemoteSocialService remoteSocialService;
@Autowired
private UserPasswordProperties userPasswordProperties;
@Autowired
private final CaptchaProperties captchaProperties;
/**
* 绑定第三方用户
*
* @param authUserData 授权响应实体
*/
@Lock4j
public void socialRegister(AuthUser authUserData) {
String authId = authUserData.getSource() + authUserData.getUuid();
// 第三方用户信息
RemoteSocialBo bo = BeanUtil.toBean(authUserData, RemoteSocialBo.class);
BeanUtil.copyProperties(authUserData.getToken(), bo);
Long userId = LoginSaasHelper.getUserId();
bo.setUserId(userId);
bo.setAuthId(authId);
bo.setOpenId(authUserData.getUuid());
bo.setUserName(authUserData.getUsername());
bo.setNickName(authUserData.getNickname());
List<RemoteSocialVo> checkList = remoteSocialService.selectByAuthId(authId);
if (CollUtil.isNotEmpty(checkList)) {
throw new ServiceException("此三方账号已经被绑定!");
}
// 查询是否已经绑定用户
RemoteSocialBo params = new RemoteSocialBo();
params.setUserId(userId);
params.setSource(bo.getSource());
List<RemoteSocialVo> list = remoteSocialService.queryList(params);
if (CollUtil.isEmpty(list)) {
// 没有绑定用户, 新增用户信息
remoteSocialService.insertByBo(bo);
} else {
// 更新用户信息
bo.setId(list.get(0).getId());
remoteSocialService.updateByBo(bo);
// 如果要绑定的平台账号已经被绑定过了 是否抛异常自行决断
// throw new ServiceException("此平台账号已经被绑定!");
}
}
/**
* 退出登录
*/
public void logout() {
try {
LoginUser loginUser = LoginSaasHelper.getLoginUser();
if (ObjectUtil.isNull(loginUser)) {
return;
}
if (TenantHelper.isEnable() && LoginSaasHelper.isSuperAdmin()) {
// 超级管理员 登出清除动态租户
TenantHelper.clearDynamic();
}
recordLogininfor(loginUser.getTenantId(), loginUser.getUsername(), Constants.LOGOUT, MessageUtils.message("user.logout.success"));
} 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();
boolean captchaEnabled = captchaProperties.getEnabled();
// 验证码开关
if (captchaEnabled) {
validateCaptcha(tenantId, username, registerBody.getCode(), registerBody.getUuid());
}
// 注册用户信息
RemoteUserBo remoteUserBo = new RemoteUserBo();
remoteUserBo.setTenantId(tenantId);
remoteUserBo.setUserName(username);
remoteUserBo.setNickName(username);
remoteUserBo.setPassword(BCrypt.hashpw(password));
remoteUserBo.setUserType(userType);
boolean regFlag = remoteUserService.registerUserInfo(remoteUserBo);
if (!regFlag) {
throw new UserException("user.register.error");
}
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.register.success"));
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
public void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
public void recordLogininfor(String tenantId, String username, String status, String message) {
// 封装对象
LogininforEvent logininforEvent = new LogininforEvent();
logininforEvent.setTenantId(tenantId);
logininforEvent.setUsername(username);
logininforEvent.setStatus(status);
logininforEvent.setMessage(message);
SpringUtils.context().publishEvent(logininforEvent);
}
/**
* 登录校验
*/
public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) {
String errorKey = CacheConstants.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) {
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
}
if (supplier.get()) {
// 错误次数递增
errorNumber++;
RedisUtils.setCacheObject(errorKey, errorNumber, Duration.ofMinutes(lockTime));
// 达到规定错误次数 则锁定登录
if (errorNumber >= maxRetryCount) {
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitExceed(), maxRetryCount, lockTime));
throw new UserException(loginType.getRetryLimitExceed(), maxRetryCount, lockTime);
} else {
// 未达到规定错误次数
recordLogininfor(tenantId, username, loginFail, MessageUtils.message(loginType.getRetryLimitCount(), errorNumber));
throw new UserException(loginType.getRetryLimitCount(), errorNumber);
}
}
// 登录成功 清空错误次数
RedisUtils.deleteObject(errorKey);
}
/**
* 校验租户
*
* @param tenantId 租户ID
*/
public void checkTenant(String tenantId) {
if (!TenantHelper.isEnable()) {
return;
}
if (TenantConstants.DEFAULT_TENANT_ID.equals(tenantId)) {
return;
}
if (StringUtils.isBlank(tenantId)) {
throw new TenantException("tenant.number.not.blank");
}
RemoteTenantVo tenant = remoteTenantService.queryByTenantId(tenantId);
if (ObjectUtil.isNull(tenant)) {
log.info("登录租户:{} 不存在.", tenantId);
throw new TenantException("tenant.not.exists");
} else if (TenantStatus.DISABLE.getCode().equals(tenant.getStatus())) {
log.info("登录租户:{} 已被停用.", tenantId);
throw new TenantException("tenant.blocked");
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())
&& new Date().after(tenant.getExpireTime())) {
log.info("登录租户:{} 已超过有效期.", tenantId);
throw new TenantException("tenant.expired");
}
}
}

View File

@ -0,0 +1,86 @@
package com.pj.auth.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import com.pj.auth.domain.vo.LoginVo;
import com.pj.auth.form.EmailLoginBody;
import com.pj.auth.service.IAuthStrategy;
import com.pj.auth.service.SysLoginService;
import com.pj.helper.LoginSaasHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.api.RemoteUserService;
import org.dromara.system.api.domain.vo.RemoteClientVo;
import org.dromara.system.api.model.LoginUser;
import org.springframework.stereotype.Service;
/**
* 邮件认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("email" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class EmailAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {
EmailLoginBody loginBody = JsonUtils.parseObject(body, EmailLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String email = loginBody.getEmail();
String emailCode = loginBody.getEmailCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
LoginUser user = remoteUserService.getUserInfoByEmail(email, tenantId);
loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUsername(), () -> !validateEmailCode(tenantId, email, emailCode));
return user;
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginSaasHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginSaasHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验邮箱验证码
*/
private boolean validateEmailCode(String tenantId, String email, String emailCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
if (StringUtils.isBlank(code)) {
loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(emailCode);
}
}

View File

@ -0,0 +1,107 @@
package com.pj.auth.service.impl;
import cn.dev33.satoken.secure.BCrypt;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import com.pj.auth.domain.vo.LoginVo;
import com.pj.auth.form.PasswordLoginBody;
import com.pj.auth.properties.CaptchaProperties;
import com.pj.auth.service.IAuthStrategy;
import com.pj.auth.service.SysLoginService;
import com.pj.helper.LoginSaasHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.exception.user.CaptchaException;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.api.RemoteUserService;
import org.dromara.system.api.domain.vo.RemoteClientVo;
import org.dromara.system.api.model.LoginUser;
import org.springframework.stereotype.Service;
/**
* 密码认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("password" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class PasswordAuthStrategy implements IAuthStrategy {
private final CaptchaProperties captchaProperties;
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {
PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String username = loginBody.getUsername();
String password = loginBody.getPassword();
String code = loginBody.getCode();
String uuid = loginBody.getUuid();
// 验证码开关
if (captchaProperties.getEnabled()) {
validateCaptcha(tenantId, username, code, uuid);
}
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
LoginUser user = remoteUserService.getUserInfo(username, tenantId);
loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
return user;
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginSaasHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginSaasHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
private void validateCaptcha(String tenantId, String username, String code, String uuid) {
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey);
if (captcha == null) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
if (!code.equalsIgnoreCase(captcha)) {
loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException();
}
}
}

View File

@ -0,0 +1,86 @@
package com.pj.auth.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import com.pj.auth.domain.vo.LoginVo;
import com.pj.auth.form.SmsLoginBody;
import com.pj.auth.service.IAuthStrategy;
import com.pj.auth.service.SysLoginService;
import com.pj.helper.LoginSaasHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.api.RemoteUserService;
import org.dromara.system.api.domain.vo.RemoteClientVo;
import org.dromara.system.api.model.LoginUser;
import org.springframework.stereotype.Service;
/**
* 短信认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("sms" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class SmsAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {
SmsLoginBody loginBody = JsonUtils.parseObject(body, SmsLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String phonenumber = loginBody.getPhonenumber();
String smsCode = loginBody.getSmsCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
LoginUser user = remoteUserService.getUserInfoByPhonenumber(phonenumber, tenantId);
loginService.checkLogin(LoginType.SMS, tenantId, user.getUsername(), () -> !validateSmsCode(tenantId, phonenumber, smsCode));
return user;
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginSaasHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginSaasHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
/**
* 校验短信验证码
*/
private boolean validateSmsCode(String tenantId, String phonenumber, String smsCode) {
String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + phonenumber);
if (StringUtils.isBlank(code)) {
loginService.recordLogininfor(tenantId, phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException();
}
return code.equals(smsCode);
}
}

View File

@ -0,0 +1,114 @@
package com.pj.auth.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import com.pj.helper.LoginSaasHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthUser;
import org.apache.dubbo.config.annotation.DubboReference;
import com.pj.auth.domain.vo.LoginVo;
import com.pj.auth.form.SocialLoginBody;
import com.pj.auth.service.IAuthStrategy;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.social.config.properties.SocialProperties;
import org.dromara.common.social.utils.SocialUtils;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.system.api.RemoteSocialService;
import org.dromara.system.api.RemoteUserService;
import org.dromara.system.api.domain.vo.RemoteClientVo;
import org.dromara.system.api.domain.vo.RemoteSocialVo;
import org.dromara.system.api.model.LoginUser;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
/**
* 第三方授权策略
*
* @author thiszhc is 三三
*/
@Slf4j
@Service("social" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class SocialAuthStrategy implements IAuthStrategy {
private final SocialProperties socialProperties;
@DubboReference
private RemoteSocialService remoteSocialService;
@DubboReference
private RemoteUserService remoteUserService;
/**
* 登录-第三方授权登录
*
* @param body 登录信息
* @param client 客户端信息
*/
@Override
public LoginVo login(String body, RemoteClientVo client) {
SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
ValidatorUtils.validate(loginBody);
AuthResponse<AuthUser> response = SocialUtils.loginAuth(
loginBody.getSource(), loginBody.getSocialCode(),
loginBody.getSocialState(), socialProperties);
if (!response.ok()) {
throw new ServiceException(response.getMsg());
}
AuthUser authUserData = response.getData();
if ("GITEE".equals(authUserData.getSource())) {
// 如用户使用 gitee 登录顺手 star 给作者一点支持 拒绝白嫖
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Cloud-Plus")
.formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken()))
.executeAsync();
}
List<RemoteSocialVo> list = remoteSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
if (CollUtil.isEmpty(list)) {
throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
}
RemoteSocialVo socialVo;
if (TenantHelper.isEnable()) {
Optional<RemoteSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
if (opt.isEmpty()) {
throw new ServiceException("对不起,你没有权限登录当前租户!");
}
socialVo = opt.get();
} else {
socialVo = list.get(0);
}
LoginUser loginUser = remoteUserService.getUserInfo(socialVo.getUserId(), socialVo.getTenantId());
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginSaasHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginSaasHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
return loginVo;
}
}

View File

@ -0,0 +1,69 @@
package com.pj.auth.service.impl;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import com.pj.auth.domain.vo.LoginVo;
import com.pj.auth.form.XcxLoginBody;
import com.pj.auth.service.IAuthStrategy;
import com.pj.auth.service.SysLoginService;
import com.pj.helper.LoginSaasHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.system.api.RemoteUserService;
import org.dromara.system.api.domain.vo.RemoteClientVo;
import org.dromara.system.api.model.XcxLoginUser;
import org.springframework.stereotype.Service;
/**
* 邮件认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("xcx" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class XcxAuthStrategy implements IAuthStrategy {
private final SysLoginService loginService;
@DubboReference
private RemoteUserService remoteUserService;
@Override
public LoginVo login(String body, RemoteClientVo client) {
XcxLoginBody loginBody = JsonUtils.parseObject(body, XcxLoginBody.class);
ValidatorUtils.validate(loginBody);
// xcxCode 小程序调用 wx.login 授权后获取
String xcxCode = loginBody.getXcxCode();
// 多个小程序识别使用
String appid = loginBody.getAppid();
// todo 以下自行实现
// 校验 appid + appsrcret + xcxCode 调用登录凭证校验接口 获取 session_key openid
String openid = "";
XcxLoginUser loginUser = remoteUserService.getUserInfoByOpenid(openid);
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginSaasHelper.CLIENT_KEY, client.getClientId());
// 生成token
LoginSaasHelper.login(loginUser, model);
LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId());
loginVo.setOpenid(openid);
return loginVo;
}
}

View File

@ -1,41 +0,0 @@
package com.pj.config;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpLogic;
import org.dromara.common.satoken.core.service.SaPermissionImpl;
import org.dromara.common.satoken.handler.SaTokenExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* sa-token 配置
*
* @author Lion Li
*/
@Configuration
public class SaTokenConfig {
@Bean
public StpLogic getStpLogicJwt() {
// Sa-Token 整合 jwt (简单模式)
return new StpLogicJwtForSimple();
}
/**
* 权限接口实现(使用bean注入方便用户替换)
*/
@Bean
public StpInterface stpInterface() {
return new SaPermissionImpl();
}
/**
* 异常处理器
*/
@Bean
public SaTokenExceptionHandler saTokenExceptionHandler() {
return new SaTokenExceptionHandler();
}
}

View File

@ -0,0 +1,80 @@
package com.pj.config;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpLogic;
import com.pj.core.service.SaPermissionImpl;
import com.pj.handler.SaTokenExceptionHandler;
import com.pj.helper.LoginSaasHelper;
import org.dromara.common.log.aspect.CommonLogTenantIntercept;
import org.dromara.system.api.model.LoginUser;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* sa-token 配置
*
* @author Lion Li
*/
@Configuration
public class SaTokenSsoConfig {
@Bean
public StpLogic getStpLogicJwt() {
// Sa-Token 整合 jwt (简单模式)
return new StpLogicJwtForSimple();
}
/**
* 权限接口实现(使用bean注入方便用户替换)
*/
@Bean
public StpInterface stpInterface() {
return new SaPermissionImpl();
}
/**
* 异常处理器
*/
@Bean
public SaTokenExceptionHandler saTokenExceptionHandler() {
return new SaTokenExceptionHandler();
}
/**
* 日志拦截器记录tenant
*
* @return
*/
@Bean
public CommonLogTenantIntercept commonLogTenantIntercept() {
return new CommonLogTenantIntercept() {
@Override
public Object getTenantId() {
return LoginSaasHelper.getTenantId();
}
@Override
public String getUsername() {
LoginUser loginUser = LoginSaasHelper.getLoginUser();
if (loginUser != null) {
return loginUser.getUsername();
}
return null;
}
@Override
public String getDeptName() {
LoginUser loginUser = LoginSaasHelper.getLoginUser();
if (loginUser != null) {
return loginUser.getDeptName();
}
return null;
}
};
}
}

View File

@ -5,13 +5,26 @@ import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.sso.config.SaSsoServerConfig; import cn.dev33.satoken.sso.config.SaSsoServerConfig;
import cn.dev33.satoken.sso.processor.SaSsoServerProcessor; import cn.dev33.satoken.sso.processor.SaSsoServerProcessor;
import cn.dev33.satoken.stp.StpUtil; import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil; import com.pj.auth.domain.vo.LoginVo;
import com.pj.model.vo.LoginVo; import com.pj.auth.service.IAuthStrategy;
import com.pj.model.vo.SysClientVo; import com.pj.auth.service.SysLoginService;
import com.pj.service.IAuthStrategy; import com.pj.helper.LoginSaasHelper;
import org.dromara.common.core.domain.model.PasswordLoginBody; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.model.LoginBody;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.system.api.RemoteClientService;
import org.dromara.system.api.domain.vo.RemoteClientVo;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
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.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
@ -22,7 +35,13 @@ import org.springframework.web.servlet.ModelAndView;
* @author click33 * @author click33
*/ */
@RestController @RestController
@RequiredArgsConstructor
@Slf4j
public class SsoServerController { public class SsoServerController {
private final SysLoginService sysLoginService;
@DubboReference
private RemoteClientService remoteClientService;
/** /**
* SSO-Server端处理所有SSO相关请求 * SSO-Server端处理所有SSO相关请求
@ -41,6 +60,32 @@ public class SsoServerController {
return SaSsoServerProcessor.instance.dister(); return SaSsoServerProcessor.instance.dister();
} }
@PostMapping("/sso/doLogin")
public Object ssoDoLoginRequest(@RequestBody String body) {
LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class);
ValidatorUtils.validate(loginBody);
// 授权类型和客户端id
String clientId = loginBody.getClientId();
String grantType = loginBody.getGrantType();
RemoteClientVo clientVo = remoteClientService.queryByClientId(clientId);
// 查询不到 client client 内不包含 grantType
if (ObjectUtil.isNull(clientVo) || !StringUtils.contains(clientVo.getGrantType(), grantType)) {
log.info("客户端id: {} 认证类型:{} 异常!.", clientId, grantType);
return R.fail(MessageUtils.message("auth.grant.type.error"));
} else if (!UserConstants.NORMAL.equals(clientVo.getStatus())) {
return R.fail(MessageUtils.message("auth.grant.type.blocked"));
}
// 校验租户
sysLoginService.checkTenant(loginBody.getTenantId());
// 登录
LoginVo loginVo = IAuthStrategy.login(body, clientVo, grantType);
Long userId = LoginSaasHelper.getUserId();
log.info(userId + "欢迎登录RuoYi-Cloud-Plus微服务管理系统");
return R.ok(loginVo);
}
// 配置SSO相关参数 // 配置SSO相关参数
@Autowired @Autowired
private void configSso(SaSsoServerConfig ssoServer) { private void configSso(SaSsoServerConfig ssoServer) {
@ -49,38 +94,6 @@ public class SsoServerController {
ssoServer.notLoginView = () -> { ssoServer.notLoginView = () -> {
return new ModelAndView("sa-login.html"); return new ModelAndView("sa-login.html");
}; };
// 配置登录处理函数
ssoServer.doLoginHandle = (name, pwd) -> {
// 此处仅做模拟登录真实环境应该查询数据进行登录
if ("sa".equals(name) && "123456".equals(pwd)) {
PasswordLoginBody body = new PasswordLoginBody();
body.setUsername("sa");
body.setPassword("123456");
body.setClientId("ClientId");
body.setGrantType("password");
body.setTenantId("1");
body.setCode("code1");
body.setUuid("qweqw");
SysClientVo client = new SysClientVo();
client.setId(1L);
client.setClientId("ClientId1");
client.setClientKey("setClientKey");
client.setClientSecret("setClientSecret");
client.setGrantType("password");
client.setDeviceType("pc");
client.setActiveTimeout(1800L);
client.setTimeout(300L);
client.setStatus("0");
LoginVo loginVo = IAuthStrategy.login(JSONUtil.toJsonStr(body), client, body.getGrantType());
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
}
return SaResult.error("登录失败!");
};
} }
} }

View File

@ -0,0 +1,172 @@
package com.pj.core.dao;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.util.SaFoxUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.dromara.common.redis.utils.RedisUtils;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Sa-Token持久层接口(使用框架自带RedisUtils实现 协议统一)
* <p>
* 采用 caffeine + redis 多级缓存 优化并发查询效率
*
* @author Lion Li
*/
public class PlusSaTokenDao implements SaTokenDao {
private static final Cache<String, Object> CAFFEINE = Caffeine.newBuilder()
// 设置最后一次写入或访问后经过固定时间过期
.expireAfterWrite(5, TimeUnit.SECONDS)
// 初始的缓存空间大小
.initialCapacity(100)
// 缓存的最大条数
.maximumSize(1000)
.build();
/**
* 获取Value如无返空
*/
@Override
public String get(String key) {
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
return (String) o;
}
/**
* 写入Value并设定存活时间 (单位: )
*/
@Override
public void set(String key, String value, long timeout) {
if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
return;
}
// 判断是否为永不过期
if (timeout == NEVER_EXPIRE) {
RedisUtils.setCacheObject(key, value);
} else {
RedisUtils.setCacheObject(key, value, Duration.ofSeconds(timeout));
}
CAFFEINE.invalidate(key);
}
/**
* 修修改指定key-value键值对 (过期时间不变)
*/
@Override
public void update(String key, String value) {
if (RedisUtils.hasKey(key)) {
RedisUtils.setCacheObject(key, value, true);
CAFFEINE.invalidate(key);
}
}
/**
* 删除Value
*/
@Override
public void delete(String key) {
RedisUtils.deleteObject(key);
}
/**
* 获取Value的剩余存活时间 (单位: )
*/
@Override
public long getTimeout(String key) {
long timeout = RedisUtils.getTimeToLive(key);
return timeout < 0 ? timeout : timeout / 1000;
}
/**
* 修改Value的剩余存活时间 (单位: )
*/
@Override
public void updateTimeout(String key, long timeout) {
RedisUtils.expire(key, Duration.ofSeconds(timeout));
}
/**
* 获取Object如无返空
*/
@Override
public Object getObject(String key) {
Object o = CAFFEINE.get(key, k -> RedisUtils.getCacheObject(key));
return o;
}
/**
* 写入Object并设定存活时间 (单位: )
*/
@Override
public void setObject(String key, Object object, long timeout) {
if (timeout == 0 || timeout <= NOT_VALUE_EXPIRE) {
return;
}
// 判断是否为永不过期
if (timeout == NEVER_EXPIRE) {
RedisUtils.setCacheObject(key, object);
} else {
RedisUtils.setCacheObject(key, object, Duration.ofSeconds(timeout));
}
CAFFEINE.invalidate(key);
}
/**
* 更新Object (过期时间不变)
*/
@Override
public void updateObject(String key, Object object) {
if (RedisUtils.hasKey(key)) {
RedisUtils.setCacheObject(key, object, true);
CAFFEINE.invalidate(key);
}
}
/**
* 删除Object
*/
@Override
public void deleteObject(String key) {
RedisUtils.deleteObject(key);
}
/**
* 获取Object的剩余存活时间 (单位: )
*/
@Override
public long getObjectTimeout(String key) {
long timeout = RedisUtils.getTimeToLive(key);
return timeout < 0 ? timeout : timeout / 1000;
}
/**
* 修改Object的剩余存活时间 (单位: )
*/
@Override
public void updateObjectTimeout(String key, long timeout) {
RedisUtils.expire(key, Duration.ofSeconds(timeout));
}
/**
* 搜索数据
*/
@SuppressWarnings("unchecked")
@Override
public List<String> searchData(String prefix, String keyword, int start, int size, boolean sortType) {
String keyStr = prefix + "*" + keyword + "*";
return (List<String>) CAFFEINE.get(keyStr, k -> {
Collection<String> keys = RedisUtils.keys(keyStr);
List<String> list = new ArrayList<>(keys);
return SaFoxUtil.searchList(list, start, size, sortType);
});
}
}

View File

@ -0,0 +1,47 @@
package com.pj.core.service;
import cn.dev33.satoken.stp.StpInterface;
import com.pj.helper.LoginSaasHelper;
import org.dromara.common.core.enums.UserType;
import org.dromara.system.api.model.LoginUser;
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) {
LoginUser loginUser = LoginSaasHelper.getLoginUser();
UserType userType = UserType.getUserType(loginUser.getUserType());
if (userType == UserType.SYS_USER) {
return new ArrayList<>(loginUser.getMenuPermission());
} else if (userType == UserType.APP_USER) {
// 其他端 自行根据业务编写
}
return new ArrayList<>();
}
/**
* 获取角色权限列表
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
LoginUser loginUser = LoginSaasHelper.getLoginUser();
UserType userType = UserType.getUserType(loginUser.getUserType());
if (userType == UserType.SYS_USER) {
return new ArrayList<>(loginUser.getRolePermission());
} else if (userType == UserType.APP_USER) {
// 其他端 自行根据业务编写
}
return new ArrayList<>();
}
}

View File

@ -0,0 +1,52 @@
package com.pj.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 jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
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, "认证失败,无法访问系统资源");
}
}

View File

@ -1,202 +0,0 @@
//package com.pj.utils;
//
//import cn.dev33.satoken.session.SaSession;
//import cn.dev33.satoken.stp.SaLoginModel;
//import cn.dev33.satoken.stp.StpUtil;
//import cn.hutool.core.collection.CollUtil;
//import cn.hutool.core.convert.Convert;
//import cn.hutool.core.util.ObjectUtil;
//import lombok.AccessLevel;
//import lombok.NoArgsConstructor;
//import org.dromara.common.core.constant.TenantConstants;
//import org.dromara.common.core.constant.UserConstants;
//import org.dromara.common.core.domain.model.LoginUser;
//import org.dromara.common.core.enums.UserType;
//
//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, SaLoginModel model) {
// model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
// 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);
// }
//
// /**
// * 获取用户(多级缓存)
// */
// public static LoginUser getLoginUser() {
// SaSession session = StpUtil.getTokenSession();
// if (ObjectUtil.isNull(session)) {
// return null;
// }
// return (LoginUser) session.get(LOGIN_USER_KEY);
// }
//
// /**
// * 获取用户基于token
// */
// public static LoginUser getLoginUser(String token) {
// SaSession session = StpUtil.getTokenSessionByToken(token);
// if (ObjectUtil.isNull(session)) {
// return null;
// }
// return (LoginUser) session.get(LOGIN_USER_KEY);
// }
//
// /**
// * 获取用户id
// */
// public static Long getUserId() {
// return Convert.toLong(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 UserConstants.SUPER_ADMIN_ID.equals(userId);
// }
//
// /**
// * 是否为超级管理员
// *
// * @return 结果
// */
// public static boolean isSuperAdmin() {
// return isSuperAdmin(getUserId());
// }
//
// /**
// * 是否为租户管理员
// *
// * @param rolePermission 角色权限标识组
// * @return 结果
// */
// public static boolean isTenantAdmin(Set<String> rolePermission) {
// if (CollUtil.isEmpty(rolePermission)) {
// return false;
// }
// return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY);
// }
//
// /**
// * 是否为租户管理员
// *
// * @return 结果
// */
// public static boolean isTenantAdmin() {
// return Convert.toBool(isTenantAdmin(getLoginUser().getRolePermission()));
// }
//
// /**
// * 检查当前用户是否已登录
// *
// * @return 结果
// */
// public static boolean isLogin() {
// try {
// return getLoginUser() != null;
// } catch (Exception e) {
// return false;
// }
// }
//
//}

View File

@ -0,0 +1,202 @@
package com.pj.helper;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.dromara.common.core.constant.TenantConstants;
import org.dromara.common.core.constant.UserConstants;
import org.dromara.common.core.enums.UserType;
import org.dromara.system.api.model.LoginUser;
import java.util.Set;
/**
* 登录鉴权助手
* <p>
* user_type 用户类型 同一个用户表 可以有多种用户类型 例如 pc,app
* deivce 设备类型 同一个用户类型 可以有 多种设备类型 例如 web,ios
* 可以组成 用户类型与设备类型多对多的 权限灵活控制
* <p>
* 多用户体系 针对 多种用户类型 但权限控制不一致
* 可以组成 多用户类型表与多设备类型 分别控制权限
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class LoginSaasHelper {
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, SaLoginModel model) {
model = ObjectUtil.defaultIfNull(model, new SaLoginModel());
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);
}
/**
* 获取用户(多级缓存)
*/
public static LoginUser getLoginUser() {
SaSession session = StpUtil.getTokenSession();
if (ObjectUtil.isNull(session)) {
return null;
}
return (LoginUser) session.get(LOGIN_USER_KEY);
}
/**
* 获取用户基于token
*/
public static LoginUser getLoginUser(String token) {
SaSession session = StpUtil.getTokenSessionByToken(token);
if (ObjectUtil.isNull(session)) {
return null;
}
return (LoginUser) session.get(LOGIN_USER_KEY);
}
/**
* 获取用户id
*/
public static Long getUserId() {
return Convert.toLong(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 UserConstants.SUPER_ADMIN_ID.equals(userId);
}
/**
* 是否为超级管理员
*
* @return 结果
*/
public static boolean isSuperAdmin() {
return isSuperAdmin(getUserId());
}
/**
* 是否为租户管理员
*
* @param rolePermission 角色权限标识组
* @return 结果
*/
public static boolean isTenantAdmin(Set<String> rolePermission) {
if (CollUtil.isEmpty(rolePermission)) {
return false;
}
return rolePermission.contains(TenantConstants.TENANT_ADMIN_ROLE_KEY);
}
/**
* 是否为租户管理员
*
* @return 结果
*/
public static boolean isTenantAdmin() {
return Convert.toBool(isTenantAdmin(getLoginUser().getRolePermission()));
}
/**
* 检查当前用户是否已登录
*
* @return 结果
*/
public static boolean isLogin() {
try {
return getLoginUser() != null;
} catch (Exception e) {
return false;
}
}
}

View File

@ -1,40 +0,0 @@
package com.pj.model;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 角色
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class RoleDTO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 角色ID
*/
private Long roleId;
/**
* 角色名称
*/
private String roleName;
/**
* 角色权限
*/
private String roleKey;
/**
* 数据范围1所有数据权限2自定义数据权限3本部门数据权限4本部门及以下数据权限5仅本人数据权限
*/
private String dataScope;
}

View File

@ -1,74 +0,0 @@
package com.pj.model.vo;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 授权管理视图对象 sys_client
*
* @author Michelle.Chung
* @date 2023-05-15
*/
@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;
}

View File

@ -1,80 +0,0 @@
package com.pj.model.vo;
import lombok.Data;
import org.dromara.common.core.constant.UserConstants;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 角色信息视图对象 sys_role
*
* @author Michelle.Chung
*/
@Data
public class SysRoleVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 角色ID
*/
private Long roleId;
/**
* 角色名称
*/
private String roleName;
/**
* 角色权限字符串
*/
private String roleKey;
/**
* 显示顺序
*/
private Integer roleSort;
/**
* 数据范围1全部数据权限 2自定数据权限 3本部门数据权限 4本部门及以下数据权限
*/
private String dataScope;
/**
* 菜单树选择项是否关联显示
*/
private Boolean menuCheckStrictly;
/**
* 部门树选择项是否关联显示
*/
private Boolean deptCheckStrictly;
/**
* 角色状态0正常 1停用
*/
private String status;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
private Date createTime;
/**
* 用户是否存在此角色标识 默认不存在
*/
private boolean flag = false;
public boolean isSuperAdmin() {
return UserConstants.SUPER_ADMIN_ID.equals(this.roleId);
}
}

View File

@ -1,131 +0,0 @@
package com.pj.model.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 用户信息视图对象 sys_user
*
* @author Michelle.Chung
*/
@Data
public class SysUserVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 租户ID
*/
private String tenantId;
/**
* 部门ID
*/
private Long deptId;
/**
* 用户账号
*/
private String userName;
/**
* 用户昵称
*/
private String nickName;
/**
* 用户类型sys_user系统用户
*/
private String userType;
/**
* 用户邮箱
*/
private String email;
/**
* 手机号码
*/
private String phonenumber;
/**
* 用户性别0男 1女 2未知
*/
private String sex;
/**
* 头像地址
*/
private Long avatar;
/**
* 密码
*/
@JsonIgnore
@JsonProperty
private String password;
/**
* 帐号状态0正常 1停用
*/
private String status;
/**
* 最后登录IP
*/
private String loginIp;
/**
* 最后登录时间
*/
private Date loginDate;
/**
* 备注
*/
private String remark;
/**
* 创建时间
*/
private Date createTime;
/**
* 部门名
*/
private String deptName;
/**
* 角色对象
*/
private List<SysRoleVo> roles;
/**
* 角色组
*/
private Long[] roleIds;
/**
* 岗位组
*/
private Long[] postIds;
/**
* 数据权限 当前角色ID
*/
private Long roleId;
}

View File

@ -1,121 +1,100 @@
package com.pj.service.impl; //package com.pj.service.impl;
//
import cn.dev33.satoken.stp.SaLoginModel; //import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil; //import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil; //import cn.hutool.json.JSONUtil;
import cn.hutool.core.lang.Opt; //import lombok.RequiredArgsConstructor;
import cn.hutool.core.util.ObjectUtil; //import lombok.extern.slf4j.Slf4j;
import cn.hutool.json.JSONUtil; //import org.dromara.common.core.domain.model.EmailLoginBody;
import com.pj.model.vo.LoginVo; //import org.dromara.common.core.domain.model.LoginUser;
import com.pj.model.vo.SysClientVo; //import org.dromara.common.core.utils.ValidatorUtils;
import com.pj.model.vo.SysRoleVo; //import org.dromara.common.tenant.helper.TenantHelper;
import com.pj.model.vo.SysUserVo; //import org.springframework.stereotype.Service;
import com.pj.service.IAuthStrategy; //
import lombok.RequiredArgsConstructor; ///**
import lombok.extern.slf4j.Slf4j; // * 邮件认证策略
import org.dromara.common.core.constant.Constants; // *
import org.dromara.common.core.constant.GlobalConstants; // * @author Michelle.Chung
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.EmailLoginBody;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.enums.UserStatus;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 邮件认证策略
*
* @author Michelle.Chung
*/
@Slf4j
@Service("email" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class EmailAuthStrategy implements IAuthStrategy {
//后续改为远程的
// private final SysLoginService loginService;
// private final SysUserMapper userMapper;
@Override
public LoginVo login(String body, SysClientVo client) {
EmailLoginBody loginBody = JSONUtil.toBean(body, EmailLoginBody.class);
ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId();
String email = loginBody.getEmail();
String emailCode = loginBody.getEmailCode();
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
// SysUserVo user = loadUserByEmail(email);
// loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
// 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
// return loginService.buildLoginUser(user);
return buildLoginUser();
});
loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel();
model.setDevice(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;
}
/**
* 构建登录用户
*/
private LoginUser buildLoginUser() {
LoginUser loginUser = new LoginUser();
loginUser.setTenantId("1");
loginUser.setUserId(1L);
loginUser.setDeptId(2L);
loginUser.setUsername("测试用户登录名");
loginUser.setNickname("测试用户名称");
loginUser.setUserType("用户类型1");
return loginUser;
}
// /**
// * 校验邮箱验证码
// */ // */
// private boolean validateEmailCode(String tenantId, String email, String emailCode) { //@Slf4j
// String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email); //@Service("email" + IAuthStrategy.BASE_NAME)
// if (StringUtils.isBlank(code)) { //@RequiredArgsConstructor
// loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")); //public class EmailAuthStrategy implements IAuthStrategy {
// throw new CaptchaExpireException(); //
// } // //后续改为远程的
// return code.equals(emailCode); //// private final SysLoginService loginService;
//// private final SysUserMapper userMapper;
//
// @Override
// public LoginVo login(String body, SysClientVo client) {
// EmailLoginBody loginBody = JSONUtil.toBean(body, EmailLoginBody.class);
// ValidatorUtils.validate(loginBody);
// String tenantId = loginBody.getTenantId();
// String email = loginBody.getEmail();
// String emailCode = loginBody.getEmailCode();
// LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
//// SysUserVo user = loadUserByEmail(email);
//// loginService.checkLogin(LoginType.EMAIL, tenantId, user.getUserName(), () -> !validateEmailCode(tenantId, email, emailCode));
// // 此处可根据登录用户的数据不同 自行创建 loginUser 属性不够用继承扩展就行了
//// return loginService.buildLoginUser(user);
// return buildLoginUser();
// });
// loginUser.setClientKey(client.getClientKey());
// loginUser.setDeviceType(client.getDeviceType());
// SaLoginModel model = new SaLoginModel();
// model.setDevice(client.getDeviceType());
// // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// // 例如: 后台用户30分钟过期 app用户1天过期
// model.setTimeout(client.getTimeout());
// model.setActiveTimeout(client.getActiveTimeout());
// model.setExtra(LoginSaasHelper.CLIENT_KEY, client.getClientId());
// // 生成token
// LoginSaasHelper.login(loginUser, model);
//
// LoginVo loginVo = new LoginVo();
// loginVo.setAccessToken(StpUtil.getTokenValue());
// loginVo.setExpireIn(StpUtil.getTokenTimeout());
// loginVo.setClientId(client.getClientId());
// return loginVo;
// } // }
// //
// private SysUserVo loadUserByEmail(String email) { //
// SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email)); // /**
// if (ObjectUtil.isNull(user)) { // * 构建登录用户
// log.info("登录用户:{} 不存在.", email); // */
// throw new UserException("user.not.exists", email); // private LoginUser buildLoginUser() {
// } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { // LoginUser loginUser = new LoginUser();
// log.info("登录用户:{} 已被停用.", email); // loginUser.setTenantId("1");
// throw new UserException("user.blocked", email); // loginUser.setUserId(1L);
// loginUser.setDeptId(2L);
// loginUser.setUsername("测试用户登录名");
// loginUser.setNickname("测试用户名称");
// loginUser.setUserType("用户类型1");
// return loginUser;
// } // }
// return user; //
// } //// /**
//// * 校验邮箱验证码
} //// */
//// private boolean validateEmailCode(String tenantId, String email, String emailCode) {
//// String code = RedisUtils.getCacheObject(GlobalConstants.CAPTCHA_CODE_KEY + email);
//// if (StringUtils.isBlank(code)) {
//// loginService.recordLogininfor(tenantId, email, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
//// throw new CaptchaExpireException();
//// }
//// return code.equals(emailCode);
//// }
////
//// private SysUserVo loadUserByEmail(String email) {
//// SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getEmail, email));
//// if (ObjectUtil.isNull(user)) {
//// log.info("登录用户:{} 不存在.", email);
//// throw new UserException("user.not.exists", email);
//// } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
//// log.info("登录用户:{} 已被停用.", email);
//// throw new UserException("user.blocked", email);
//// }
//// return user;
//// }
//
//}

View File

@ -1,117 +1,117 @@
package com.pj.service.impl; //package com.pj.service.impl;
//
import cn.dev33.satoken.stp.SaLoginModel; //import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil; //import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.json.JSONUtil; //import cn.hutool.json.JSONUtil;
import com.pj.model.vo.LoginVo; //import com.pj.model.vo.LoginVo;
import com.pj.model.vo.SysClientVo; //import com.pj.model.vo.SysClientVo;
import com.pj.service.IAuthStrategy; //import com.pj.service.IAuthStrategy;
import lombok.RequiredArgsConstructor; //import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; //import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.model.LoginUser; //import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.PasswordLoginBody; //import org.dromara.common.core.domain.model.PasswordLoginBody;
import org.dromara.common.core.utils.ValidatorUtils; //import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.satoken.utils.LoginHelper; //import org.dromara.common.satoken.utils.LoginSaasHelper;
import org.dromara.common.tenant.helper.TenantHelper; //import org.dromara.common.tenant.helper.TenantHelper;
import org.springframework.stereotype.Service; //import org.springframework.stereotype.Service;
//
/** ///**
* 密码认证策略 // * 密码认证策略
* // *
* @author Michelle.Chung // * @author Michelle.Chung
*/ // */
@Slf4j //@Slf4j
@Service("password" + IAuthStrategy.BASE_NAME) //@Service("password" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor //@RequiredArgsConstructor
public class PasswordAuthStrategy implements IAuthStrategy { //public class PasswordAuthStrategy implements IAuthStrategy {
//
// private final CaptchaProperties captchaProperties; //// private final CaptchaProperties captchaProperties;
// private final SysLoginService loginService; //// private final SysLoginService loginService;
// private final SysUserMapper userMapper; //// private final SysUserMapper userMapper;
//
@Override // @Override
public LoginVo login(String body, SysClientVo client) { // public LoginVo login(String body, SysClientVo client) {
PasswordLoginBody loginBody = JSONUtil.toBean(body, PasswordLoginBody.class); // PasswordLoginBody loginBody = JSONUtil.toBean(body, PasswordLoginBody.class);
ValidatorUtils.validate(loginBody); // ValidatorUtils.validate(loginBody);
String tenantId = loginBody.getTenantId(); // String tenantId = loginBody.getTenantId();
String username = loginBody.getUsername(); // String username = loginBody.getUsername();
String password = loginBody.getPassword(); // String password = loginBody.getPassword();
String code = loginBody.getCode(); // String code = loginBody.getCode();
String uuid = loginBody.getUuid(); // String uuid = loginBody.getUuid();
//
// boolean captchaEnabled = captchaProperties.getEnable(); //// boolean captchaEnabled = captchaProperties.getEnable();
// // 验证码开关 //// // 验证码开关
// if (captchaEnabled) { //// if (captchaEnabled) {
// validateCaptcha(tenantId, username, code, uuid); //// validateCaptcha(tenantId, username, code, uuid);
// } //// }
LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { // LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> {
// SysUserVo user = loadUserByUsername(username); //// SysUserVo user = loadUserByUsername(username);
// loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword())); //// loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword()));
// // 此处可根据登录用户的数据不同 自行创建 loginUser //// // 此处可根据登录用户的数据不同 自行创建 loginUser
// return loginService.buildLoginUser(user); //// return loginService.buildLoginUser(user);
return buildLoginUser(); // return buildLoginUser();
}); // });
loginUser.setClientKey(client.getClientKey()); // loginUser.setClientKey(client.getClientKey());
loginUser.setDeviceType(client.getDeviceType()); // loginUser.setDeviceType(client.getDeviceType());
SaLoginModel model = new SaLoginModel(); // SaLoginModel model = new SaLoginModel();
model.setDevice(client.getDeviceType()); // model.setDevice(client.getDeviceType());
// 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置 // // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
// 例如: 后台用户30分钟过期 app用户1天过期 // // 例如: 后台用户30分钟过期 app用户1天过期
model.setTimeout(client.getTimeout()); // model.setTimeout(client.getTimeout());
model.setActiveTimeout(client.getActiveTimeout()); // model.setActiveTimeout(client.getActiveTimeout());
model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId()); // model.setExtra(LoginSaasHelper.CLIENT_KEY, client.getClientId());
// 生成token // // 生成token
LoginHelper.login(loginUser, model); // LoginSaasHelper.login(loginUser, model);
//
LoginVo loginVo = new LoginVo(); // LoginVo loginVo = new LoginVo();
loginVo.setAccessToken(StpUtil.getTokenValue()); // loginVo.setAccessToken(StpUtil.getTokenValue());
loginVo.setExpireIn(StpUtil.getTokenTimeout()); // loginVo.setExpireIn(StpUtil.getTokenTimeout());
loginVo.setClientId(client.getClientId()); // loginVo.setClientId(client.getClientId());
return loginVo; // return loginVo;
}
private LoginUser buildLoginUser() {
LoginUser loginUser = new LoginUser();
loginUser.setTenantId("1");
loginUser.setUserId(1L);
loginUser.setDeptId(2L);
loginUser.setUsername("测试用户登录名");
loginUser.setNickname("测试用户名称");
loginUser.setUserType("app_user");
return loginUser;
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
*/
// private void validateCaptcha(String tenantId, String username, String code, String uuid) {
// String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
// String captcha = RedisUtils.getCacheObject(verifyKey);
// RedisUtils.deleteObject(verifyKey);
// if (captcha == null) {
// loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
// throw new CaptchaExpireException();
// }
// if (!code.equalsIgnoreCase(captcha)) {
// loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
// throw new CaptchaException();
// }
// } // }
// //
// private SysUserVo loadUserByUsername(String username) { // private LoginUser buildLoginUser() {
// SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username)); // LoginUser loginUser = new LoginUser();
// if (ObjectUtil.isNull(user)) { // loginUser.setTenantId("1");
// log.info("登录用户:{} 不存在.", username); // loginUser.setUserId(1L);
// throw new UserException("user.not.exists", username); // loginUser.setDeptId(2L);
// } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) { // loginUser.setUsername("测试用户登录名");
// log.info("登录用户:{} 已被停用.", username); // loginUser.setNickname("测试用户名称");
// throw new UserException("user.blocked", username); // loginUser.setUserType("app_user");
// return loginUser;
// } // }
// return user; //
// } // /**
// * 校验验证码
} // *
// * @param username 用户名
// * @param code 验证码
// * @param uuid 唯一标识
// */
//// private void validateCaptcha(String tenantId, String username, String code, String uuid) {
//// String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
//// String captcha = RedisUtils.getCacheObject(verifyKey);
//// RedisUtils.deleteObject(verifyKey);
//// if (captcha == null) {
//// loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
//// throw new CaptchaExpireException();
//// }
//// if (!code.equalsIgnoreCase(captcha)) {
//// loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
//// throw new CaptchaException();
//// }
//// }
////
//// private SysUserVo loadUserByUsername(String username) {
//// SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>().eq(SysUser::getUserName, username));
//// if (ObjectUtil.isNull(user)) {
//// log.info("登录用户:{} 不存在.", username);
//// throw new UserException("user.not.exists", username);
//// } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
//// log.info("登录用户:{} 已被停用.", username);
//// throw new UserException("user.blocked", username);
//// }
//// return user;
//// }
//
//}

View File

@ -3,12 +3,8 @@ server:
port: 19000 port: 19000
spring: spring:
cloud: application:
nacos: name: ruoyi-sso-server
discovery:
server-addr: http://127.0.0.1:8848
namespace: c5c00044-93e6-4e66-8060-3f39aa7ab82b
group: ${spring.profiles.active}
# Redis配置 SSO模式一和模式二使用Redis来同步会话 # Redis配置 SSO模式一和模式二使用Redis来同步会话
data: data:
redis: redis:
@ -77,27 +73,29 @@ security:
- /*/api-docs/** - /*/api-docs/**
- /sso/signout - /sso/signout
# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖 # dubbo
dubbo: dubbo:
config:
protocol:
name: tri
port: 20880
application: application:
qos-enable: false
logger: slf4j logger: slf4j
# 元数据中心 local 本地 remote 远程 这里使用远程便于其他服务获取 name: ${spring.application.name}
metadataType: remote
# 可选值 interface、instance、all默认是 all即接口级地址、应用级地址都注册
register-mode: instance
service-discovery:
# FORCE_INTERFACE只消费接口级地址如无地址则报错单订阅 2.x 地址
# APPLICATION_FIRST智能决策接口级/应用级地址,双订阅
# FORCE_APPLICATION只消费应用级地址如无地址则报错单订阅 3.x 地址
migration: FORCE_APPLICATION
# 注册中心配置 # 注册中心配置
registry: registry:
address: nacos://127.0.0.1:8848 address: nacos://127.0.0.1:8848
group: DUBBO_GROUP group: DUBBO_GROUP
username: nacos
password: nacos
parameters: parameters:
namespace: c5c00044-93e6-4e66-8060-3f39aa7ab82b namespace: c5c00044-93e6-4e66-8060-3f39aa7ab82b
# metadata-report:
# address: redis://${spring.data.redis.host}:${spring.data.redis.port}
# # 集群开关,目前单机
# cluster: false
# parameters:
# database: 6
# timeout: ${spring.data.redis.timeout}
# 消费者相关配置 # 消费者相关配置
consumer: consumer:
# 结果缓存(LRU算法) # 结果缓存(LRU算法)