diff --git a/pom.xml b/pom.xml index ddb686a52..cb96974c1 100644 --- a/pom.xml +++ b/pom.xml @@ -62,7 +62,7 @@ 3.11.0 3.1.2 1.3.0 - 3.3.0 + 3.2.14 @@ -422,6 +422,12 @@ ${dubbo.version} + + org.apache.dubbo + dubbo-metadata-report-redis + ${dubbo.version} + + + org.dromara - ruoyi-common-satoken - ${revision} + ruoyi-common-redis + + + + cn.dev33 + sa-token-spring-boot3-starter - + - org.dromara - ruoyi-common-security - ${revision} + cn.dev33 + sa-token-jwt + + + + + + + cn.dev33 + sa-token-redis-jackson + 1.38.0 + + + org.apache.commons + commons-pool2 + + + + + cn.dev33 + sa-token-sso + 1.38.0 + + + + + cn.dev33 + sa-token-sso + 1.38.0 + + + + + + + + + org.dromara @@ -80,26 +119,12 @@ ${revision} - + org.dromara ruoyi-api-system - - - cn.dev33 - sa-token-sso - 1.38.0 - - - - - cn.dev33 - sa-token-sso - 1.38.0 - - org.apache.dubbo @@ -111,6 +136,46 @@ dubbo-nacos-spring-boot-starter + + + + me.zhyd.oauth + JustAuth + 1.16.6 + + + org.dromara + ruoyi-common-log + + + org.dromara + ruoyi-common-ratelimiter + + + + cn.hutool + hutool-captcha + + + org.dromara + ruoyi-common-social + + + org.dromara + ruoyi-common-encrypt + + + + + + + + + + + + + diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/captcha/UnsignedMathGenerator.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/captcha/UnsignedMathGenerator.java new file mode 100644 index 000000000..3765a4963 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/captcha/UnsignedMathGenerator.java @@ -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)); + } +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/config/CaptchaConfig.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/config/CaptchaConfig.java new file mode 100644 index 000000000..ae2f40666 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/config/CaptchaConfig.java @@ -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; + } + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/controller/CaptchaController.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/controller/CaptchaController.java new file mode 100644 index 000000000..fb01de4f0 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/controller/CaptchaController.java @@ -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 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); + } + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/controller/TokenController.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/controller/TokenController.java new file mode 100644 index 000000000..92102c3e0 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/controller/TokenController.java @@ -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 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 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 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 socialCallback(@RequestBody SocialLoginBody loginBody) { + // 获取第三方登录信息 + AuthResponse 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 unlockSocial(@PathVariable Long socialId) { + Boolean rows = remoteSocialService.deleteWithValidById(socialId); + return rows ? R.ok() : R.fail("取消授权失败"); + } + + /** + * 登出方法 + */ + @PostMapping("logout") + public R logout() { + sysLoginService.logout(); + return R.ok(); + } + + /** + * 用户注册 + */ + @ApiEncrypt + @PostMapping("register") + public R register(@RequestBody RegisterBody registerBody) { + if (!remoteConfigService.selectRegisterEnabled(registerBody.getTenantId())) { + return R.fail("当前系统没有开启注册功能!"); + } + // 用户注册 + sysLoginService.register(registerBody); + return R.ok(); + } + + /** + * 登录页面租户下拉框 + * + * @return 租户列表 + */ + @GetMapping("/tenant/list") + public R tenantList(HttpServletRequest request) throws Exception { + // 返回对象 + LoginTenantVo result = new LoginTenantVo(); + boolean enable = TenantHelper.isEnable(); + result.setTenantEnabled(enable); + // 如果未开启租户这直接返回 + if (!enable) { + return R.ok(result); + } + + List tenantList = remoteTenantService.queryList(); + List 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 list = StreamUtils.filter(voList, vo -> + StringUtils.equals(vo.getDomain(), host)); + result.setVoList(CollUtil.isNotEmpty(list) ? list : voList); + return R.ok(result); + } + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/convert/TenantVoConvert.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/convert/TenantVoConvert.java new file mode 100644 index 000000000..368ccd99a --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/convert/TenantVoConvert.java @@ -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 { + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/CaptchaVo.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/CaptchaVo.java similarity index 90% rename from ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/CaptchaVo.java rename to ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/CaptchaVo.java index 5c37211a7..b528a779e 100644 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/CaptchaVo.java +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/CaptchaVo.java @@ -1,4 +1,4 @@ -package com.pj.model.vo; +package com.pj.auth.domain.vo; import lombok.Data; diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/LoginTenantVo.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/LoginTenantVo.java new file mode 100644 index 000000000..9fe6791df --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/LoginTenantVo.java @@ -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 voList; + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/LoginVo.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/LoginVo.java similarity index 96% rename from ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/LoginVo.java rename to ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/LoginVo.java index a6eb5179a..af05e5790 100644 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/LoginVo.java +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/LoginVo.java @@ -1,4 +1,4 @@ -package com.pj.model.vo; +package com.pj.auth.domain.vo; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/TenantListVo.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/TenantListVo.java new file mode 100644 index 000000000..49144741e --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/domain/vo/TenantListVo.java @@ -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; + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/enums/CaptchaCategory.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/enums/CaptchaCategory.java new file mode 100644 index 000000000..8c19c73ee --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/enums/CaptchaCategory.java @@ -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 clazz; +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/enums/CaptchaType.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/enums/CaptchaType.java new file mode 100644 index 000000000..96dbd851a --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/enums/CaptchaType.java @@ -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 clazz; +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/EmailLoginBody.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/EmailLoginBody.java new file mode 100644 index 000000000..8811f34d5 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/EmailLoginBody.java @@ -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; + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/PasswordLoginBody.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/PasswordLoginBody.java new file mode 100644 index 000000000..85d9edc89 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/PasswordLoginBody.java @@ -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; + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/RegisterBody.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/RegisterBody.java new file mode 100644 index 000000000..275651174 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/RegisterBody.java @@ -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; + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/SmsLoginBody.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/SmsLoginBody.java new file mode 100644 index 000000000..393f96dfb --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/SmsLoginBody.java @@ -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; + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/SocialLoginBody.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/SocialLoginBody.java new file mode 100644 index 000000000..325cc83b7 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/SocialLoginBody.java @@ -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; + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/XcxLoginBody.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/XcxLoginBody.java new file mode 100644 index 000000000..56d85d2f2 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/form/XcxLoginBody.java @@ -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; + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/listener/UserActionListener.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/listener/UserActionListener.java new file mode 100644 index 000000000..0d3b94791 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/listener/UserActionListener.java @@ -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) { + } + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/properties/CaptchaProperties.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/properties/CaptchaProperties.java new file mode 100644 index 000000000..5d188e9d8 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/properties/CaptchaProperties.java @@ -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; + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/properties/UserPasswordProperties.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/properties/UserPasswordProperties.java new file mode 100644 index 000000000..3a56d48a6 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/properties/UserPasswordProperties.java @@ -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; + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/service/IAuthStrategy.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/IAuthStrategy.java similarity index 73% rename from ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/service/IAuthStrategy.java rename to ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/IAuthStrategy.java index 39d762a90..38b5e3046 100644 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/service/IAuthStrategy.java +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/IAuthStrategy.java @@ -1,10 +1,9 @@ -package com.pj.service; +package com.pj.auth.service; - -import com.pj.model.vo.LoginVo; -import com.pj.model.vo.SysClientVo; +import com.pj.auth.domain.vo.LoginVo; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.SpringUtils; +import org.dromara.system.api.domain.vo.RemoteClientVo; /** * 授权策略 @@ -23,9 +22,9 @@ public interface IAuthStrategy { * @param grantType 授权类型 * @return 登录验证信息 */ - static LoginVo login(String body, SysClientVo client, String grantType) { + static LoginVo login(String body, RemoteClientVo client, String grantType) { // 授权类型和客户端id - String beanName = grantType + IAuthStrategy.BASE_NAME; + String beanName = grantType + BASE_NAME; if (!SpringUtils.containsBean(beanName)) { throw new ServiceException("授权类型不正确!"); } @@ -40,6 +39,6 @@ public interface IAuthStrategy { * @param client 授权管理视图对象 * @return 登录验证信息 */ - LoginVo login(String body, SysClientVo client); + LoginVo login(String body, RemoteClientVo client); } diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/SysLoginService.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/SysLoginService.java new file mode 100644 index 000000000..865357d92 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/SysLoginService.java @@ -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 checkList = remoteSocialService.selectByAuthId(authId); + if (CollUtil.isNotEmpty(checkList)) { + throw new ServiceException("此三方账号已经被绑定!"); + } + // 查询是否已经绑定用户 + RemoteSocialBo params = new RemoteSocialBo(); + params.setUserId(userId); + params.setSource(bo.getSource()); + List 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 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"); + } + } +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/EmailAuthStrategy.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/EmailAuthStrategy.java new file mode 100644 index 000000000..4d0bba597 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/EmailAuthStrategy.java @@ -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); + } + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/PasswordAuthStrategy.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/PasswordAuthStrategy.java new file mode 100644 index 000000000..fe7a692fd --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/PasswordAuthStrategy.java @@ -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(); + } + } + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/SmsAuthStrategy.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/SmsAuthStrategy.java new file mode 100644 index 000000000..3c1d16971 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/SmsAuthStrategy.java @@ -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); + } + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/SocialAuthStrategy.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/SocialAuthStrategy.java new file mode 100644 index 000000000..20864a87f --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/SocialAuthStrategy.java @@ -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 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 list = remoteSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid()); + if (CollUtil.isEmpty(list)) { + throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!"); + } + RemoteSocialVo socialVo; + if (TenantHelper.isEnable()) { + Optional 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; + } + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/XcxAuthStrategy.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/XcxAuthStrategy.java new file mode 100644 index 000000000..96fd481e1 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/auth/service/impl/XcxAuthStrategy.java @@ -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; + } + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/config/SaTokenConfig.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/config/SaTokenConfig.java deleted file mode 100644 index 7002b3124..000000000 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/config/SaTokenConfig.java +++ /dev/null @@ -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(); - } - -} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/config/SaTokenSsoConfig.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/config/SaTokenSsoConfig.java new file mode 100644 index 000000000..1e8143f26 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/config/SaTokenSsoConfig.java @@ -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; + } + + }; + } + + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/controller/SsoServerController.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/controller/SsoServerController.java index 1b199d86d..4c228836b 100644 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/controller/SsoServerController.java +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/controller/SsoServerController.java @@ -5,13 +5,26 @@ import cn.dev33.satoken.context.model.SaRequest; import cn.dev33.satoken.sso.config.SaSsoServerConfig; import cn.dev33.satoken.sso.processor.SaSsoServerProcessor; import cn.dev33.satoken.stp.StpUtil; -import cn.dev33.satoken.util.SaResult; -import cn.hutool.json.JSONUtil; -import com.pj.model.vo.LoginVo; -import com.pj.model.vo.SysClientVo; -import com.pj.service.IAuthStrategy; -import org.dromara.common.core.domain.model.PasswordLoginBody; +import cn.hutool.core.util.ObjectUtil; +import com.pj.auth.domain.vo.LoginVo; +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.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.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.ModelAndView; @@ -22,7 +35,13 @@ import org.springframework.web.servlet.ModelAndView; * @author click33 */ @RestController +@RequiredArgsConstructor +@Slf4j public class SsoServerController { + private final SysLoginService sysLoginService; + + @DubboReference + private RemoteClientService remoteClientService; /** * SSO-Server端:处理所有SSO相关请求 @@ -41,6 +60,32 @@ public class SsoServerController { 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相关参数 @Autowired private void configSso(SaSsoServerConfig ssoServer) { @@ -49,38 +94,6 @@ public class SsoServerController { ssoServer.notLoginView = () -> { 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("登录失败!"); - }; - } } diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/core/dao/PlusSaTokenDao.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/core/dao/PlusSaTokenDao.java new file mode 100644 index 000000000..ab7180fda --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/core/dao/PlusSaTokenDao.java @@ -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实现 协议统一) + *

+ * 采用 caffeine + redis 多级缓存 优化并发查询效率 + * + * @author Lion Li + */ +public class PlusSaTokenDao implements SaTokenDao { + + private static final Cache 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 searchData(String prefix, String keyword, int start, int size, boolean sortType) { + String keyStr = prefix + "*" + keyword + "*"; + return (List) CAFFEINE.get(keyStr, k -> { + Collection keys = RedisUtils.keys(keyStr); + List list = new ArrayList<>(keys); + return SaFoxUtil.searchList(list, start, size, sortType); + }); + } +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/core/service/SaPermissionImpl.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/core/service/SaPermissionImpl.java new file mode 100644 index 000000000..3ce65fadb --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/core/service/SaPermissionImpl.java @@ -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 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 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<>(); + } +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/handler/SaTokenExceptionHandler.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/handler/SaTokenExceptionHandler.java new file mode 100644 index 000000000..cba870714 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/handler/SaTokenExceptionHandler.java @@ -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 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 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 handleNotLoginException(NotLoginException e, HttpServletRequest request) { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage()); + return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "认证失败,无法访问系统资源"); + } + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/helper/LoginHelper.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/helper/LoginHelper.java deleted file mode 100644 index f1339facd..000000000 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/helper/LoginHelper.java +++ /dev/null @@ -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; -// -///** -// * 登录鉴权助手 -// *

