微服务版后端初始化

This commit is contained in:
yaoyn
2025-02-08 17:51:37 +08:00
parent 54af6be188
commit da009a7cc4
1897 changed files with 429541 additions and 81 deletions

15
xjrsoft-auth/Dockerfile Normal file
View File

@ -0,0 +1,15 @@
# 基础镜像
FROM nexus.gdyditc.com:8082/openjdk:11-arm64
# author
MAINTAINER xjrsoft
# 挂载目录
VOLUME /home/xjrsoft
# 创建目录
RUN mkdir -p /home/xjrsoft
# 指定路径
WORKDIR /home/xjrsoft
# 复制jar文件到路径
COPY ./xjrsoft-auth/target/xjrsoft-auth.jar /home/xjrsoft/xjrsoft-auth.jar
# 启动认证服务
ENTRYPOINT ["java","-jar","xjrsoft-auth.jar","-Dfile.encoding=UTF-8"]

256
xjrsoft-auth/pom.xml Normal file
View File

@ -0,0 +1,256 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>xjrsoft-cloud</artifactId>
<groupId>com.xjrsoft</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>xjrsoft-auth</artifactId>
<name>${project.artifactId}</name>
<version>${xjrsoft.framework.version}</version>
<properties>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Sa-Token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
</dependency>
<!-- Sa-Token 整合 Redis (使用 jackson 序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Sa-Token插件权限缓存与业务缓存分离 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-alone-redis</artifactId>
</dependency>
<!-- Http请求工具 -->
<dependency>
<groupId>com.ejlchina</groupId>
<artifactId>okhttps</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<dependency>
<groupId>com.xjrsoft</groupId>
<artifactId>xjrsoft-service-system-api</artifactId>
<version>${xjrsoft.framework.version}</version>
</dependency>
<dependency>
<groupId>com.xjrsoft</groupId>
<artifactId>xjrsoft-service-organization-api</artifactId>
<version>${xjrsoft.framework.version}</version>
</dependency>
<!--引入nacos依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入spring-cloud-alibaba-nacos-config依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!--引入spring-cloud-alibaba-nacos-config bootstrap依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!--引入sentinel依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--引入sentinel-datasource-nacos依赖-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>com.xjrsoft</groupId>
<artifactId>xjrsoft-common-core</artifactId>
<version>${xjrsoft.framework.version}</version>
</dependency>
<dependency>
<groupId>com.xjrsoft</groupId>
<artifactId>xjrsoft-service-organization-api</artifactId>
<version>${xjrsoft.framework.version}</version>
</dependency>
<dependency>
<groupId>com.xjrsoft</groupId>
<artifactId>xjrsoft-common-tenant</artifactId>
<version>${xjrsoft.framework.version}</version>
</dependency>
<dependency>
<groupId>com.xjrsoft</groupId>
<artifactId>xjrsoft-common-redis</artifactId>
<version>${xjrsoft.framework.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>com.xjrsoft</groupId>
<artifactId>xjrsoft-common-redis</artifactId>
<version>${xjrsoft.framework.version}</version>
</dependency>
<dependency>
<groupId>com.xjrsoft</groupId>
<artifactId>xjrsoft-service-workflow-api</artifactId>
<version>${xjrsoft.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.xjrsoft</groupId>-->
<!-- <artifactId>xjrsoft-common-mybatis</artifactId>-->
<!-- <version>${xjrsoft.framework.version}</version>-->
<!-- <exclusions>-->
<!-- <exclusion>-->
<!-- <groupId>com.baomidou</groupId>-->
<!-- <artifactId>dynamic-datasource-spring-boot-starter</artifactId>-->
<!-- </exclusion>-->
<!-- </exclusions>-->
<!-- </dependency>-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-authz-client</artifactId>
<version>${keycloak.version}</version>
</dependency>
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>
<version>${justauth.version}</version>
</dependency>
<dependency>
<groupId>com.xjrsoft</groupId>
<artifactId>xjrsoft-service-organization-api</artifactId>
<version>${xjrsoft.framework.version}</version>
</dependency>
<dependency>
<groupId>kingbase</groupId>
<artifactId>kingbase</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,21 @@
package com.xjrsoft.auth;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.ComponentScan;
/**
* 启动Sa-SSO Server端
* @author Zexy
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients(basePackages = "com.xjrsoft")
@ComponentScan(value = "com.xjrsoft")
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class, args);
}
}

View File

@ -0,0 +1,155 @@
package com.xjrsoft.auth.config;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializerBase;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
/**
* @Author: tzx
* @Date: 2022/8/2 16:54
*/
@Configuration
public class JacksonConfig {
@Value("${spring.jackson.date-format}")
private String pattern;
/**
* 默认设置所有long类型 序列化返回前端 全变成string
* 所有LocalDatetime 序列号返回前端 全部格式化
* @return
*/
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() {
return builder -> {
builder.serializerByType(Long.class, ToStringSerializer.instance);
builder.serializerByType(Long.TYPE, ToStringSerializer.instance);
builder.serializerByType(BigDecimal.class, bigDecimalSerializer());
builder.serializerByType(LocalDateTime.class, localDateTimeSerializer());
builder.deserializerByType(LocalDateTime.class, localDateTimeDeserializer());
builder.featuresToEnable(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN);
builder.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
builder.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE);
builder.visibility(PropertyAccessor.SETTER, JsonAutoDetect.Visibility.NONE);
builder.visibility(PropertyAccessor.CREATOR, JsonAutoDetect.Visibility.NONE);
};
}
/**
* Jackson格式化BigDecimal类型数据
* @return
*/
public ToStringSerializerBase bigDecimalSerializer() {
return new ToStringSerializerBase(BigDecimal.class) {
protected final static int MAX_BIG_DECIMAL_SCALE = 9999;
@Override
public String valueToString(Object value) {
throw new IllegalStateException();
}
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (gen.isEnabled(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN)) {
final BigDecimal bd = (BigDecimal) value;
// 24-Aug-2016, tatu: [core#315] prevent possible DoS vector, so we need this
if (!_verifyBigDecimalRange(gen, bd)) {
// ... but wouldn't it be nice to trigger error via generator? Alas,
// no method to do that. So we'll do...
final String errorMsg = String.format(
"Attempt to write plain `java.math.BigDecimal` (see JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN) with illegal scale (%d): needs to be between [-%d, %d]",
bd.scale(), MAX_BIG_DECIMAL_SCALE, MAX_BIG_DECIMAL_SCALE);
provider.reportMappingProblem(errorMsg);
}
// text = bd.toPlainString();
gen.writeNumber(bd);
} else {
gen.writeString(value.toString());
}
}
// 24-Aug-2016, tatu: [core#315] prevent possible DoS vector, so we need this
protected boolean _verifyBigDecimalRange(JsonGenerator gen, BigDecimal value) throws IOException {
int scale = value.scale();
return ((scale >= -MAX_BIG_DECIMAL_SCALE) && (scale <= MAX_BIG_DECIMAL_SCALE));
}
};
}
/**
* localDatetime格式化
* @return
*/
@Bean
public LocalDateTimeSerializer localDateTimeSerializer(){
return new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(pattern));
}
/**
* localDatetime格式化
* @return
*/
@Bean
public JsonDeserializer<LocalDateTime> localDateTimeDeserializer(){
final String pattern = this.pattern;
return new JsonDeserializer<LocalDateTime>(){
@SneakyThrows
@Override
public LocalDateTime deserialize(JsonParser p, DeserializationContext context) throws IOException {
String value = p.getText();
if (StrUtil.isEmpty(value)) {
return null;
}
Class<?> aClass = p.getCurrentValue().getClass();
Field field = ReflectUtil.getField(aClass, p.getCurrentName());
String format = pattern;
JsonFormat annotation = field.getAnnotation(JsonFormat.class);
if (annotation != null) {
format = StringUtils.defaultIfEmpty(annotation.pattern(), pattern);
}
return LocalDateTimeUtil.parse(value, format);
}
};
}
@Bean
public Converter<String, LocalTime> stringToLocalTimeConverter() {
return new Converter<String, LocalTime>(){
@Override
public LocalTime convert(String value) {
if (StrUtil.isNotBlank(value)) {
return LocalTime.parse(value);
}
return null;
}
};
}
}

