From 106518c5979ad04d00e1aa991389101d188ce050 Mon Sep 17 00:00:00 2001 From: xuekang <914468783@qq.com> Date: 星期五, 10 五月 2024 20:42:10 +0800 Subject: [PATCH] 初始化 --- ruoyi-auth/src/main/java/org/dromara/auth/service/impl/EmailAuthStrategy.java | 84 ++ ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java | 79 ++ ruoyi-auth/src/main/resources/logback-plus.xml | 28 ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java | 88 ++ ruoyi-auth/src/main/java/org/dromara/auth/properties/UserPasswordProperties.java | 29 ruoyi-auth/src/main/java/org/dromara/auth/controller/TokenController.java | 209 +++++ ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SmsAuthStrategy.java | 84 ++ ruoyi-auth/src/main/java/org/dromara/auth/form/PasswordLoginBody.java | 34 ruoyi-auth/Dockerfile | 23 ruoyi-auth/src/main/java/org/dromara/auth/service/impl/XcxAuthStrategy.java | 69 + ruoyi-auth/src/main/java/org/dromara/auth/service/SysLoginService.java | 257 +++++++ ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginTenantVo.java | 25 ruoyi-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java | 104 ++ ruoyi-auth/src/main/java/org/dromara/auth/form/SocialLoginBody.java | 36 + ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaCategory.java | 35 + ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaType.java | 29 ruoyi-auth/src/main/resources/banner.txt | 10 ruoyi-auth/pom.xml | 146 ++++ ruoyi-auth/src/main/java/org/dromara/auth/listener/UserActionListener.java | 162 ++++ ruoyi-auth/src/main/java/org/dromara/auth/RuoYiAuthApplication.java | 23 ruoyi-auth/src/main/java/org/dromara/auth/form/RegisterBody.java | 36 + ruoyi-auth/src/main/java/org/dromara/auth/config/CaptchaConfig.java | 62 + ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SocialAuthStrategy.java | 109 +++ ruoyi-auth/src/main/java/org/dromara/auth/domain/convert/TenantVoConvert.java | 16 ruoyi-auth/src/main/resources/application.yml | 31 ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginVo.java | 54 + ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/TenantListVo.java | 19 ruoyi-auth/src/main/java/org/dromara/auth/properties/CaptchaProperties.java | 45 + ruoyi-auth/src/main/java/org/dromara/auth/form/XcxLoginBody.java | 29 ruoyi-auth/src/main/java/org/dromara/auth/service/IAuthStrategy.java | 35 + ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/CaptchaVo.java | 25 ruoyi-auth/src/main/java/org/dromara/auth/form/EmailLoginBody.java | 32 ruoyi-auth/src/main/java/org/dromara/auth/form/SmsLoginBody.java | 30 33 files changed, 2,077 insertions(+), 0 deletions(-) diff --git a/ruoyi-auth/Dockerfile b/ruoyi-auth/Dockerfile new file mode 100644 index 0000000..f6721d2 --- /dev/null +++ b/ruoyi-auth/Dockerfile @@ -0,0 +1,23 @@ +#FROM findepi/graalvm:java17-native +FROM openjdk:17.0.2-oraclelinux8 + +MAINTAINER Lion Li + +RUN mkdir -p /ruoyi/auth/logs \ + /ruoyi/auth/temp \ + /ruoyi/skywalking/agent + +WORKDIR /ruoyi/auth + +ENV SERVER_PORT=9210 LANG=C.UTF-8 LC_ALL=C.UTF-8 JAVA_OPTS="" + +EXPOSE ${SERVER_PORT} + +ADD ./target/ruoyi-auth.jar ./app.jar + +ENTRYPOINT java -Djava.security.egd=file:/dev/./urandom -Dserver.port=${SERVER_PORT} \ + #-Dskywalking.agent.service_name=ruoyi-auth \ + #-javaagent:/ruoyi/skywalking/agent/skywalking-agent.jar \ + -jar app.jar \ + -XX:+HeapDumpOnOutOfMemoryError -Xlog:gc*,:time,tags,level -XX:+UseZGC ${JAVA_OPTS} + diff --git a/ruoyi-auth/pom.xml b/ruoyi-auth/pom.xml new file mode 100644 index 0000000..a09df55 --- /dev/null +++ b/ruoyi-auth/pom.xml @@ -0,0 +1,146 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-cloud-plus</artifactId> + <version>${revision}</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>ruoyi-auth</artifactId> + + <description> + ruoyi-auth 璁よ瘉鎺堟潈涓績 + </description> + + <dependencies> + + <!-- SpringCloud Alibaba Nacos --> + <dependency> + <groupId>com.alibaba.cloud</groupId> + <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> + </dependency> + + <!-- SpringCloud Alibaba Nacos Config --> + <dependency> + <groupId>com.alibaba.cloud</groupId> + <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId> + </dependency> + + <dependency> + <groupId>cn.hutool</groupId> + <artifactId>hutool-captcha</artifactId> + </dependency> + + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-sentinel</artifactId> + </dependency> + + <!-- RuoYi Common Security--> + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-security</artifactId> + </dependency> + + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-social</artifactId> + </dependency> + + <!-- RuoYi Common Log --> + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-log</artifactId> + </dependency> + + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-doc</artifactId> + </dependency> + + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-web</artifactId> + </dependency> + + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-ratelimiter</artifactId> + </dependency> + + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-encrypt</artifactId> + </dependency> + + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-dubbo</artifactId> + </dependency> + + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-seata</artifactId> + </dependency> + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-tenant</artifactId> + <exclusions> + <exclusion> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-common-mybatis</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.dromara</groupId> + <artifactId>ruoyi-api-resource</artifactId> + </dependency> + + <!-- 鑷畾涔夎礋杞藉潎琛�(澶氬洟闃熷紑鍙戜娇鐢�) --> +<!-- <dependency>--> +<!-- <groupId>org.dromara</groupId>--> +<!-- <artifactId>ruoyi-common-loadbalancer</artifactId>--> +<!-- </dependency>--> + + <!-- ELK 鏃ュ織鏀堕泦 --> +<!-- <dependency>--> +<!-- <groupId>org.dromara</groupId>--> +<!-- <artifactId>ruoyi-common-logstash</artifactId>--> +<!-- </dependency>--> + + <!-- skywalking 鏃ュ織鏀堕泦 --> +<!-- <dependency>--> +<!-- <groupId>org.dromara</groupId>--> +<!-- <artifactId>ruoyi-common-skylog</artifactId>--> +<!-- </dependency>--> + + <!-- prometheus 鐩戞帶 --> +<!-- <dependency>--> +<!-- <groupId>org.dromara</groupId>--> +<!-- <artifactId>ruoyi-common-prometheus</artifactId>--> +<!-- </dependency>--> + + </dependencies> + + <build> + <finalName>${project.artifactId}</finalName> + <plugins> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <version>${spring-boot.version}</version> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/RuoYiAuthApplication.java b/ruoyi-auth/src/main/java/org/dromara/auth/RuoYiAuthApplication.java new file mode 100644 index 0000000..12d4d63 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/RuoYiAuthApplication.java @@ -0,0 +1,23 @@ +package org.dromara.auth; + +import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; +import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup; + +/** + * 璁よ瘉鎺堟潈涓績 + * + * @author ruoyi + */ +@EnableDubbo +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +public class RuoYiAuthApplication { + public static void main(String[] args) { + SpringApplication application = new SpringApplication(RuoYiAuthApplication.class); + application.setApplicationStartup(new BufferingApplicationStartup(2048)); + application.run(args); + System.out.println("(鈾モ棤鈥库棤)锞夛緸 璁よ瘉鎺堟潈涓績鍚姩鎴愬姛 醿�(麓凇`醿�)锞� "); + } +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java b/ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java new file mode 100644 index 0000000..feb4cdf --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/captcha/UnsignedMathGenerator.java @@ -0,0 +1,88 @@ +package org.dromara.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-auth/src/main/java/org/dromara/auth/config/CaptchaConfig.java b/ruoyi-auth/src/main/java/org/dromara/auth/config/CaptchaConfig.java new file mode 100644 index 0000000..5880016 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/config/CaptchaConfig.java @@ -0,0 +1,62 @@ +package org.dromara.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.PINK; + 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-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java b/ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java new file mode 100644 index 0000000..63d002c --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/controller/CaptchaController.java @@ -0,0 +1,79 @@ +package org.dromara.auth.controller; + +import cn.dev33.satoken.annotation.SaIgnore; +import cn.hutool.captcha.AbstractCaptcha; +import cn.hutool.captcha.generator.CodeGenerator; +import cn.hutool.core.util.IdUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.auth.domain.vo.CaptchaVo; +import org.dromara.auth.enums.CaptchaType; +import org.dromara.auth.properties.CaptchaProperties; +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 + */ +@SaIgnore +@Slf4j +@Validated +@RequiredArgsConstructor +@RestController +public class CaptchaController { + + private final CaptchaProperties captchaProperties; + + /** + * 鐢熸垚楠岃瘉鐮� + */ + @RateLimiter(time = 60, count = 10, limitType = LimitType.IP) + @GetMapping("/code") + public R<CaptchaVo> getCode() { + CaptchaVo captchaVo = new CaptchaVo(); + boolean captchaEnabled = captchaProperties.getEnabled(); + if (!captchaEnabled) { + captchaVo.setCaptchaEnabled(false); + return R.ok(captchaVo); + } + // 淇濆瓨楠岃瘉鐮佷俊鎭� + String uuid = IdUtil.simpleUUID(); + String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid; + // 鐢熸垚楠岃瘉鐮� + CaptchaType captchaType = captchaProperties.getType(); + boolean isMath = CaptchaType.MATH == captchaType; + Integer length = isMath ? captchaProperties.getNumberLength() : captchaProperties.getCharLength(); + CodeGenerator codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), length); + AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz()); + captcha.setGenerator(codeGenerator); + captcha.createCode(); + 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-auth/src/main/java/org/dromara/auth/controller/TokenController.java b/ruoyi-auth/src/main/java/org/dromara/auth/controller/TokenController.java new file mode 100644 index 0000000..6424a6a --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/controller/TokenController.java @@ -0,0 +1,209 @@ +package org.dromara.auth.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjectUtil; +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.auth.domain.vo.LoginTenantVo; +import org.dromara.auth.domain.vo.LoginVo; +import org.dromara.auth.domain.vo.TenantListVo; +import org.dromara.auth.form.RegisterBody; +import org.dromara.auth.form.SocialLoginBody; +import org.dromara.auth.service.IAuthStrategy; +import org.dromara.auth.service.SysLoginService; +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.satoken.utils.LoginHelper; +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.resource.api.RemoteMessageService; +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.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * token 鎺у埗 + * + * @author Lion Li + */ +@Slf4j +@RequiredArgsConstructor +@RestController +public class TokenController { + + private final SocialProperties socialProperties; + private final SysLoginService sysLoginService; + private final ScheduledExecutorService scheduledExecutorService; + + @DubboReference + private final RemoteConfigService remoteConfigService; + @DubboReference + private final RemoteTenantService remoteTenantService; + @DubboReference + private final RemoteClientService remoteClientService; + @DubboReference + private final RemoteSocialService remoteSocialService; + @DubboReference(stub = "true") + private final RemoteMessageService remoteMessageService; + + /** + * 鐧诲綍鏂规硶 + * + * @param body 鐧诲綍淇℃伅 + * @return 缁撴灉 + */ + @ApiEncrypt + @PostMapping("/login") + public R<LoginVo> login(@RequestBody String body) { + LoginBody loginBody = JsonUtils.parseObject(body, LoginBody.class); + ValidatorUtils.validate(loginBody); + // 鎺堟潈绫诲瀷鍜屽鎴风id + String clientId = loginBody.getClientId(); + String grantType = loginBody.getGrantType(); + RemoteClientVo clientVo = remoteClientService.queryByClientId(clientId); + + // 鏌ヨ涓嶅埌 client 鎴� client 鍐呬笉鍖呭惈 grantType + if (ObjectUtil.isNull(clientVo) || !StringUtils.contains(clientVo.getGrantType(), grantType)) { + log.info("瀹㈡埛绔痠d: {} 璁よ瘉绫诲瀷锛歿} 寮傚父!.", 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 = LoginHelper.getUserId(); + scheduledExecutorService.schedule(() -> { + try { + remoteMessageService.sendMessage(userId, "娆㈣繋鐧诲綍RuoYi-Cloud-Plus寰湇鍔$鐞嗙郴缁�"); + } catch (Exception ignored) { + } + }, 3, TimeUnit.SECONDS); + return R.ok(loginVo); + } + + /** + * 绗笁鏂圭櫥褰曡姹� + * + * @param source 鐧诲綍鏉ユ簮 + * @return 缁撴灉 + */ + @GetMapping("/binding/{source}") + public R<String> authBinding(@PathVariable("source") String source) { + SocialLoginConfigProperties obj = socialProperties.getType().get(source); + if (ObjectUtil.isNull(obj)) { + return R.fail(source + "骞冲彴璐﹀彿鏆備笉鏀寔"); + } + AuthRequest authRequest = SocialUtils.getAuthRequest(source, socialProperties); + String authorizeUrl = authRequest.authorize(AuthStateUtils.createState()); + return R.ok("鎿嶄綔鎴愬姛", authorizeUrl); + } + + /** + * 绗笁鏂圭櫥褰曞洖璋冧笟鍔″鐞� 缁戝畾鎺堟潈 + * + * @param loginBody 璇锋眰浣� + * @return 缁撴灉 + */ + @PostMapping("/social/callback") + public R<Void> socialCallback(@RequestBody SocialLoginBody loginBody) { + // 鑾峰彇绗笁鏂圭櫥褰曚俊鎭� + AuthResponse<AuthUser> response = SocialUtils.loginAuth( + loginBody.getSource(), loginBody.getSocialCode(), + loginBody.getSocialState(), socialProperties); + AuthUser authUserData = response.getData(); + // 鍒ゆ柇鎺堟潈鍝嶅簲鏄惁鎴愬姛 + if (!response.ok()) { + return R.fail(response.getMsg()); + } + sysLoginService.socialRegister(authUserData); + return R.ok(); + } + + + /** + * 鍙栨秷鎺堟潈 + * + * @param socialId socialId + */ + @DeleteMapping(value = "/unlock/{socialId}") + public R<Void> unlockSocial(@PathVariable Long socialId) { + Boolean rows = remoteSocialService.deleteWithValidById(socialId); + return rows ? R.ok() : R.fail("鍙栨秷鎺堟潈澶辫触"); + } + + /** + * 鐧诲嚭鏂规硶 + */ + @PostMapping("logout") + public R<Void> logout() { + sysLoginService.logout(); + return R.ok(); + } + + /** + * 鐢ㄦ埛娉ㄥ唽 + */ + @ApiEncrypt + @PostMapping("register") + public R<Void> register(@RequestBody RegisterBody registerBody) { + if (!remoteConfigService.selectRegisterEnabled(registerBody.getTenantId())) { + return R.fail("褰撳墠绯荤粺娌℃湁寮�鍚敞鍐屽姛鑳斤紒"); + } + // 鐢ㄦ埛娉ㄥ唽 + sysLoginService.register(registerBody); + return R.ok(); + } + + /** + * 鐧诲綍椤甸潰绉熸埛涓嬫媺妗� + * + * @return 绉熸埛鍒楄〃 + */ + @GetMapping("/tenant/list") + public R<LoginTenantVo> tenantList(HttpServletRequest request) throws Exception { + List<RemoteTenantVo> tenantList = remoteTenantService.queryList(); + List<TenantListVo> voList = MapstructUtils.convert(tenantList, TenantListVo.class); + // 鑾峰彇鍩熷悕 + String host; + String referer = request.getHeader("referer"); + if (StringUtils.isNotBlank(referer)) { + // 杩欓噷浠巖eferer涓彇鍊兼槸涓轰簡鏈湴浣跨敤hosts娣诲姞铏氭嫙鍩熷悕锛屾柟渚挎湰鍦扮幆澧冭皟璇� + host = referer.split("//")[1].split("/")[0]; + } else { + host = new URL(request.getRequestURL().toString()).getHost(); + } + // 鏍规嵁鍩熷悕杩涜绛涢�� + List<TenantListVo> list = StreamUtils.filter(voList, vo -> + StringUtils.equals(vo.getDomain(), host)); + // 杩斿洖瀵硅薄 + LoginTenantVo vo = new LoginTenantVo(); + vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList); + vo.setTenantEnabled(TenantHelper.isEnable()); + return R.ok(vo); + } + +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/domain/convert/TenantVoConvert.java b/ruoyi-auth/src/main/java/org/dromara/auth/domain/convert/TenantVoConvert.java new file mode 100644 index 0000000..7888f2c --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/domain/convert/TenantVoConvert.java @@ -0,0 +1,16 @@ +package org.dromara.auth.domain.convert; + +import io.github.linpeilie.BaseMapper; +import org.dromara.auth.domain.vo.TenantListVo; +import org.dromara.system.api.domain.vo.RemoteTenantVo; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; + +/** + * 绉熸埛vo杞崲鍣� + * @author zhujie + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public interface TenantVoConvert extends BaseMapper<RemoteTenantVo, TenantListVo> { + +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/CaptchaVo.java b/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/CaptchaVo.java new file mode 100644 index 0000000..2a4c0bd --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/CaptchaVo.java @@ -0,0 +1,25 @@ +package org.dromara.auth.domain.vo; + +import lombok.Data; + +/** + * 楠岃瘉鐮佷俊鎭� + * + * @author Michelle.Chung + */ +@Data +public class CaptchaVo { + + /** + * 鏄惁寮�鍚獙璇佺爜 + */ + private Boolean captchaEnabled = true; + + private String uuid; + + /** + * 楠岃瘉鐮佸浘鐗� + */ + private String img; + +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginTenantVo.java b/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginTenantVo.java new file mode 100644 index 0000000..fcfdcc3 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginTenantVo.java @@ -0,0 +1,25 @@ +package org.dromara.auth.domain.vo; + +import lombok.Data; + +import java.util.List; + +/** + * 鐧诲綍绉熸埛瀵硅薄 + * + * @author Michelle.Chung + */ +@Data +public class LoginTenantVo { + + /** + * 绉熸埛寮�鍏� + */ + private Boolean tenantEnabled; + + /** + * 绉熸埛瀵硅薄鍒楄〃 + */ + private List<TenantListVo> voList; + +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginVo.java b/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginVo.java new file mode 100644 index 0000000..e4bea14 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/LoginVo.java @@ -0,0 +1,54 @@ +package org.dromara.auth.domain.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 鐧诲綍楠岃瘉淇℃伅 + * + * @author Michelle.Chung + */ +@Data +public class LoginVo { + + /** + * 鎺堟潈浠ょ墝 + */ + @JsonProperty("access_token") + private String accessToken; + + /** + * 鍒锋柊浠ょ墝 + */ + @JsonProperty("refresh_token") + private String refreshToken; + + /** + * 鎺堟潈浠ょ墝 access_token 鐨勬湁鏁堟湡 + */ + @JsonProperty("expire_in") + private Long expireIn; + + /** + * 鍒锋柊浠ょ墝 refresh_token 鐨勬湁鏁堟湡 + */ + @JsonProperty("refresh_expire_in") + private Long refreshExpireIn; + + /** + * 搴旂敤id + */ + @JsonProperty("client_id") + private String clientId; + + /** + * 浠ょ墝鏉冮檺 + */ + private String scope; + + /** + * 鐢ㄦ埛 openid + */ + private String openid; + +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/TenantListVo.java b/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/TenantListVo.java new file mode 100644 index 0000000..b993b37 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/domain/vo/TenantListVo.java @@ -0,0 +1,19 @@ +package org.dromara.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-auth/src/main/java/org/dromara/auth/enums/CaptchaCategory.java b/ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaCategory.java new file mode 100644 index 0000000..b387aed --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaCategory.java @@ -0,0 +1,35 @@ +package org.dromara.auth.enums; + +import cn.hutool.captcha.AbstractCaptcha; +import cn.hutool.captcha.CircleCaptcha; +import cn.hutool.captcha.LineCaptcha; +import cn.hutool.captcha.ShearCaptcha; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 楠岃瘉鐮佺被鍒� + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum CaptchaCategory { + + /** + * 绾挎骞叉壈 + */ + LINE(LineCaptcha.class), + + /** + * 鍦嗗湀骞叉壈 + */ + CIRCLE(CircleCaptcha.class), + + /** + * 鎵洸骞叉壈 + */ + SHEAR(ShearCaptcha.class); + + private final Class<? extends AbstractCaptcha> clazz; +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaType.java b/ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaType.java new file mode 100644 index 0000000..b663345 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/enums/CaptchaType.java @@ -0,0 +1,29 @@ +package org.dromara.auth.enums; + +import cn.hutool.captcha.generator.CodeGenerator; +import cn.hutool.captcha.generator.RandomGenerator; +import org.dromara.auth.captcha.UnsignedMathGenerator; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 楠岃瘉鐮佺被鍨� + * + * @author Lion Li + */ +@Getter +@AllArgsConstructor +public enum CaptchaType { + + /** + * 鏁板瓧 + */ + MATH(UnsignedMathGenerator.class), + + /** + * 瀛楃 + */ + CHAR(RandomGenerator.class); + + private final Class<? extends CodeGenerator> clazz; +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/form/EmailLoginBody.java b/ruoyi-auth/src/main/java/org/dromara/auth/form/EmailLoginBody.java new file mode 100644 index 0000000..931e236 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/form/EmailLoginBody.java @@ -0,0 +1,32 @@ +package org.dromara.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-auth/src/main/java/org/dromara/auth/form/PasswordLoginBody.java b/ruoyi-auth/src/main/java/org/dromara/auth/form/PasswordLoginBody.java new file mode 100644 index 0000000..edb7ab2 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/form/PasswordLoginBody.java @@ -0,0 +1,34 @@ +package org.dromara.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-auth/src/main/java/org/dromara/auth/form/RegisterBody.java b/ruoyi-auth/src/main/java/org/dromara/auth/form/RegisterBody.java new file mode 100644 index 0000000..386c0fc --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/form/RegisterBody.java @@ -0,0 +1,36 @@ +package org.dromara.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-auth/src/main/java/org/dromara/auth/form/SmsLoginBody.java b/ruoyi-auth/src/main/java/org/dromara/auth/form/SmsLoginBody.java new file mode 100644 index 0000000..48e262f --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/form/SmsLoginBody.java @@ -0,0 +1,30 @@ +package org.dromara.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-auth/src/main/java/org/dromara/auth/form/SocialLoginBody.java b/ruoyi-auth/src/main/java/org/dromara/auth/form/SocialLoginBody.java new file mode 100644 index 0000000..cbd61c9 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/form/SocialLoginBody.java @@ -0,0 +1,36 @@ +package org.dromara.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; + + /** + * 绗笁鏂圭櫥褰昪ode + */ + @NotBlank(message = "{social.code.not.blank}") + private String socialCode; + + /** + * 绗笁鏂圭櫥褰晄ocialState + */ + @NotBlank(message = "{social.state.not.blank}") + private String socialState; + +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/form/XcxLoginBody.java b/ruoyi-auth/src/main/java/org/dromara/auth/form/XcxLoginBody.java new file mode 100644 index 0000000..c68306c --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/form/XcxLoginBody.java @@ -0,0 +1,29 @@ +package org.dromara.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 { + + /** + * 灏忕▼搴廼d(澶氫釜灏忕▼搴忔椂浣跨敤) + */ + private String appid; + + /** + * 灏忕▼搴廲ode + */ + @NotBlank(message = "{xcx.code.not.blank}") + private String xcxCode; + +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/listener/UserActionListener.java b/ruoyi-auth/src/main/java/org/dromara/auth/listener/UserActionListener.java new file mode 100644 index 0000000..86df42c --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/listener/UserActionListener.java @@ -0,0 +1,162 @@ +package org.dromara.auth.listener; + +import cn.dev33.satoken.config.SaTokenConfig; +import cn.dev33.satoken.listener.SaTokenListener; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; +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.satoken.utils.LoginHelper; +import org.dromara.resource.api.RemoteMessageService; +import org.dromara.system.api.RemoteUserService; +import org.dromara.system.api.domain.SysUserOnline; +import org.dromara.system.api.model.LoginUser; +import org.springframework.stereotype.Component; + +import java.time.Duration; +import java.util.concurrent.ScheduledExecutorService; + +/** + * 鐢ㄦ埛琛屼负 渚﹀惉鍣ㄧ殑瀹炵幇 + * + * @author Lion Li + */ +@RequiredArgsConstructor +@Component +@Slf4j +public class UserActionListener implements SaTokenListener { + + private final SaTokenConfig tokenConfig; + private final ScheduledExecutorService scheduledExecutorService; + @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(); + LoginUser user = LoginHelper.getLoginUser(); + 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); + userOnline.setUserName(user.getUsername()); + userOnline.setClientKey(user.getClientKey()); + userOnline.setDeviceType(user.getDeviceType()); + if (ObjectUtil.isNotNull(user.getDeptName())) { + userOnline.setDeptName(user.getDeptName()); + } + 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(user.getTenantId()); + logininforEvent.setUsername(user.getUsername()); + logininforEvent.setStatus(Constants.LOGIN_SUCCESS); + logininforEvent.setMessage(MessageUtils.message("user.login.success")); + logininforEvent.setRequest(ServletUtils.getRequest()); + SpringUtils.context().publishEvent(logininforEvent); + // 鏇存柊鐧诲綍淇℃伅 + remoteUserService.recordLoginInfo(user.getUserId(), ServletUtils.getClientIP()); + log.info("user doLogin, useId:{}, token:{}", loginId, tokenValue); + } + + /** + * 姣忔娉ㄩ攢鏃惰Е鍙� + */ + @Override + public void doLogout(String loginType, Object loginId, String tokenValue) { + 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) { + 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) { + 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-auth/src/main/java/org/dromara/auth/properties/CaptchaProperties.java b/ruoyi-auth/src/main/java/org/dromara/auth/properties/CaptchaProperties.java new file mode 100644 index 0000000..1cd7098 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/properties/CaptchaProperties.java @@ -0,0 +1,45 @@ +package org.dromara.auth.properties; + +import org.dromara.auth.enums.CaptchaCategory; +import org.dromara.auth.enums.CaptchaType; +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 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-auth/src/main/java/org/dromara/auth/properties/UserPasswordProperties.java b/ruoyi-auth/src/main/java/org/dromara/auth/properties/UserPasswordProperties.java new file mode 100644 index 0000000..5960d71 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/properties/UserPasswordProperties.java @@ -0,0 +1,29 @@ +package org.dromara.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-auth/src/main/java/org/dromara/auth/service/IAuthStrategy.java b/ruoyi-auth/src/main/java/org/dromara/auth/service/IAuthStrategy.java new file mode 100644 index 0000000..0bc3657 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/service/IAuthStrategy.java @@ -0,0 +1,35 @@ +package org.dromara.auth.service; + +import org.dromara.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; + +/** + * 鎺堟潈绛栫暐 + * + * @author Michelle.Chung + */ +public interface IAuthStrategy { + + String BASE_NAME = "AuthStrategy"; + + /** + * 鐧诲綍 + */ + static LoginVo login(String body, RemoteClientVo client, String grantType) { + // 鎺堟潈绫诲瀷鍜屽鎴风id + String beanName = grantType + BASE_NAME; + if (!SpringUtils.containsBean(beanName)) { + throw new ServiceException("鎺堟潈绫诲瀷涓嶆纭�!"); + } + IAuthStrategy instance = SpringUtils.getBean(beanName); + return instance.login(body, client); + } + + /** + * 鐧诲綍 + */ + LoginVo login(String body, RemoteClientVo client); + +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/service/SysLoginService.java b/ruoyi-auth/src/main/java/org/dromara/auth/service/SysLoginService.java new file mode 100644 index 0000000..eb2d7e6 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/service/SysLoginService.java @@ -0,0 +1,257 @@ +package org.dromara.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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import me.zhyd.oauth.model.AuthUser; +import org.apache.dubbo.config.annotation.DubboReference; +import org.dromara.auth.form.RegisterBody; +import org.dromara.auth.properties.CaptchaProperties; +import org.dromara.auth.properties.UserPasswordProperties; +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.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.ServletUtils; +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.satoken.utils.LoginHelper; +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 鎺堟潈鍝嶅簲瀹炰綋 + */ + public void socialRegister(AuthUser authUserData) { + String authId = authUserData.getSource() + authUserData.getUuid(); + // 绗笁鏂圭敤鎴蜂俊鎭� + RemoteSocialBo bo = BeanUtil.toBean(authUserData, RemoteSocialBo.class); + BeanUtil.copyProperties(authUserData.getToken(), bo); + bo.setUserId(LoginHelper.getUserId()); + bo.setAuthId(authId); + bo.setOpenId(authUserData.getUuid()); + bo.setUserName(authUserData.getUsername()); + bo.setNickName(authUserData.getNickname()); + // 鏌ヨ鏄惁宸茬粡缁戝畾鐢ㄦ埛 + List<RemoteSocialVo> list = remoteSocialService.selectByAuthId(authId); + if (CollUtil.isEmpty(list)) { + // 娌℃湁缁戝畾鐢ㄦ埛, 鏂板鐢ㄦ埛淇℃伅 + remoteSocialService.insertByBo(bo); + } else { + // 鏇存柊鐢ㄦ埛淇℃伅 + bo.setId(list.get(0).getId()); + remoteSocialService.updateByBo(bo); + } + } + + /** + * 閫�鍑虹櫥褰� + */ + public void logout() { + try { + LoginUser loginUser = LoginHelper.getLoginUser(); + if (ObjectUtil.isNull(loginUser)) { + return; + } + if (TenantHelper.isEnable() && LoginHelper.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.defaultString(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); + logininforEvent.setRequest(ServletUtils.getRequest()); + SpringUtils.context().publishEvent(logininforEvent); + } + + /** + * 鐧诲綍鏍¢獙 + */ + public void checkLogin(LoginType loginType, String tenantId, String username, Supplier<Boolean> supplier) { + String errorKey = GlobalConstants.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-auth/src/main/java/org/dromara/auth/service/impl/EmailAuthStrategy.java b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/EmailAuthStrategy.java new file mode 100644 index 0000000..8790fe1 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/EmailAuthStrategy.java @@ -0,0 +1,84 @@ +package org.dromara.auth.service.impl; + +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.dromara.auth.domain.vo.LoginVo; +import org.dromara.auth.form.EmailLoginBody; +import org.dromara.auth.service.IAuthStrategy; +import org.dromara.auth.service.SysLoginService; +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.satoken.utils.LoginHelper; +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 = remoteUserService.getUserInfoByEmail(email, tenantId); + loginService.checkLogin(LoginType.EMAIL, tenantId, loginUser.getUsername(), () -> !validateEmailCode(tenantId, email, emailCode)); + 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 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-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java new file mode 100644 index 0000000..9550485 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/PasswordAuthStrategy.java @@ -0,0 +1,104 @@ +package org.dromara.auth.service.impl; + +import cn.dev33.satoken.secure.BCrypt; +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.dromara.auth.domain.vo.LoginVo; +import org.dromara.auth.form.PasswordLoginBody; +import org.dromara.auth.properties.CaptchaProperties; +import org.dromara.auth.service.IAuthStrategy; +import org.dromara.auth.service.SysLoginService; +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.satoken.utils.LoginHelper; +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 = remoteUserService.getUserInfo(username, tenantId); + loginService.checkLogin(LoginType.PASSWORD, tenantId, username, () -> !BCrypt.checkpw(password, loginUser.getPassword())); + 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; + } + + /** + * 鏍¢獙楠岃瘉鐮� + * + * @param username 鐢ㄦ埛鍚� + * @param code 楠岃瘉鐮� + * @param uuid 鍞竴鏍囪瘑 + */ + private void validateCaptcha(String tenantId, String username, String code, String uuid) { + String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.defaultString(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-auth/src/main/java/org/dromara/auth/service/impl/SmsAuthStrategy.java b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SmsAuthStrategy.java new file mode 100644 index 0000000..d32b5aa --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SmsAuthStrategy.java @@ -0,0 +1,84 @@ +package org.dromara.auth.service.impl; + +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.dromara.auth.domain.vo.LoginVo; +import org.dromara.auth.form.SmsLoginBody; +import org.dromara.auth.service.IAuthStrategy; +import org.dromara.auth.service.SysLoginService; +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.satoken.utils.LoginHelper; +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 = remoteUserService.getUserInfoByPhonenumber(phonenumber, tenantId); + loginService.checkLogin(LoginType.SMS, tenantId, loginUser.getUsername(), () -> !validateSmsCode(tenantId, phonenumber, smsCode)); + 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 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-auth/src/main/java/org/dromara/auth/service/impl/SocialAuthStrategy.java b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SocialAuthStrategy.java new file mode 100644 index 0000000..ea1bde1 --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SocialAuthStrategy.java @@ -0,0 +1,109 @@ +package org.dromara.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 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 org.dromara.auth.domain.vo.LoginVo; +import org.dromara.auth.form.SocialLoginBody; +import org.dromara.auth.service.IAuthStrategy; +import org.dromara.auth.service.SysLoginService; +import org.dromara.common.core.exception.ServiceException; +import org.dromara.common.core.utils.ValidatorUtils; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.common.social.config.properties.SocialProperties; +import org.dromara.common.social.utils.SocialUtils; +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; + private final SysLoginService loginService; + + @DubboReference + private RemoteSocialService remoteSocialService; + @DubboReference + private RemoteUserService remoteUserService; + + /** + * 鐧诲綍-绗笁鏂规巿鏉冪櫥褰� + * + * @param body 鐧诲綍淇℃伅 + * @param client 瀹㈡埛绔俊鎭� + */ + @Override + public LoginVo login(String body, RemoteClientVo client) { + SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class); + ValidatorUtils.validate(loginBody); + AuthResponse<AuthUser> response = SocialUtils.loginAuth( + loginBody.getSource(), loginBody.getSocialCode(), + loginBody.getSocialState(), socialProperties); + if (!response.ok()) { + throw new ServiceException(response.getMsg()); + } + AuthUser authUserData = response.getData(); + if ("GITEE".equals(authUserData.getSource())) { + // 濡傜敤鎴蜂娇鐢� gitee 鐧诲綍椤烘墜 star 缁欎綔鑰呬竴鐐规敮鎸� 鎷掔粷鐧藉珫 + HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Vue-Plus") + .formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken())) + .executeAsync(); + HttpUtil.createRequest(Method.PUT, "https://gitee.com/api/v5/user/starred/dromara/RuoYi-Cloud-Plus") + .formStr(MapUtil.of("access_token", authUserData.getToken().getAccessToken())) + .executeAsync(); + } + + List<RemoteSocialVo> list = remoteSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid()); + if (CollUtil.isEmpty(list)) { + throw new ServiceException("浣犺繕娌℃湁缁戝畾绗笁鏂硅处鍙凤紝缁戝畾鍚庢墠鍙互鐧诲綍锛�"); + } + Optional<RemoteSocialVo> opt = list.stream().filter(x -> x.getTenantId().equals(loginBody.getTenantId())).findAny(); + if (opt.isEmpty()) { + throw new ServiceException("瀵逛笉璧凤紝浣犳病鏈夋潈闄愮櫥褰曞綋鍓嶇鎴凤紒"); + } + RemoteSocialVo socialVo = opt.get(); + + 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(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; + } + +} diff --git a/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/XcxAuthStrategy.java b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/XcxAuthStrategy.java new file mode 100644 index 0000000..20ec4cc --- /dev/null +++ b/ruoyi-auth/src/main/java/org/dromara/auth/service/impl/XcxAuthStrategy.java @@ -0,0 +1,69 @@ +package org.dromara.auth.service.impl; + +import cn.dev33.satoken.stp.SaLoginModel; +import cn.dev33.satoken.stp.StpUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.dubbo.config.annotation.DubboReference; +import org.dromara.auth.domain.vo.LoginVo; +import org.dromara.auth.form.XcxLoginBody; +import org.dromara.auth.service.IAuthStrategy; +import org.dromara.auth.service.SysLoginService; +import org.dromara.common.core.utils.ValidatorUtils; +import org.dromara.common.json.utils.JsonUtils; +import org.dromara.common.satoken.utils.LoginHelper; +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(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()); + loginVo.setOpenid(openid); + return loginVo; + } + +} diff --git a/ruoyi-auth/src/main/resources/application.yml b/ruoyi-auth/src/main/resources/application.yml new file mode 100644 index 0000000..24f5810 --- /dev/null +++ b/ruoyi-auth/src/main/resources/application.yml @@ -0,0 +1,31 @@ +# Tomcat +server: + port: 9210 + +# Spring +spring: + application: + # 搴旂敤鍚嶇О + name: ruoyi-auth + profiles: + # 鐜閰嶇疆 + active: @profiles.active@ + +--- # nacos 閰嶇疆 +spring: + cloud: + nacos: + # nacos 鏈嶅姟鍦板潃 + server-addr: @nacos.server@ + discovery: + # 娉ㄥ唽缁� + group: @nacos.discovery.group@ + namespace: ${spring.profiles.active} + config: + # 閰嶇疆缁� + group: @nacos.config.group@ + namespace: ${spring.profiles.active} + config: + import: + - optional:nacos:application-common.yml + - optional:nacos:${spring.application.name}.yml diff --git a/ruoyi-auth/src/main/resources/banner.txt b/ruoyi-auth/src/main/resources/banner.txt new file mode 100644 index 0000000..97c5c27 --- /dev/null +++ b/ruoyi-auth/src/main/resources/banner.txt @@ -0,0 +1,10 @@ +Spring Boot Version: ${spring-boot.version} +Spring Application Name: ${spring.application.name} + _ _ _ + (_) | | | | + _ __ _ _ ___ _ _ _ ______ __ _ _ _ | |_ | |__ +| '__|| | | | / _ \ | | | || ||______| / _` || | | || __|| '_ \ +| | | |_| || (_) || |_| || | | (_| || |_| || |_ | | | | +|_| \__,_| \___/ \__, ||_| \__,_| \__,_| \__||_| |_| + __/ | + |___/ \ No newline at end of file diff --git a/ruoyi-auth/src/main/resources/logback-plus.xml b/ruoyi-auth/src/main/resources/logback-plus.xml new file mode 100644 index 0000000..a2e187f --- /dev/null +++ b/ruoyi-auth/src/main/resources/logback-plus.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<configuration scan="true" scanPeriod="60 seconds" debug="false"> + <!-- 鏃ュ織瀛樻斁璺緞 --> + <property name="log.path" value="logs/${project.artifactId}"/> + <!-- 鏃ュ織杈撳嚭鏍煎紡 --> + <property name="console.log.pattern" + value="%red(%d{yyyy-MM-dd HH:mm:ss}) %green([%thread]) %highlight(%-5level) %boldMagenta(%logger{36}%n) - %msg%n"/> + + <!-- 鎺у埗鍙拌緭鍑� --> + <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> + <encoder> + <pattern>${console.log.pattern}</pattern> + <charset>utf-8</charset> + </encoder> + </appender> + + <include resource="logback-common.xml" /> + + <include resource="logback-logstash.xml" /> + + <!-- 寮�鍚� skywalking 鏃ュ織鏀堕泦 --> + <include resource="logback-skylog.xml" /> + + <!--绯荤粺鎿嶄綔鏃ュ織--> + <root level="info"> + <appender-ref ref="console"/> + </root> +</configuration> -- Gitblit v1.9.1