-// * user_type 为 用户类型 同一个用户表 可以有多种用户类型 例如 pc,app -// * deivce 为 设备类型 同一个用户类型 可以有 多种设备类型 例如 web,ios -// * 可以组成 用户类型与设备类型多对多的 权限灵活控制 -// *

-// * 多用户体系 针对 多种用户类型 但权限控制不一致 -// * 可以组成 多用户类型表与多设备类型 分别控制权限 -// * -// * @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 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; -// } -// } -// -//} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/helper/LoginSaasHelper.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/helper/LoginSaasHelper.java new file mode 100644 index 000000000..3a5f44096 --- /dev/null +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/helper/LoginSaasHelper.java @@ -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; + +/** + * 登录鉴权助手 + *

+ * user_type 为 用户类型 同一个用户表 可以有多种用户类型 例如 pc,app + * deivce 为 设备类型 同一个用户类型 可以有 多种设备类型 例如 web,ios + * 可以组成 用户类型与设备类型多对多的 权限灵活控制 + *

+ * 多用户体系 针对 多种用户类型 但权限控制不一致 + * 可以组成 多用户类型表与多设备类型 分别控制权限 + * + * @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 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; + } + } + +} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/RoleDTO.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/RoleDTO.java deleted file mode 100644 index 06ff8a1c2..000000000 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/RoleDTO.java +++ /dev/null @@ -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; - -} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/SysClientVo.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/SysClientVo.java deleted file mode 100644 index 8e9d3e830..000000000 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/SysClientVo.java +++ /dev/null @@ -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 grantTypeList; - - /** - * 授权类型 - */ - private String grantType; - - /** - * 设备类型 - */ - private String deviceType; - - /** - * token活跃超时时间 - */ - private Long activeTimeout; - - /** - * token固定超时时间 - */ - private Long timeout; - - /** - * 状态(0正常 1停用) - */ - private String status; - - -} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/SysRoleVo.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/SysRoleVo.java deleted file mode 100644 index 4d7058a44..000000000 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/SysRoleVo.java +++ /dev/null @@ -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); - } - -} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/SysUserVo.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/SysUserVo.java deleted file mode 100644 index f07884bfd..000000000 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/model/vo/SysUserVo.java +++ /dev/null @@ -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 roles; - - /** - * 角色组 - */ - private Long[] roleIds; - - /** - * 岗位组 - */ - private Long[] postIds; - - /** - * 数据权限 当前角色ID - */ - private Long roleId; - -} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/service/impl/EmailAuthStrategy.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/service/impl/EmailAuthStrategy.java index ed572ac83..032195f7a 100644 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/service/impl/EmailAuthStrategy.java +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/service/impl/EmailAuthStrategy.java @@ -1,121 +1,100 @@ -package com.pj.service.impl; - -import cn.dev33.satoken.stp.SaLoginModel; -import cn.dev33.satoken.stp.StpUtil; -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.lang.Opt; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.json.JSONUtil; -import com.pj.model.vo.LoginVo; -import com.pj.model.vo.SysClientVo; -import com.pj.model.vo.SysRoleVo; -import com.pj.model.vo.SysUserVo; -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; -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) { -// 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); +//package com.pj.service.impl; +// +//import cn.dev33.satoken.stp.SaLoginModel; +//import cn.dev33.satoken.stp.StpUtil; +//import cn.hutool.json.JSONUtil; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.dromara.common.core.domain.model.EmailLoginBody; +//import org.dromara.common.core.domain.model.LoginUser; +//import org.dromara.common.core.utils.ValidatorUtils; +//import org.dromara.common.tenant.helper.TenantHelper; +//import org.springframework.stereotype.Service; +// +///** +// * 邮件认证策略 +// * +// * @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(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().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; +// +// /** +// * 构建登录用户 +// */ +// 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) { +//// 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().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; +//// } +// +//} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/service/impl/PasswordAuthStrategy.java b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/service/impl/PasswordAuthStrategy.java index 7fdab746d..617a06ab6 100644 --- a/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/service/impl/PasswordAuthStrategy.java +++ b/ruoyi-modules/ruoyi-sso-server/src/main/java/com/pj/service/impl/PasswordAuthStrategy.java @@ -1,117 +1,117 @@ -package com.pj.service.impl; - -import cn.dev33.satoken.stp.SaLoginModel; -import cn.dev33.satoken.stp.StpUtil; -import cn.hutool.json.JSONUtil; -import com.pj.model.vo.LoginVo; -import com.pj.model.vo.SysClientVo; -import com.pj.service.IAuthStrategy; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.dromara.common.core.domain.model.LoginUser; -import org.dromara.common.core.domain.model.PasswordLoginBody; -import org.dromara.common.core.utils.ValidatorUtils; -import org.dromara.common.satoken.utils.LoginHelper; -import org.dromara.common.tenant.helper.TenantHelper; -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; -// private final SysUserMapper userMapper; - - @Override - public LoginVo login(String body, SysClientVo client) { - PasswordLoginBody loginBody = JSONUtil.toBean(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(); - -// boolean captchaEnabled = captchaProperties.getEnable(); -// // 验证码开关 -// if (captchaEnabled) { -// validateCaptcha(tenantId, username, code, uuid); -// } - LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { -// SysUserVo user = loadUserByUsername(username); -// loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword())); -// // 此处可根据登录用户的数据不同 自行创建 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("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(); -// } +//package com.pj.service.impl; +// +//import cn.dev33.satoken.stp.SaLoginModel; +//import cn.dev33.satoken.stp.StpUtil; +//import cn.hutool.json.JSONUtil; +//import com.pj.model.vo.LoginVo; +//import com.pj.model.vo.SysClientVo; +//import com.pj.service.IAuthStrategy; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.dromara.common.core.domain.model.LoginUser; +//import org.dromara.common.core.domain.model.PasswordLoginBody; +//import org.dromara.common.core.utils.ValidatorUtils; +//import org.dromara.common.satoken.utils.LoginSaasHelper; +//import org.dromara.common.tenant.helper.TenantHelper; +//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; +//// private final SysUserMapper userMapper; +// +// @Override +// public LoginVo login(String body, SysClientVo client) { +// PasswordLoginBody loginBody = JSONUtil.toBean(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(); +// +//// boolean captchaEnabled = captchaProperties.getEnable(); +//// // 验证码开关 +//// if (captchaEnabled) { +//// validateCaptcha(tenantId, username, code, uuid); +//// } +// LoginUser loginUser = TenantHelper.dynamic(tenantId, () -> { +//// SysUserVo user = loadUserByUsername(username); +//// loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, user.getPassword())); +//// // 此处可根据登录用户的数据不同 自行创建 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 loadUserByUsername(String username) { -// SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper().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; +// 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) { +//// SysUserVo user = userMapper.selectVoOne(new LambdaQueryWrapper().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; +//// } +// +//} diff --git a/ruoyi-modules/ruoyi-sso-server/src/main/resources/application.yml b/ruoyi-modules/ruoyi-sso-server/src/main/resources/application.yml index 79b6e2ef8..285cf54a9 100644 --- a/ruoyi-modules/ruoyi-sso-server/src/main/resources/application.yml +++ b/ruoyi-modules/ruoyi-sso-server/src/main/resources/application.yml @@ -3,12 +3,8 @@ server: port: 19000 spring: - cloud: - nacos: - discovery: - server-addr: http://127.0.0.1:8848 - namespace: c5c00044-93e6-4e66-8060-3f39aa7ab82b - group: ${spring.profiles.active} + application: + name: ruoyi-sso-server # Redis配置 (SSO模式一和模式二使用Redis来同步会话) data: redis: @@ -77,27 +73,29 @@ security: - /*/api-docs/** - /sso/signout -# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖 +# dubbo dubbo: + config: + protocol: + name: tri + port: 20880 application: + qos-enable: false logger: slf4j - # 元数据中心 local 本地 remote 远程 这里使用远程便于其他服务获取 - metadataType: remote - # 可选值 interface、instance、all,默认是 all,即接口级地址、应用级地址都注册 - register-mode: instance - service-discovery: - # FORCE_INTERFACE,只消费接口级地址,如无地址则报错,单订阅 2.x 地址 - # APPLICATION_FIRST,智能决策接口级/应用级地址,双订阅 - # FORCE_APPLICATION,只消费应用级地址,如无地址则报错,单订阅 3.x 地址 - migration: FORCE_APPLICATION + name: ${spring.application.name} # 注册中心配置 registry: address: nacos://127.0.0.1:8848 group: DUBBO_GROUP - username: nacos - password: nacos parameters: 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: # 结果缓存(LRU算法)