View File

@ -0,0 +1,8 @@
package com.xjrsoft.auth.constant;
/**
* @Author: tzx
* @Date: 2023/9/20 14:50
*/
public interface AuthConstant {
}

View File

@ -0,0 +1,263 @@
package com.xjrsoft.auth.controller;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.xjrsoft.auth.dto.LoginDto;
import com.xjrsoft.auth.service.ILoginService;
import com.xjrsoft.auth.service.SsoService;
import com.xjrsoft.common.core.annotation.XjrLog;
import com.xjrsoft.common.core.config.KeyCloakConfig;
import com.xjrsoft.common.core.config.XjrSmsConfig;
import com.xjrsoft.common.core.constant.GlobalConstant;
import com.xjrsoft.common.core.domain.result.R;
import com.xjrsoft.common.core.enums.EnabledMark;
import com.xjrsoft.common.core.enums.YesOrNoEnum;
import com.xjrsoft.common.core.exception.MyException;
import com.xjrsoft.common.core.uitls.SmsSender;
import com.xjrsoft.common.redis.service.RedisUtil;
import com.xjrsoft.organization.client.IOauthClient;
import com.xjrsoft.organization.client.IUserClient;
import com.xjrsoft.organization.entity.User;
import com.xjrsoft.system.client.ILoginConfigClient;
import com.xjrsoft.system.dto.*;
import com.xjrsoft.system.entity.LoginConfig;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.utils.AuthStateUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.keycloak.authorization.client.AuthzClient;
import org.keycloak.authorization.client.Configuration;
import org.keycloak.representations.AccessTokenResponse;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @Author: tzx
* @Date: 2023/10/12 15:39
*/
@RestController
@Tag(name = "登录模块")
@RequestMapping(GlobalConstant.SYSTEM_MODULE_PREFIX)
@RequiredArgsConstructor
public class LoginController {
private final ILoginService loginService;
private final IUserClient userClient;
private final RedisUtil redisUtil;
private KeyCloakConfig keyCloakConfig;
private SmsSender smsSender;
private XjrSmsConfig smsConfig;
private IOauthClient oauthClient;
private final ILoginConfigClient loginConfigClient;
private SsoService ssoService;
@PostMapping(value = {"/login/{ltpasToken}", "/login"})
@Operation(summary = "登录", description = "传入账号:account,密码:password")
@XjrLog(value = "账号密码登录成功")
public R login(@RequestBody @Valid LoginDto dto, @PathVariable(required = false) String ltpasToken) {
if(StrUtil.isNotBlank (ltpasToken)) {
String userName = ssoService.auth(ltpasToken);
if(StrUtil.isNotBlank(userName)) {
dto.setUserName(userName);
User user = loginService.getUserInfoByName(userName);
dto.setPassword(user.getPassword());
return R.ok(loginService.login(dto));
} else {
return R.error("秘钥登录失败");
}
}
return R.ok(loginService.login(dto));
}
@PostMapping("/create-token")
@Operation(summary = "创建token", description = "传入账号:account,密码:password")
@XjrLog(value = "账号密码登录成功")
public R createToken(@RequestBody @Valid CreateTokenDto dto) {
return R.ok(loginService.createToken(dto));
}
/**
* 发送验证码
*/
@GetMapping("/captcha")
@XjrLog(value = "发送验证码")
@Operation(summary = "发送验证码", description = "传入账号:mobile")
public R captcha(@RequestParam String mobile) {
// 验证短信限时发送次数验证
// 获取该号码短信的发送次数
Object o = redisUtil.get(GlobalConstant.CACHE_COUNT_SMS_CODE_PREFIX + mobile);
int sendCount = NumberUtils.toInt(String.valueOf(o));
if (o == null) {
// 未发送过验证码,初始化
redisUtil.set(GlobalConstant.CACHE_COUNT_SMS_CODE_PREFIX + mobile, 1, smsConfig.getLimitTime() * 3600);
} else if (sendCount > smsConfig.getLimitCount()) {
// 发送次数超过限制
return R.error("发送验证码次数达上限,请稍后再试!");
} else {
// 更新发送次数
long expire = redisUtil.getExpire(GlobalConstant.CACHE_COUNT_SMS_CODE_PREFIX + mobile);
redisUtil.set(GlobalConstant.CACHE_COUNT_SMS_CODE_PREFIX + mobile, sendCount + 1, expire);
}
//生成六位数的字符串
String code = RandomUtils.nextInt(100000, 999999) + StringPool.EMPTY;
smsSender.sendCaptcha(mobile, code);
redisUtil.set(GlobalConstant.CAPTCHA + StringPool.UNDERSCORE + mobile, code, 60L);
return R.ok(Boolean.TRUE);
}
/**
* 验证码
*/
@PostMapping("/captcha")
@XjrLog(value = "验证码登录")
public R loginByCaptcha(@RequestBody LoginCaptchaDto loginCaptchaDto) {
String mobile = loginCaptchaDto.getMobile();
String code = redisUtil.get(GlobalConstant.CAPTCHA + StringPool.UNDERSCORE + mobile);
if (code != null && StringUtils.equals(code, loginCaptchaDto.getCode())) {
User user = userClient.getUserByMobileFeign(mobile);
if (user == null) {
return R.error("用户不存在!");
}
if (user.getEnabledMark() == EnabledMark.DISABLED.getCode()) {
return R.error("账户未启用");
}
//此登录接口登录web端
StpUtil.login(user.getId(), "PC");
SaSession tokenSession = StpUtil.getTokenSession();
tokenSession.set(GlobalConstant.LOGIN_USER_INFO_KEY, user);
Map<String, Object> vo = new HashMap<>(1);
vo.put(GlobalConstant.TOKEN_KEY, StpUtil.getTokenValue());
return R.ok(vo);
}
return R.error("验证码不存在!");
}
/**
* 退出
*/
@PostMapping("/logout")
public R logout() {
StpUtil.logout();
return R.ok("登出成功!");
}
@PostMapping("/token")
@Operation(summary = "根据keycloak-token 登录", description = "传入keycloak-token")
@XjrLog(value = "keycloak-token登录成功")
public R loginByToken(@RequestBody KeyCloakLoginInfoDto dto) {
Map<String, Object> credentialsMap = new HashMap<>(1);
credentialsMap.put("secret", keyCloakConfig.getSecret());
Configuration configuration = new Configuration(keyCloakConfig.getUrl(), keyCloakConfig.getRealm(), keyCloakConfig.getClientId(), credentialsMap, null);
AuthzClient authzClient = AuthzClient.create(configuration);
AccessTokenResponse response = authzClient.obtainAccessToken(keyCloakConfig.getUserName(), keyCloakConfig.getPassword());
if (StrUtil.isNotBlank(response.getError())) {
return R.error(response.getError());
}
//TODO keycloak 登陆过 解析token获取数据 做框架登录操作
JWT jwt = JWTUtil.parseToken(dto.getToken());
Object code = jwt.getPayload(keyCloakConfig.getPayload());
User user = userClient.getUserByCodeFeign(String.valueOf(code));
List<LoginConfig> list = loginConfigClient.getAllListFeign();
if (list.size() == 0){//如果没有,则设置为默认配置,并进行保存
LoginConfig loginConfig = new LoginConfig();
loginConfig.setId(1L);
loginConfig.setMulLogin("0,1");
loginConfig.setMutualExclusion(YesOrNoEnum.YES.getCode());
loginConfig.setWithoutLogin(YesOrNoEnum.YES.getCode());
loginConfig.setPasswordStrategy(YesOrNoEnum.YES.getCode());
loginConfig.setStrategyMaxNumber(7);
list.add(loginConfig);
loginConfigClient.addLoginConfigFeign(loginConfig);
}
LoginConfig loginConfig = list.get(0);
if (user == null) {
return R.error("帐号密码错误!");
} else if (!Integer.valueOf(1).equals(user.getEnabledMark())) {
return R.error("当前账号已被锁定,请联系管理员!");
}
if (StrUtil.isNotBlank(dto.getDevice()) && dto.getDevice() == "PC" && !loginConfig.getMulLogin().contains("0")){
throw new MyException("当前Web端登录未授权请联系管理员");
}else if (StrUtil.isNotBlank(dto.getDevice()) && dto.getDevice() == "APP" && !loginConfig.getMulLogin().contains("1")){
throw new MyException("当前APP端登录未授权请联系管理员");
}else {
if (StrUtil.isBlank(loginConfig.getMulLogin()) || !loginConfig.getMulLogin().contains("0")){
throw new MyException("当前Web端登录未授权请联系管理员");
}
}
SaTokenConfig oldConfig = SaManager.getConfig();
if (loginConfig.getMutualExclusion() == YesOrNoEnum.NO.getCode()){
//不开启同端互斥
oldConfig.setIsConcurrent(true);
}else {
//开启同端互斥
oldConfig.setIsConcurrent(false);
}
// 注入到 SaManager 中
SaManager.setConfig(oldConfig);
if (loginConfig.getWithoutLogin() == YesOrNoEnum.YES.getCode()){//开启就设置为7天免登录
StpUtil.login(user.getId(),new SaLoginModel().setDevice(dto.getDevice()).setTimeout(60 * 60 * 24 * 7));
}else {
//此登录接口登录web端,IsLastingCookie设置为false也就是关闭浏览器后再次打开需要重新登录
StpUtil.login(user.getId(), new SaLoginModel().setDevice(dto.getDevice()).setIsLastingCookie(false));
}
SaSession tokenSession = StpUtil.getTokenSession();
tokenSession.set(GlobalConstant.LOGIN_USER_INFO_KEY, user);
Map<String, Object> vo = new HashMap<>(1);
vo.put(GlobalConstant.TOKEN_KEY, StpUtil.getTokenValue());
return R.ok("登录成功!", vo);
}
@PostMapping("/qrcode-login")
@Operation(summary = "oauth 扫码登录", description = "oauth 扫码登录")
@XjrLog(value = "oauth 扫码登录")
public R createAuthorizeUrl(@Valid @RequestBody CreateAuthorizeUrlDto dto){
AuthRequest authRequest = oauthClient.getAuthRequest(dto.getSource());
String authorizeUrl = authRequest.authorize(AuthStateUtils.createState());
return R.ok(authorizeUrl);
}
}

