New file |
| | |
| | | #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} |
| | | |
New file |
| | |
| | | <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> |
New file |
| | |
| | | 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("(♥◠‿◠)ノ゙ 认证授权中心启动成功 ლ(´ڡ`ლ)゙ "); |
| | | } |
| | | } |
New file |
| | |
| | | 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)); |
| | | } |
| | | } |
New file |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | 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); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | 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("客户端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 = 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)) { |
| | | // 这里从referer中取值是为了本地使用hosts添加虚拟域名,方便本地环境调试 |
| | | host = referer.split("//")[1].split("/")[0]; |
| | | } else { |
| | | host = new URL(request.getRequestURL().toString()).getHost(); |
| | | } |
| | | // 根据域名进行筛选 |
| | | List<TenantListVo> list = StreamUtils.filter(voList, vo -> |
| | | StringUtils.equals(vo.getDomain(), host)); |
| | | // 返回对象 |
| | | LoginTenantVo vo = new LoginTenantVo(); |
| | | vo.setVoList(CollUtil.isNotEmpty(list) ? list : voList); |
| | | vo.setTenantEnabled(TenantHelper.isEnable()); |
| | | return R.ok(vo); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | 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> { |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | } |
New file |
| | |
| | | package org.dromara.auth.domain.vo; |
| | | |
| | | import lombok.Data; |
| | | |
| | | /** |
| | | * 租户列表 |
| | | * |
| | | * @author zhujie |
| | | */ |
| | | @Data |
| | | public class TenantListVo { |
| | | |
| | | private String tenantId; |
| | | |
| | | private String companyName; |
| | | |
| | | private String domain; |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | } |
New file |
| | |
| | | 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; |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | /** |
| | | * 第三方登录code |
| | | */ |
| | | @NotBlank(message = "{social.code.not.blank}") |
| | | private String socialCode; |
| | | |
| | | /** |
| | | * 第三方登录socialState |
| | | */ |
| | | @NotBlank(message = "{social.state.not.blank}") |
| | | private String socialState; |
| | | |
| | | } |
New file |
| | |
| | | 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 { |
| | | |
| | | /** |
| | | * 小程序id(多个小程序时使用) |
| | | */ |
| | | private String appid; |
| | | |
| | | /** |
| | | * 小程序code |
| | | */ |
| | | @NotBlank(message = "{xcx.code.not.blank}") |
| | | private String xcxCode; |
| | | |
| | | } |
New file |
| | |
| | | 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) { |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | |
| | | } |
New file |
| | |
| | | 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); |
| | | |
| | | } |
New file |
| | |
| | | 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"); |
| | | } |
| | | } |
| | | } |
New file |
| | |
| | | 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); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | 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(); |
| | | } |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | 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); |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |
New file |
| | |
| | | # 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 |
New file |
| | |
| | | Spring Boot Version: ${spring-boot.version} |
| | | Spring Application Name: ${spring.application.name} |
| | | _ _ _ |
| | | (_) | | | | |
| | | _ __ _ _ ___ _ _ _ ______ __ _ _ _ | |_ | |__ |
| | | | '__|| | | | / _ \ | | | || ||______| / _` || | | || __|| '_ \ |
| | | | | | |_| || (_) || |_| || | | (_| || |_| || |_ | | | | |
| | | |_| \__,_| \___/ \__, ||_| \__,_| \__,_| \__||_| |_| |
| | | __/ | |
| | | |___/ |
New file |
| | |
| | | <?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> |