View File

@ -0,0 +1,97 @@
package com.xjrsoft.auth.controller;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.sso.SaSsoHandle;
import com.ejlchina.okhttps.OkHttps;
import com.xjrsoft.common.core.constant.GlobalConstant;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Sa-Token-SSO Server端 Controller
* @author Zexy
*/
@RestController
@Tag(name = "授权中心")
public class SsoServerController {
/**
* SSO-Server统一认证地址
*/
@RequestMapping(GlobalConstant.AUTH_CENTER)
@Operation(summary = "统一认证地址")
public Object ssoAuth() {
return SaSsoHandle.ssoAuth();
}
/**
* SSO-ServerRestAPI 登录接口
*/
@RequestMapping(GlobalConstant.AUTH_LOGIN)
@Operation(summary = "SSO-ServerRestAPI 登录接口")
public Object ssoDoLogin() {
return SaSsoHandle.ssoDoLogin();
}
/**
* SSO-Server校验ticket 获取账号id
*/
@RequestMapping(GlobalConstant.AUTH_CHECK_TICKET)
@Operation(summary = "SSO-Server校验ticket 获取账号id")
public Object ssoCheckTicket() {
return SaSsoHandle.ssoCheckTicket();
}
/**
* 注销单点登录
* @return
*/
@RequestMapping(GlobalConstant.AUTH_LOGOUT)
@Operation(summary = "SSO-Server注销单点登录")
public Object ssoLogout() {
return SaSsoHandle.ssoServerLogout();
}
/**
* 配置SSO相关参数
*
* @param cfg
*/
@Autowired
private void configSso(SaTokenConfig cfg) {
cfg.sso
// 配置未登录时返回的View
.setNotLoginView(() -> "当前会话在SSO-Server端尚未登录请先访问"
+ "<a href='/sso/doLogin?name=sa&pwd=123456' target='_blank'> doLogin登录 </a>"
+ "进行登录之后,刷新页面开始授权")
// 配置:登录处理函数
.setDoLoginHandle((name, pwd) -> {
SaRequest request = SaHolder.getRequest();
name = request.getParam("username");
pwd = request.getParam("password");
String s = SaSecureUtil.md5BySalt(pwd, GlobalConstant.SECRET_KEY);
return null;
})// 配置Http请求处理器
.setSendHttp(url -> {
// 此处为了提高响应速度这里可将sync换为async
return OkHttps.async(url).get();
});
}
}

View File

@ -0,0 +1,32 @@
package com.xjrsoft.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotBlank;
/**
* @author Zexy
*/
@Data
public class LoginDto {
@NotBlank(message = "账号不能为空!")
private String userName;
@Schema(name = "密码")
// @NotBlank(message = "密码不能为空!")
// @Length(min = 6,max = 32,message = "密码长度不得小于6个字符不得大于32个字符")
private String password;
@Schema(name = "租户码")
private String tenantCode;
@Schema(name = "设备类型-默认为PCpc为0app为1")
private Integer deviceType;
@Schema(name = "互信token")
private String token;
}

View File

@ -0,0 +1,22 @@
package com.xjrsoft.auth.handler;
import com.xjrsoft.common.core.domain.result.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@Slf4j
public class MyExceptionHandler {
// @ResponseBody
// @ExceptionHandler(value =Exception.class)
// public R exceptionHandler(Exception e){
// e.printStackTrace();
// return R.error("认证中心错误,请联系管理员!");
// }
}

View File

@ -0,0 +1,22 @@
package com.xjrsoft.auth.service;
import com.xjrsoft.auth.dto.LoginDto;
import com.xjrsoft.organization.entity.User;
import com.xjrsoft.system.dto.CreateTokenDto;
import com.xjrsoft.system.entity.LoginConfig;
import com.xjrsoft.system.vo.CreateTokenVo;
import com.xjrsoft.system.vo.LoginVo;
/**
* @Author: tzx
* @Date: 2023/4/21 14:20
*/
public interface ILoginService {
User getUserInfoByName(String userName);
LoginVo login(LoginDto dto);
CreateTokenVo createToken(CreateTokenDto dto);
}

View File

@ -0,0 +1,20 @@
package com.xjrsoft.auth.service;
import com.xjrsoft.auth.vo.ResponseVo;
import com.xjrsoft.common.core.result.Response;
import com.xjrsoft.organization.vo.LoginVo;
import javax.servlet.http.HttpServletResponse;
public interface SsoService {
void auth(String redirect, String ltpasToken, String method,HttpServletResponse response);
String auth(String ltpasToken);
Response<LoginVo> getUser();
ResponseVo getTodoTask(String type, String ltpasToken);
String getLtpasToken(String username);
}

View File

@ -0,0 +1,340 @@
package com.xjrsoft.auth.service.impl;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.temp.SaTempUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.xjrsoft.auth.dto.LoginDto;
import com.xjrsoft.auth.service.ILoginService;
import com.xjrsoft.common.core.config.LicenseConfig;
import com.xjrsoft.common.core.constant.GlobalConstant;
import com.xjrsoft.common.core.constant.StringPool;
import com.xjrsoft.common.core.enums.EnabledMark;
import com.xjrsoft.common.core.enums.YesOrNoEnum;
import com.xjrsoft.common.core.exception.MyException;
import com.xjrsoft.common.core.exception.ValidationException;
import com.xjrsoft.common.redis.service.RedisUtil;
import com.xjrsoft.organization.client.*;
import com.xjrsoft.organization.entity.*;
import com.xjrsoft.organization.service.*;
import com.xjrsoft.system.client.ILoginConfigClient;
import com.xjrsoft.system.client.ITenantClient;
import com.xjrsoft.system.dto.CreateTokenDto;
import com.xjrsoft.system.entity.LoginConfig;
import com.xjrsoft.system.entity.Tenant;
import com.xjrsoft.system.service.ILoginConfigService;
import com.xjrsoft.system.vo.CreateTokenVo;
import com.xjrsoft.system.vo.LoginVo;
import com.xjrsoft.tenant.config.TenantConfig;
import com.xjrsoft.tenant.util.SecureUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
import java.util.stream.Collectors;
/**
* @Author: tzx
* @Date: 2023/4/21 14:22
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class LoginServiceImpl implements ILoginService {
private final IUserClient userClient;
private final IUserDeptRelationClient userDeptRelationClient;
private final IUserPostRelationClient userPostRelationClient;
private final IPostClient postClient;
private final IDepartmentClient departmentClient;
private final ITenantClient tenantClient;
private final RedisUtil redisUtil;
private final LicenseConfig licenseConfig;
private final TenantConfig tenantConfig;
private final ILoginConfigClient loginConfigClient;
private final IUserRoleRelationClient userRoleRelationClient;
@Override
public User getUserInfoByName(String userName) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName, userName);
User loginUser = userService.getOne(queryWrapper);
return loginUser;
}
@Override
public LoginVo login(@RequestBody(required = false) LoginDto dto) {
LoginVo result = new LoginVo();
if (licenseConfig.getEnabled()) {
//查出所有在线用户
List<String> onlineUser = StpUtil.searchSessionId("", 0, Integer.MAX_VALUE);
//如果已经登录人数超过授权人数 不允许登录
if (onlineUser.size() >= licenseConfig.getLoginMax()) {
throw new MyException("登录人数超过授权人数,无法登录,请联系管理员!");
}
}
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUserName, dto.getUserName());
User loginUser = userService.getOne(queryWrapper);
List<LoginConfig> list = loginConfigService.list();
if (list.size() == 0){//如果没有,则设置为默认配置,并进行保存
LoginConfig loginConfig = new LoginConfig();
loginConfig.setId(1L);
loginConfig.setMulLogin("0,1");
loginConfig.setMutualExclusion(YesOrNoEnum.YES.getCode());
loginConfig.setWithoutLogin(YesOrNoEnum.YES.getCode());
loginConfig.setPasswordStrategy(YesOrNoEnum.YES.getCode());
loginConfig.setStrategyMaxNumber(7);
list.add(loginConfig);
loginConfigService.save(loginConfig);
}
LoginConfig loginConfig = list.get(0);
if (StrUtil.isNotBlank(loginConfig.getMulLogin())){
if (ObjectUtil.isNotEmpty(dto.getDeviceType()) && dto.getDeviceType() == 0 && !loginConfig.getMulLogin().contains("0")){
throw new MyException("当前Web端登录未授权请联系管理员");
}else if (ObjectUtil.isNotEmpty(dto.getDeviceType()) && dto.getDeviceType() == 1 && !loginConfig.getMulLogin().contains("1")){
throw new MyException("当前APP端登录未授权请联系管理员");
}
}else {
throw new MyException("当前Web端、app端登录未授权请联系管理员");
}
//if (loginUser == null || !StrUtil.equals(loginUser.getPassword(), SaSecureUtil.md5BySalt(dto.getPassword(), GlobalConstant.SECRET_KEY))) {
if(checkPassword(loginUser,dto)){
if (loginUser.getEnabledMark() == EnabledMark.DISABLED.getCode()) {
throw new MyException("当前账号已被锁定,请联系管理员!");
}
if (loginConfig.getPasswordStrategy() == YesOrNoEnum.YES.getCode()){ // 如果开启了密码策略,密码错误次数超过最大次数就锁定
Integer number = 1;
String value = redisUtil.get(GlobalConstant.LOGIN_ERROR_NUMBER + loginUser.getId());
if (StrUtil.isNotBlank(value)){
if (loginConfig.getStrategyMaxNumber() - 1 > Integer.valueOf(value)){
redisUtil.set(GlobalConstant.LOGIN_ERROR_NUMBER + loginUser.getId() , Integer.valueOf(value) + 1);
}else {
loginUser.setEnabledMark(EnabledMark.DISABLED.getCode());
userService.updateById(loginUser);
throw new MyException("当前账号已被锁定,请联系管理员");
}
}else {
if (loginConfig.getStrategyMaxNumber() == 1){
loginUser.setEnabledMark(EnabledMark.DISABLED.getCode());
userService.updateById(loginUser);
throw new MyException("当前账号已被锁定,请联系管理员");
}else {
redisUtil.set(GlobalConstant.LOGIN_ERROR_NUMBER + loginUser.getId() , number);
}
}
}
throw new MyException("账号或密码不正确");
}
//登录成功之后将对应的密码错误次数给删除掉
redisUtil.delete(GlobalConstant.LOGIN_ERROR_NUMBER + loginUser.getId());
SaTokenConfig oldConfig = SaManager.getConfig();
if (loginConfig.getMutualExclusion() == YesOrNoEnum.NO.getCode()){
//不开启同端互斥
oldConfig.setIsConcurrent(true);
}else {
//开启同端互斥
oldConfig.setIsConcurrent(false);
}
// 注入到 SaManager 中
SaManager.setConfig(oldConfig);
//此登录接口
if (loginConfig.getWithoutLogin() == YesOrNoEnum.YES.getCode()){//开启就设置为7天免登录
if (ObjectUtil.isNotEmpty(dto.getDeviceType()) && dto.getDeviceType() == 1){
StpUtil.login(loginUser.getId(),new SaLoginModel().setDevice("APP").setTimeout(60 * 60 * 24 * 7));
} else {//默认为PC端登录
StpUtil.login(loginUser.getId(),new SaLoginModel().setDevice("PC").setTimeout(60 * 60 * 24 * 7));
}
}else {
//此登录接口登录web端,IsLastingCookie设置为false也就是关闭浏览器后再次打开需要重新登录
if (ObjectUtil.isNotEmpty(dto.getDeviceType()) && dto.getDeviceType() == 1){
StpUtil.login(loginUser.getId(), new SaLoginModel().setDevice("APP").setIsLastingCookie(false));
} else {//默认为PC端登录
StpUtil.login(loginUser.getId(), new SaLoginModel().setDevice("PC").setIsLastingCookie(false));
}
}
// StpUtil.login(loginUser.getId(),"PC");
SaSession tokenSession = StpUtil.getTokenSession();
List<UserDeptRelation> userDeptRelations = userDeptRelationService.list(Wrappers.lambdaQuery(UserDeptRelation.class)
.eq(UserDeptRelation::getUserId, StpUtil.getLoginIdAsLong()));
List<UserPostRelation> userPostRelations = userPostRelationService.list(Wrappers.lambdaQuery(UserPostRelation.class)
.eq(UserPostRelation::getUserId, StpUtil.getLoginIdAsLong()));
//获取登陆人所选择的身份缓存
String postId = redisUtil.get(GlobalConstant.LOGIN_IDENTITY_CACHE_PREFIX + loginUser.getId());
Post post = new Post();
if (StrUtil.isNotBlank(postId) && !postId.equals("null")) {
post = postService.getById(Long.valueOf(postId));
}
if (userPostRelations.size() > 0) {
List<Long> postIds = userPostRelations.stream().map(UserPostRelation::getPostId).collect(Collectors.toList());
List<Post> postList = postService.listByIds(postIds);
if ((post == null || StrUtil.isBlank(postId)) && com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isNotEmpty(postList)) {
post = postList.get(0);
}
tokenSession.set(GlobalConstant.LOGIN_USER_POST_INFO_KEY, post);
tokenSession.set(GlobalConstant.LOGIN_USER_POST_LIST_KEY, postList);
loginUser.setPostId(post.getId());
//将登陆人所选择的身份缓存起来
//切换身份的时候 会一起修改
//redisUtil.set(GlobalConstant.LOGIN_IDENTITY_CACHE_PREFIX + loginUser.getId(), post.getId());
}
if (userDeptRelations.size() > 0) {
// 存当前用户所有部门到缓存
List<Long> departmentIds = userDeptRelations.stream().map(UserDeptRelation::getDeptId).collect(Collectors.toList());
List<Department> departmentList = departmentService.listByIds(departmentIds);
departmentService.queryPathFromRoot(departmentList);
tokenSession.set(GlobalConstant.LOGIN_USER_DEPT_LIST_KEY, departmentList);
//主部门
List<Long> mainDepartmentIds = userDeptRelations.stream().filter(x->YesOrNoEnum.YES.getTextCode().equals(x.getIsMain()))
.map(UserDeptRelation::getDeptId).collect(Collectors.toList());
tokenSession.set(GlobalConstant.LOGIN_USER_MAIN_DEPT_ID_KEY, mainDepartmentIds);
Department department = departmentList.get(0);
//如果此人有岗位 使用岗位的deptId 找到当前组织机构
if (ObjectUtil.isNotNull(post.getId())) {
department = departmentService.getById(post.getDeptId());
}else if(ObjectUtil.isNotEmpty(mainDepartmentIds)){
//找一个主部门
department=departmentList.stream().filter(x->mainDepartmentIds.contains(x.getId())).findFirst().get();
}
tokenSession.set(GlobalConstant.LOGIN_USER_DEPT_INFO_KEY, department);
loginUser.setDepartmentId(department.getId());
}
//根据登录信息 将post 和 department 信息存入用户信息中
tokenSession.set(GlobalConstant.LOGIN_USER_INFO_KEY, loginUser);
result.setToken(StpUtil.getTokenValue());
return result;
}
/**
* 判断用户名和密码是否正确有2中情况1账号密码登录2互信token登录
* 方式1是传入的未加密的密码方式2传入的是通过md5已加密的密码所以通过判断密码的长度来判断是否已加密
* */
public boolean checkPassword(User loginUser,LoginDto dto)
{
//如果长度是32,即传入的是密文
if(dto.getPassword().length() == 32){
return (loginUser == null || !StrUtil.equals(loginUser.getPassword(), dto.getPassword()));
}
else {
return (loginUser == null || !StrUtil.equals(loginUser.getPassword(), SaSecureUtil.md5BySalt(dto.getPassword(), GlobalConstant.SECRET_KEY)));
}
}
@Override
public CreateTokenVo createToken(CreateTokenDto dto) {
CreateTokenVo vo = new CreateTokenVo();
if(dto.getExpire() == -1){
String token = SaTempUtil.createToken(IdUtil.fastSimpleUUID() + StringPool.UNDERSCORE + GlobalConstant.SECRET_KEY, Integer.MAX_VALUE);
vo.setToken(token);
return vo;
}
else {
String token = SaTempUtil.createToken(IdUtil.fastSimpleUUID() + StringPool.UNDERSCORE + GlobalConstant.SECRET_KEY, dto.getExpire());
vo.setToken(token);
return vo;
}
}
/**
* 校验登录的配置是否满足
* @param deviceType
* @return
*/
@Override
public LoginConfig validateLoginConfig(Integer deviceType) {
if (licenseConfig.getEnabled()) {
//查出所有在线用户
List<String> onlineUser = StpUtil.searchSessionId("", 0, Integer.MAX_VALUE);
//如果已经登录人数超过授权人数 不允许登录
if (onlineUser.size() >= licenseConfig.getLoginMax()) {
throw new MyException("登录人数超过授权人数,无法登录,请联系管理员!");
}
}
List<LoginConfig> list = loginConfigClient.getAllListFeign();
if (list.size() == 0){//如果没有,则设置为默认配置,并进行保存
LoginConfig loginConfig = new LoginConfig();
loginConfig.setMulLogin("0,1");
loginConfig.setMutualExclusion(YesOrNoEnum.YES.getCode());
loginConfig.setWithoutLogin(YesOrNoEnum.YES.getCode());
loginConfig.setPasswordStrategy(YesOrNoEnum.YES.getCode());
loginConfig.setStrategyMaxNumber(7);
list.add(loginConfig);
loginConfigClient.addLoginConfigFeign(loginConfig);
}
LoginConfig loginConfig = list.get(0);
if (StrUtil.isNotBlank(loginConfig.getMulLogin())){
if (ObjectUtil.isNotEmpty(deviceType) && deviceType == 0 && !loginConfig.getMulLogin().contains("0")){
throw new MyException("当前Web端登录未授权请联系管理员");
}else if (ObjectUtil.isNotEmpty(deviceType) && deviceType == 1 && !loginConfig.getMulLogin().contains("1")){
throw new MyException("当前APP端登录未授权请联系管理员");
}
}else {
throw new MyException("当前Web端、app端登录未授权请联系管理员");
}
SaTokenConfig oldConfig = SaManager.getConfig();
if (loginConfig.getMutualExclusion() == YesOrNoEnum.NO.getCode()){
//不开启同端互斥
oldConfig.setIsConcurrent(true);
}else {
//开启同端互斥
oldConfig.setIsConcurrent(false);
}
// 注入到 SaManager 中
SaManager.setConfig(oldConfig);
return loginConfig;
}
}

View File

@ -0,0 +1,486 @@
package com.xjrsoft.auth.service.impl;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.http.HttpRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xjrsoft.auth.vo.ResponseVo;
import com.xjrsoft.auth.vo.TodoItem;
import com.xjrsoft.auth.service.SsoService;
import com.xjrsoft.common.core.constant.GlobalConstant;
import com.xjrsoft.common.core.result.Response;
import com.xjrsoft.common.core.uitls.BeanUtil;
import com.xjrsoft.common.core.uitls.DateUtil;
import com.xjrsoft.common.redis.service.RedisUtil;
import com.xjrsoft.organization.client.IDepartmentClient;
import com.xjrsoft.organization.client.IUserClient;
import com.xjrsoft.organization.entity.Department;
import com.xjrsoft.organization.entity.User;
import com.xjrsoft.organization.vo.UserDeptVo;
import com.xjrsoft.organization.vo.UserInfoVo;
import com.xjrsoft.organization.vo.UserRoleVo;
import com.xjrsoft.organization.vo.UserVo;
import com.xjrsoft.organization.vo.LoginVo;
import com.xjrsoft.workflow.client.IWorkflowExecuteClient;
import com.xjrsoft.workflow.dto.PendingTaskDto;
import com.xjrsoft.workflow.vo.PendingTaskVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
@Slf4j
public class SsoServiceImpl implements SsoService {
@Value("${xjrsoft.token_secret_key:}")
private String secretKey;
@Value("${xjrsoft.dist.pc_url:}")
private String pcUrl;
@Value("${xjrsoft.dist.mobile_url:}")
private String mobileUrl;
@Value("${xjrsoft.dist.config:}")
private String config;
@Value("${xjrsoft.dist.pc_config:}")
private String pc_config;
@Value("${xjrsoft.dist.type_name:}")
private String type_name;
@Value("${xjrsoft.dist.url:}")
private String url;
private String pcPath = "/flow/%s/%s/approveFlow?taskId=%s";
private String mobilePath = "/pages/workflow/approval?taskId=%s&processId=%s&type=todo";
@Autowired
private IUserClient userClient;
@Autowired
private IDepartmentClient departmentClient;
@Autowired
private IWorkflowExecuteClient iWorkflowExecuteClient;
@Autowired
private RedisUtil redisUtil;
@Value("${xjrsoft.dist.tenant_code_name:}")
private String tenantCodeName;
@Override
public String getLtpasToken(String username) {
String token = encodeToken(username);
return token;
}
@Override
public Response<LoginVo> getUser() {
SaSession tokenSession = StpUtil.getTokenSession();
User user = tokenSession.get("user", new User());
UserInfoVo userInfoVo = BeanUtil.copy(user, UserInfoVo.class);
List<Department> departments = userClient.queryDepartmentsOfUserIdFeign(user.getId());
Department companies = departmentClient.getDepartmentByIdFeign(user.getDepartmentId());
List<UserRoleVo> role = userClient.queryRolesOfUserFeign(user.getId());
if (departments != null) {
userInfoVo.setDepartments(BeanUtil.copyList(departments, UserDeptVo.class));
}
if (role != null) {
userInfoVo.setRoles(BeanUtil.copyList(role, UserRoleVo.class));
}
LoginVo loginVo = new LoginVo();
loginVo.setUserInfoVo(userInfoVo);
loginVo.setToken(StpUtil.getTokenValue());
return Response.ok(loginVo, "操作成功!");
}
@Override
public ResponseVo getTodoTask(String type, String ltpasToken) {
ResponseVo responseVo = new ResponseVo();
if(StringUtils.isEmpty(type) || !("pc".equals(type) || "app".equals(type))){
responseVo.setMsg("传参有误!");
return responseVo;
}
List<TodoItem> list = new ArrayList<>();
/* SaSession tokenSession = StpUtil.getTokenSession();
User user = tokenSession.get(GlobalConstant.LOGIN_USER_INFO_KEY, new User());
String userId = String.valueOf(user.getId());*/
String loginId = this.decodeToken(ltpasToken);
UserVo user = BeanUtil.copy(userClient.getUserByUserNameFeign(loginId), UserVo.class);
getToken(user);
log.info("开始查询待办列表");
List<TodoItem> tasks = this.findTask(type);
log.info("查询待办列表结束");
list.addAll(tasks);
//时间倒序
if(list.size() != 0){
list.sort(Comparator.comparing(TodoItem::getTs).reversed());
}
responseVo.setMsg("操作成功");
responseVo.setTotalCount(list.size());
responseVo.setItems(list);
return responseVo;
}
/**
* 查询待办任务信息
* @param type
* @return
*/
private List<TodoItem> findTask(String type){
List<TodoItem> list = new ArrayList<>();
//获取用户信息
PendingTaskDto dt = new PendingTaskDto();
//dt.setOriginator(userId);
long time= System.currentTimeMillis();
log.info("查询待办pengding方法开始执行{}",time);
List<PendingTaskVo> taskVos = iWorkflowExecuteClient.getPendingListFeign(dt);
log.info("查询待办pengding方法执行结束,总耗时:{}ms",(System.currentTimeMillis()-time));
if(taskVos == null || taskVos.isEmpty()){
return list;
}
for (PendingTaskVo taskVo : taskVos) {
TodoItem item = new TodoItem();
item.setTs(taskVo.getStartTime().toInstant(ZoneOffset.ofHours(8)).toEpochMilli());
item.setSubtitle("工作待办");
item.setTitle(taskVo.getSchemaName()+ "-" + taskVo.getStartUserName());
if("pc".equals(type)){
item.setType(type_name);
item.setUrl(url +"?config="+pc_config);// "url": "data/gdyd/trust/redirect2?config=gdyd-fcd-xfj",
JSONObject params = new JSONObject();
params.put("config",pc_config);
//item.setParams(params);
String dateTime = DateUtil.formatDateTime(taskVo.getStartTime());
String targetURL = String.format(pcPath,taskVo.getSchemaId(),taskVo.getProcessId(),taskVo.getTaskId());
item.setTargetURL(targetURL);
}else{
item.setType(type_name);
item.setSrc("api/"+url +"?config="+config);
item.setRoute("/route/std/iframe");
String targetURL = String.format(mobilePath,taskVo.getTaskId(),taskVo.getProcessId());
item.setTargetURL(targetURL);
}
list.add(item);
}
return list;
}
@Override
public String auth(String ltpasToken){
String loginid = this.decodeToken(ltpasToken);
if(StringUtils.isNotBlank(loginid)){
return loginid;
}
return null;
}
@Override
public void auth(String redirect, String ltpasToken, String method, HttpServletResponse response) {
redirect = StringEscapeUtils.unescapeHtml(redirect);
String loginid = this.decodeToken(ltpasToken);
if(StringUtils.isEmpty(loginid)){
toResponse(response,Response.notOk(-1, "ltpasToken已失效"),null);
return;
}
User userByUserNameFeign = userClient.getUserByUserNameFeign(loginid);
UserVo user = BeanUtil.copy(userByUserNameFeign, UserVo.class);
if (userByUserNameFeign == null) {
toResponse(response,Response.notOk(-1, "该账号不存在!"),null);
return;
} else if (!Integer.valueOf(1).equals(user.getEnabledMark())) {
toResponse(response,Response.notOk(-1, "该账号已被禁用!"),null);
return;
}
String token = getToken(user);
String tokenName = StpUtil.stpLogic.getConfig().getTokenName();
String tokenPrefix = StpUtil.stpLogic.getConfig().getTokenPrefix();
log.info("method->"+method);
if(StringUtils.isEmpty(method)){
//跳转到页面
try {
String url = makeUrl(redirect);
response.addHeader(HttpHeaders.SET_COOKIE, "Authorization".concat("=").concat(token)/*.concat("; SameSite=None ;Max-Age=3600; Path=/; Secure; HttpOnly")*/);
response.sendRedirect(url);
}catch (Exception e){
log.error("failed to redirect->"+redirect,e);
toResponse(response,Response.notOk(-1, "出现异常,页面鉴权跳转失败"),null);
}
}else {
//调用接口
String data = null;
if("get".equals(method)){
data = HttpRequest.get(redirect)
.header(tokenName,tokenPrefix + " " + token)
.execute().body();
}else if("post".equals(method)){
String[] paramsStrs = redirect.split("\\?params=");
redirect = paramsStrs[0];
if(paramsStrs.length > 1) {
JSONObject params = JSON.parseObject(paramsStrs[1]);
data = HttpRequest.post(redirect)
.header(tokenName,tokenPrefix + " " + ltpasToken)
.header("Cookie",tokenName + "=" + ltpasToken)
.form(params)
.execute().body();
}
}
toResponse(response,Response.ok(JSON.parseObject(data)),token);
}
}
/**
* 获取平台token已登录则从缓存获取否则进行登录操作
* @param user
* @param
* @return
*/
private String getToken(UserVo user){
SaSession tokenSession = null;
String token = null;
User userInfo = null;
try
{
tokenSession = StpUtil.getTokenSession();
}
catch (NotLoginException e)
{
//如果没有登陆,直接调用登陆接口
StpUtil.login(user.getId(), new SaLoginModel().setDevice("PC").setIsLastingCookie(false));
tokenSession = StpUtil.getTokenSession();
token = StpUtil.getTokenValue();
userInfo = tokenSession.get(GlobalConstant.LOGIN_USER_INFO_KEY, new User());
userInfo.setId(user.getId());
userInfo.setUserName(user.getUserName());
tokenSession.set(GlobalConstant.LOGIN_USER_INFO_KEY, userInfo);
return token;
}
token = (String)tokenSession.get(GlobalConstant.TOKEN_KEY);
if(StringUtils.isNotEmpty(token)){
StpUtil.setTokenValue(token);
}
userInfo = tokenSession.get(GlobalConstant.LOGIN_USER_INFO_KEY, new User());
String userId = String.valueOf(userInfo.getId());
if(StringUtils.isEmpty(token) || !user.getId().equals(userId)){
//此登录接口登录web端
//StpUtil.login(user.getId(), "pc");
StpUtil.login(user.getId(), new SaLoginModel().setDevice("PC").setIsLastingCookie(false));
tokenSession = StpUtil.getTokenSession();
//tokenSession.set("user", user);
token = StpUtil.getTokenValue();
userInfo.setId(user.getId());
userInfo.setUserName(user.getUserName());
tokenSession.set(GlobalConstant.LOGIN_USER_INFO_KEY, userInfo);
//todo 把获取用户信息的代码都注释了,要分析为什么之前要写这个,现在不写是可以获取待办的
//List<Department> departments = userService.queryDepartmentsOfUser(user.getId());
//XjrBaseCompany companies = companyService.getById(user.getCompanyId());
//List<UserRoleVo> role = userService.queryRolesOfUser(user.getId());
// 设置返回数据
// UserInfoVo userInfoVo = BeanUtil.copy(user, UserInfoVo.class);
/* if (companies != null) {
userInfoVo.setCompany(BeanUtil.copy(companies, CompanyPageListVo.class));
tokenSession.set("company", companyService.getById(user.getCompanyId()));
}*/
/* if (departments != null) {
userInfoVo.setDepartments(BeanUtil.copyList(departments, UserDeptVo.class));
tokenSession.set("dept", userService.queryDepartmentsOfUser(user.getId()));
}*/
/* if (role != null) {
userInfoVo.setRoles(BeanUtil.copyList(role, UserRoleVo.class));
tokenSession.set(GlobalConstant.LOGIN_USER_ROLE_CODE_KEY, role.stream().map(UserRoleVo::getCode).collect(Collectors.toList()));
tokenSession.set("role", userService.queryRolesOfUser(user.getId()));
}*/
//如果开启了多租户 需要把租户信息也要存储一份
/* if(properties.getEnabledTenant()) {
tokenSession.set("tenant",tenant);
userInfoVo.setTenantId(tenant.getId());
userInfoVo.setTenantName(tenant.getName());
}*/
// tokenSession.set("userInfoVo",userInfoVo);
}
return token;
}
private void toResponse(HttpServletResponse response, Response result, String token){
PrintWriter writer = null;
try {
response.setContentType("application/json;charset=UTF-8");
response.setCharacterEncoding("utf-8");
if (StringUtils.isNotEmpty(token)) {
response.addHeader(HttpHeaders.SET_COOKIE, "Authorization".concat("=").concat(token).concat("; SameSite=None ;Max-Age=3600; Path=/; Secure; HttpOnly"));
}
writer = response.getWriter();
writer.write(JSON.toJSONString(result));
}catch (Exception e){
e.printStackTrace();
}finally {
if (writer != null){
writer.close();
}
}
}
private String decodeToken(String ltpasToken) {
if (StringUtils.isEmpty(ltpasToken)) {
return null;
}
Long now = System.currentTimeMillis() / 1000;
String ltpasTokenDecode = new String(Base64.getDecoder().decode(ltpasToken), StandardCharsets.UTF_8);
String[] ltpasTokenInfo = ltpasTokenDecode.split("%");
String eIN = ltpasTokenInfo[0];
String sign = ltpasTokenInfo[3];
ltpasTokenInfo[3] = secretKey;
String ltpasTokenPlain = StringUtils.join(ltpasTokenInfo, "%");
if(!sign.equals(DigestUtils.sha1Hex(ltpasTokenPlain))) {
return null;
}
Long begin = Long.parseLong(ltpasTokenInfo[1]);
Long end = Long.parseLong(ltpasTokenInfo[2]);
if(now >= begin && now <= end) {
return eIN;
}
return null;
}
/**
* 通过登录账号username,等到加密后的互信token
* @param username
* @return
*/
public String encodeToken(String username) {
//过期时间
Long cycle = 24 * 60L * 60L;
Long begin = System.currentTimeMillis() /1000;
Long end = begin + cycle;
String[] ltpasTokenPlainInfo = new String[4];
ltpasTokenPlainInfo[0] = username;
ltpasTokenPlainInfo[1] = begin.toString();
ltpasTokenPlainInfo[2] = end.toString();
ltpasTokenPlainInfo[3] = secretKey;
String sign = DigestUtils.sha1Hex(StringUtils.join(ltpasTokenPlainInfo, "%"));
ltpasTokenPlainInfo[3] = sign;
String token = Base64.getEncoder().encodeToString(StringUtils.join(ltpasTokenPlainInfo, "%").getBytes(StandardCharsets.UTF_8));
return token;
}
/**
* 对重定向的url进行解析中文乱码处理
* @param url
* @return
*/
private String makeUrl(String url){
String[] urlStrs = url.split("\\?");
if(urlStrs.length == 1){
return url;
}
String domain = url.split("\\?")[0];
StringBuffer urlBuff = new StringBuffer();
urlBuff.append(domain).append("?");
String paramStrs = url.substring(url.indexOf("?") + 1);
String[] params = paramStrs.split("&");
if(params.length == 1){
makeUrlParams(0,params,urlBuff);
return urlBuff.toString();
}
makeUrlParams(0,params,urlBuff);
urlBuff.append("&");
for (int i = 1; i < params.length; i++) {
makeUrlParams(i,params,urlBuff);
urlBuff.append("&");
}
return urlBuff.substring(0, urlBuff.length() - 1);
}
/**
* 解析url参数将参数值进行编码转换
* @param index
* @param params
* @param urlBuff
*/
private void makeUrlParams(Integer index,String[] params,StringBuffer urlBuff){
String[] kv = params[index].split("=");
if(kv.length == 1 || StringUtils.isEmpty(kv[1])){
urlBuff.append(kv[0]).append("=").append("");
}else {
try {
urlBuff.append(kv[0]).append("=").append(URLEncoder.encode(kv[1], "utf-8"));
}catch (Exception e){
urlBuff.append(kv[0]).append("=").append("");
}
}
}
/**
* 获取请求地址url中获取某个参数值
* @param url
* @param name
* @return
*/
private String getParamByUrl(String url, String name) {
url += "&";
String pattern = "(\\?|&){1}#{0,1}" + name + "=[a-zA-Z0-9]*(&{1})";
Pattern r = Pattern.compile(pattern);
Matcher matcher = r.matcher(url);
if (matcher.find()) {
return matcher.group(0).split("=")[1].replace("&", "");
} else {
return "";
}
}
}

View File

@ -0,0 +1,13 @@
package com.xjrsoft.auth.vo;
import lombok.Data;
import java.util.List;
@Data
public class ResponseVo {
private Integer retcode = 1;
private Integer totalCount = 0;
private String msg;
private List<TodoItem> items;
}

View File

@ -0,0 +1,20 @@
package com.xjrsoft.auth.vo;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
@Data
public class TodoItem {
private boolean unread = true;
private boolean useBlank = true;
private boolean useHref = true;
private String subtitle;
private String title;
private String type;
private JSONObject params;
private String targetURL;//这个返回值根据app和pc判断会有不同的返回值把这两个值写到配置文件里到时候方便修改。
private String url;
private String src;
private String route;
private long ts;
}

View File

@ -0,0 +1,3 @@
spring:
profiles:
active: public

View File

@ -0,0 +1,60 @@
server:
port: 1024
spring:
application:
name: auth-service
main:
allow-bean-definition-overriding: true
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.0.252.7:3306/fcd2-msat-init?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
username: learun4dev
password: ABcd1234@
cloud:
nacos: #nacos监控
discovery:
server-addr: 10.0.252.1:8848
namespace: ITC
group: DNE
username: nacos
password: ABcd1234@
config:
server-addr: 10.0.252.1:8848 # nacos 配置中心地址
namespace: ITC
group: DNE
username: nacos
password: ABcd1234@
file-extension: yml # 指定格式 xjrsoft-auth-service-dev.yml
extension-configs:
- data-id: global-config.yml
refresh: true
group: DNE
- data-id: redis-config.yml
refresh: true
group: DNE
- data-id: sa-token-client-config.yml
refresh: true
group: DNE
- data-id: datasource-config.yml
refresh: true
group: DNE
sentinel:
transport:
dashboard: localhost:8080 #sentinel dashboard 地址
port: 8719 #默认端口, 如果 被占用,会一直+1 直到未被占用为止
springdoc:
swagger-ui:
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
show-extensions: true
api-docs:
path: '/sso/v3/api-docs'
group-configs:
- group: 'default'
paths-to-match: '/sso/**'
packages-to-scan: com.xjrsoft.auth
default-flat-param-object: false