Compare commits

..

10 Commits

Author SHA1 Message Date
xiang
77769558ac feat:token 验签 2025-09-25 23:30:47 +08:00
xiang
a8d044dcb9 feat:分页查询 2025-09-23 22:51:15 +08:00
xiang
33c7e5fc5a feat:分页查询 2025-09-23 22:41:27 +08:00
xiang
8d31023505 feat:用户新增租户信息 2025-09-23 22:09:39 +08:00
xiang
9dbecac8ba fix:测试环境阿里云密钥 2025-09-06 23:01:04 +08:00
xiang
d887faea42 fix:手机验证码 2025-09-06 22:57:47 +08:00
xiang
813eec5e7f fix:校验参数 2025-09-06 09:16:15 +08:00
xiang
7e011672a1 feat:修改server配置 2025-09-05 23:39:42 +08:00
xiang
ff79db96f7 feat:配置文件 2025-09-05 22:27:49 +08:00
xiang
5a2e4299b4 feat:配置文件 2025-09-04 23:25:41 +08:00
23 changed files with 417 additions and 45 deletions

14
pom.xml
View File

@@ -4,7 +4,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent> <parent>
<groupId>com.xiang</groupId> <groupId>com.xiang</groupId>
<artifactId>xservice-basic</artifactId> <artifactId>xservice-parent-starter</artifactId>
<version>2.0</version> <version>2.0</version>
</parent> </parent>
<packaging>pom</packaging> <packaging>pom</packaging>
@@ -76,6 +76,18 @@
<artifactId>mapstruct</artifactId> <artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version> <version>1.5.5.Final</version>
</dependency> </dependency>
<dependency>
<groupId>com.xiang</groupId>
<artifactId>xservice-message-starter</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.7</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -4,11 +4,14 @@ import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class CaptchaImageRequest { public class CaptchaImageRequest {
@NotBlank(message = "用户名不能为空")
private String username; private String username;
private Integer number; private Integer number;

View File

@@ -55,4 +55,9 @@ public class UserResp {
* 修改时间 * 修改时间
*/ */
private LocalDateTime updateTime; private LocalDateTime updateTime;
/**
* 租户id
*/
private Long tenantId;
} }

View File

@@ -2,9 +2,13 @@ package com.xiang;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@SpringBootApplication @SpringBootApplication
@ConfigurationPropertiesScan(basePackages = {
"com.xiang.xservice.basic.xservice.aliyun.config"
})
@EnableMethodSecurity(prePostEnabled = true) @EnableMethodSecurity(prePostEnabled = true)
public class AuthApplication { public class AuthApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@@ -1,6 +1,7 @@
package com.xiang.xservice.auth.server.controller; package com.xiang.xservice.auth.server.controller;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageInfo;
import com.xiang.xservice.auth.api.dto.req.user.UserAddRequest; import com.xiang.xservice.auth.api.dto.req.user.UserAddRequest;
import com.xiang.xservice.auth.api.dto.req.user.UserDeptUpdateRequest; import com.xiang.xservice.auth.api.dto.req.user.UserDeptUpdateRequest;
import com.xiang.xservice.auth.api.dto.req.user.UserQueryRequest; import com.xiang.xservice.auth.api.dto.req.user.UserQueryRequest;
@@ -32,7 +33,7 @@ public class UserController {
private final XUserService userService; private final XUserService userService;
@PostMapping("/private/user/list") @PostMapping("/private/user/list")
public Result<UserResp> getUserList(@RequestBody @Valid @NotNull(message = "请求参数不能为空") UserQueryRequest request) { public Result<PageInfo<UserResp>> getUserList(@RequestBody @Valid @NotNull(message = "请求参数不能为空") UserQueryRequest request) {
return Result.success(userService.getUserList(request)); return Result.success(userService.getUserList(request));
} }

View File

@@ -6,6 +6,7 @@ import com.xiang.xservice.auth.service.enums.CaptchaTypeEnum;
import com.xiang.xservice.auth.service.service.ValidCodeGenerateFactory; import com.xiang.xservice.auth.service.service.ValidCodeGenerateFactory;
import com.xiang.xservice.basic.common.resp.Result; import com.xiang.xservice.basic.common.resp.Result;
import com.xiang.xservice.basic.utils.RandomCodeUtils; import com.xiang.xservice.basic.utils.RandomCodeUtils;
import com.xiang.xservice.basic.xservice.aliyun.smscode.ISmsCodeService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -15,6 +16,7 @@ import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.util.Objects;
@Slf4j @Slf4j
@RestController @RestController
@@ -22,12 +24,15 @@ import javax.validation.constraints.NotNull;
public class ValidationCodeController { public class ValidationCodeController {
private final ValidCodeGenerateFactory validCodeGenerateFactory; private final ValidCodeGenerateFactory validCodeGenerateFactory;
private final ISmsCodeService smsCodeService;
@PostMapping("/public/captchaImage") @PostMapping("/public/captchaImage")
public Result<CaptchaDTO> getCaptchaImage(@RequestBody @NotNull(message = "请求参数不能为空") @Valid CaptchaImageRequest request) { public Result<CaptchaDTO> getCaptchaImage(@RequestBody @NotNull(message = "请求参数不能为空") @Valid CaptchaImageRequest request) {
if (Objects.isNull(request.getNumber())) {
request.setNumber(4);
}
try { try {
String randomCode = RandomCodeUtils.getNumberRandomCode(1); String randomCode = RandomCodeUtils.getNumberRandomCode(1);
// String randomCode = "1";
CaptchaDTO captchaImage; CaptchaDTO captchaImage;
if (StringUtils.isBlank(randomCode)) { if (StringUtils.isBlank(randomCode)) {
captchaImage = validCodeGenerateFactory.get(CaptchaTypeEnum.NORMAL_CAPTCHA_IMAGE.getType()).getCaptchaImage(request); captchaImage = validCodeGenerateFactory.get(CaptchaTypeEnum.NORMAL_CAPTCHA_IMAGE.getType()).getCaptchaImage(request);
@@ -49,8 +54,14 @@ public class ValidationCodeController {
@PostMapping("/public/getSmsCode") @PostMapping("/public/getSmsCode")
public Result<Void> getSmsCode(@RequestBody @NotNull(message = "请求参数不能为空") @Valid CaptchaImageRequest request) { public Result<Void> getSmsCode(@RequestBody @NotNull(message = "请求参数不能为空") @Valid CaptchaImageRequest request) {
if (Objects.isNull(request.getNumber())) {
request.setNumber(6);
}
try { try {
validCodeGenerateFactory.get(CaptchaTypeEnum.NUMBER_CAPTCHA_CODE.getType()).getCaptchaImage(request); CaptchaDTO captchaImage = validCodeGenerateFactory.get(CaptchaTypeEnum.NUMBER_CAPTCHA_CODE.getType()).getCaptchaImage(request);
String code = captchaImage.getCaptchaImageCode();
smsCodeService.asyncSendPhoneValidCode4DaXiangNet(request.getUsername(), code);
return Result.success(); return Result.success();
} catch (Exception e) { } catch (Exception e) {
log.error("获取验证码失败", e); log.error("获取验证码失败", e);

View File

@@ -39,4 +39,9 @@ user:
# https://oauth.pstmn.io/v1/callback # https://oauth.pstmn.io/v1/callback
# 移动端 APP 自定义 Scheme # 移动端 APP 自定义 Scheme
# myapp://callback # myapp://callback
redirectUrl: http://localhost:8080/login/oauth2/code/oauth-client-init redirectUrl: http://localhost:8080/login/oauth2/code/oauth-client-init
aliyun:
auth:
accessKeyId: LTAI5tDMjaVF8Bbqcpp4dmvP
accessKeySecret: nkmnaNjWQy5984C5kjyS0oDmdMKGQd

View File

@@ -39,4 +39,9 @@ user:
# https://oauth.pstmn.io/v1/callback # https://oauth.pstmn.io/v1/callback
# 移动端 APP 自定义 Scheme # 移动端 APP 自定义 Scheme
# myapp://callback # myapp://callback
redirectUrl: http://localhost:8080/login/oauth2/code/oauth-client-init redirectUrl: http://localhost:8080/login/oauth2/code/oauth-client-init
aliyun:
auth:
accessKeyId: LTAI5tDMjaVF8Bbqcpp4dmvP
accessKeySecret: nkmnaNjWQy5984C5kjyS0oDmdMKGQd

View File

@@ -8,9 +8,17 @@ spring:
mvc: mvc:
pathmatch: pathmatch:
matching-strategy: ant_path_matcher matching-strategy: ant_path_matcher
main:
allow-bean-definition-overriding: true
headless: true
mybatis: mybatis:
mapper-locations: mapper-locations:
- classpath*:mapper/*/*.xml - classpath*:mapper/*/*.xml
configuration: configuration:
map-underscore-to-camel-case: true map-underscore-to-camel-case: true
pagehelper:
helperDialect: mysql
reasonable: true
support-methods-arguments: true
params: count=countSql

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQD7JdqQe8W8byYT
KuPK7STLYO5TTe5uWhHj1SLq7Xoj1y0agO+x1X3/dUA1X/k5Dd2rmkmIU2VoB/9c
aoE5naXUhVfE9wDsl5AKdWRCn1PKvuofd/0kuGFS44tNQcLGtDD7e8CQQJZJPZVS
Z8F2nlHjYSTUPVvi77hKG21qoZLEa0F+TYfqgOohabQtxeBAnyRW50/g281Bqd3w
ntfM0aKfW9fsFhJnk+AlXWaqxQKkhtzMmKVeqLNfJnXvhgUA6QbnzHPtPxF+fLEx
BNnBEPF8CsQFVdt1TtsQlfCzVMTMibZU2CcwtqHUP1maEcH5zV5whPfgkahejiNm
L08Jw74VAgMBAAECggEACJHb0pGHh4IkF/XmM6Qs6xMFYmqmvRkf0JjoTYDl9+JW
Y9RMYKfqy+Yius+GSNRjPWS7p38MHkiysGL/F7uYyCwvhwU3x+kugM2+/+gWqyaT
WnwYgZ4YXIRu2ieFr4xq1symnzO14nDny4uqB9PEZFd7wS4I0ZShe5yLM6Yai88V
0Jm6Hi9RcC5efiE4tWismBKaP13WXamAVySROW30lEaMgyI66DNYgs+RHiZgAFP7
u7raUD07xrk6eV6YnG/9EvS/oqV+IPEacY+bP3ZUqvPMI50tLTEVN1yJXrr4T3kS
W1TiApaL87rdDCYem7rtIury+JcSadaI6lwP5OhmLwKBgQD/4mSrFi30IzD6LFam
N7CYBxSleWgha2fHuycDGNGcXDCn3y4JuqTMRpV1c9F5emPPv1E3ELj4plz/NnVr
67SkCs3nqoSRjyXJKhN/kJqM/NB+Ic1UgXeI+wGMkfkUHrQ6T6SghYVxWW+hQKm8
IeV7aVJiM01/Ze938cnJuWd17wKBgQD7QumZzRTMkmnmSFCpOhs2Y3B8JYrJsJXY
PeYkxea/7brDyuIdWKt0kl9EvpsrIzTe0t4LYV3Vmmfh15PNZp4PEr30NEBxeVOO
HoglNfyJgP/nvhOGYesNhqPlK96/ajEvu7FpFHwDje5RKRWxCK5qhZneDy0ppjWb
6seshN1wOwKBgQDqNPxxP/bFu6Qrh4Oz1cs0C18RakMuO5Gc1acKhZ/tntAGBxer
XgNS2dQY0e5MYwKSdwlN/mdfZ149Vko5gl8vupfmUEPQuxYZvwJjwyZCn2/x0tyO
WYXggeZUFJPHn6bUrGsBZdTS/8pV7Mqu4NOblrYKHez0C4gY390TXzjcTwKBgQDR
mD6fYrjf8Z7PTzGiCOucUhUKKpL8rgZBbVknAcL8BYZPP1Whn07fHh7EjK+Jq4O2
AHbjTWRmA7h2Z0tPAzQEZOD57gB35/pwSj3NtJwl4+sU2LUW22WlUdQ0HoVgbWf8
ZniWrFTK7kGHiFsk45YDG9F/sG8/F/wORSotWmQR8wKBgQDqrlyvMCwiJHW7vOPs
ih+utzIvtZ8D4fxzEFluTUqubAAl3N+81NuRJuEFIJLjIAeTNOHj1IdlPj5oe0aa
IYOzoB2+xJxnLPbvI1tTat/pqgXxPY24/9c9rBoTCsJboTPb0fMh1nHxTZvny4tB
jP7d5EBvIMWnCEuTo4y39ZFsMA==
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+yXakHvFvG8mEyrjyu0k
y2DuU03ubloR49Ui6u16I9ctGoDvsdV9/3VANV/5OQ3dq5pJiFNlaAf/XGqBOZ2l
1IVXxPcA7JeQCnVkQp9Tyr7qH3f9JLhhUuOLTUHCxrQw+3vAkECWST2VUmfBdp5R
42Ek1D1b4u+4ShttaqGSxGtBfk2H6oDqIWm0LcXgQJ8kVudP4NvNQand8J7XzNGi
n1vX7BYSZ5PgJV1mqsUCpIbczJilXqizXyZ174YFAOkG58xz7T8RfnyxMQTZwRDx
fArEBVXbdU7bEJXws1TEzIm2VNgnMLah1D9ZmhHB+c1ecIT34JGoXo4jZi9PCcO+
FQIDAQAB
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 应用名称:和统一配置中的项目代码保持一致(小写) -->
<property name="APP_NAME" value="xservice-auth-center"/>
<contextName>${APP_NAME}</contextName>
<!--日志文件保留天数 -->
<property name="LOG_MAX_HISTORY" value="30"/>
<!--应用日志文件保存路径 -->
<!--在没有定义${LOG_HOME}系统变量的时候,可以设置此本地变量。提交测试、上线时,要将其注释掉,使用系统变量。 -->
<property name="LOG_HOME" value="logs/${APP_NAME}"/>
<!--<property name="LOG_HOME" msg="/home/logs/${APP_NAME}" />-->
<!--控制台输出appender-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<!--设置输出格式-->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%thread表示线程名%-5level级别从左显示5个字符宽度%msg日志消息%n是换行符-->
<pattern>%boldGreen(%contextName): %boldCyan(%d{yyyy-MM-dd HH:mm:ss:SSS}) %highlight([%c]) %boldMagenta([%t]) %boldCyan([%L]) %highlight([traceId:%X{traceId:-},spanId:%X{spanId:-},localIp:%X{localIp:-}]) %boldGreen([%p]) - %msg%n
</pattern>
<!--设置编码-->
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照每天生成日志文件:主项目日志 -->
<appender name="APP_DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/debug-%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>${LOG_MAX_HISTORY}</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%c类名%t表示线程名%L行 %p日志级别 %msg日志消息%n是换行符 -->
<pattern>%contextName: %d{yyyy-MM-dd HH:mm:ss.SSS} [%c][%t][%L][%p] [traceId:%X{traceId:-},spanId:%X{spanId:-},localIp:%X{localIp:-}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 此日志文件只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>debug</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 按照每天生成日志文件:主项目日志 -->
<appender name="APP_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/info-%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>${LOG_MAX_HISTORY}</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%c类名%t表示线程名%L行 %p日志级别 %msg日志消息%n是换行符 -->
<pattern>%contextName: %d{yyyy-MM-dd HH:mm:ss.SSS} [%c][%t][%L][%p] [traceId:%X{traceId:-},spanId:%X{spanId:-},localIp:%X{localIp:-}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 此日志文件只记录info级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 按照每天生成日志文件:主项目日志 -->
<appender name="APP_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名 -->
<FileNamePattern>${LOG_HOME}/error-%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数 -->
<MaxHistory>${LOG_MAX_HISTORY}</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期%c类名%t表示线程名%L行 %p日志级别 %msg日志消息%n是换行符 -->
<pattern>%contextName: %d{yyyy-MM-dd HH:mm:ss.SSS} [%c][%t][%L][%p] [traceId:%X{traceId:-},spanId:%X{spanId:-},localIp:%X{localIp:-}] - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 此日志文件只记录error级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--日志输出到文件-->
<root level="info">
<appender-ref ref="APP_DEBUG"/>
<appender-ref ref="APP_INFO"/>
<appender-ref ref="APP_ERROR"/>
<appender-ref ref="console"/>
</root>
<!-- mybatis 日志级别 -->
<logger name="com.xiang" level="debug"/>
</configuration>

View File

@@ -1,12 +1,5 @@
package com.xiang.xservice.auth.service.config; package com.xiang.xservice.auth.service.config;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.xiang.xservice.basic.exception.CustomAccessDeniedHandler;
import com.xiang.xservice.basic.exception.CustomAuthenticationEntryPoint;
import com.xiang.xservice.basic.utils.JwkUtils;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -24,9 +17,6 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
@@ -140,18 +130,6 @@ public class AuthorizationServerConfig {
return repository; return repository;
} }
@Bean
public JWKSource<SecurityContext> jwkSource() {
RSAKey rsaKey = JwkUtils.generateRsa();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}
@Bean
public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
return new NimbusJwtEncoder(jwkSource);
}
@Bean @Bean
public AuthorizationServerSettings authorizationServerSettings() { public AuthorizationServerSettings authorizationServerSettings() {
return AuthorizationServerSettings.builder() return AuthorizationServerSettings.builder()
@@ -159,9 +137,5 @@ public class AuthorizationServerConfig {
.build(); .build();
} }
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
} }

View File

@@ -0,0 +1,32 @@
package com.xiang.xservice.auth.service.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xiang.xservice.basic.common.resp.Result;
import com.xiang.xservice.basic.exception.code.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 用户403鉴权失败异常捕获
*/
@Slf4j
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
log.error("======用户鉴权失败======");
Result<Void> result = Result.error(ErrorCode.USER_DENIED_ACCESS);
response.getWriter().write(new ObjectMapper().writeValueAsString(result));
}
}

View File

@@ -0,0 +1,30 @@
package com.xiang.xservice.auth.service.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xiang.xservice.basic.common.resp.Result;
import com.xiang.xservice.basic.exception.code.ErrorCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 用户未鉴权异常处理器
*/
@Slf4j
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
log.error("========用户未进行验证鉴权==========");
Result<Void> result = Result.error(ErrorCode.NOT_AUTHORIZATION);
response.getWriter().write(new ObjectMapper().writeValueAsString(result));
}
}

View File

@@ -0,0 +1,71 @@
package com.xiang.xservice.auth.service.config;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
@Configuration
public class JwtConfig {
private RSAPrivateKey loadPrivateKey(String classpath) throws Exception {
InputStream is = new ClassPathResource(classpath).getInputStream();
String key = new String(is.readAllBytes(), StandardCharsets.UTF_8)
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(key));
return (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(spec);
}
private RSAPublicKey loadPublicKey(String classpath) throws Exception {
InputStream is = new ClassPathResource(classpath).getInputStream();
String key = new String(is.readAllBytes(), StandardCharsets.UTF_8)
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
.replaceAll("\\s+", "");
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.getDecoder().decode(key));
return (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(spec);
}
@Bean
public JWKSource<SecurityContext> jwkSource() throws Exception {
// 使用RSA对称加密进行加密
RSAPublicKey publicKey = loadPublicKey("keys/rsa-public.pem");
RSAPrivateKey privateKey = loadPrivateKey("keys/rsa-private.pem");
RSAKey rsaKey = new RSAKey.Builder(publicKey)
.privateKey(privateKey)
.keyID("xservice")
.build();
JWKSet jwkSet = new JWKSet(rsaKey);
return (jwkSelector, ctx) -> jwkSelector.select(jwkSet);
}
@Bean
public JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
return new NimbusJwtEncoder(jwkSource);
}
@Bean
public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
}
}

View File

@@ -2,6 +2,7 @@ package com.xiang.xservice.auth.service.constants;
public class RedisConstant { public class RedisConstant {
public static final String LOGIN_TOKEN = "login:token:";
public static final String XS_PERMISSION_ROLE = "auth:permission:role"; public static final String XS_PERMISSION_ROLE = "auth:permission:role";
public static final String XS_SMS_CODE_KEY = "auth:sms:code:key:"; public static final String XS_SMS_CODE_KEY = "auth:sms:code:key:";
public static final String XS_CAPTCHA_CODE_KEY = "auth:captcha:code:key:"; public static final String XS_CAPTCHA_CODE_KEY = "auth:captcha:code:key:";

View File

@@ -1,5 +1,6 @@
package com.xiang.xservice.auth.service.convert; package com.xiang.xservice.auth.service.convert;
import com.github.pagehelper.PageInfo;
import com.xiang.xservice.auth.api.dto.req.user.UserAddRequest; import com.xiang.xservice.auth.api.dto.req.user.UserAddRequest;
import com.xiang.xservice.auth.api.dto.req.user.UserQueryRequest; import com.xiang.xservice.auth.api.dto.req.user.UserQueryRequest;
import com.xiang.xservice.auth.api.dto.req.user.UserUpdateRequest; import com.xiang.xservice.auth.api.dto.req.user.UserUpdateRequest;
@@ -8,8 +9,6 @@ import com.xiang.xservice.auth.service.entity.XUser;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper(componentModel = "spring") @Mapper(componentModel = "spring")
public interface XUserConvert { public interface XUserConvert {
XUserConvert INSTANCE = Mappers.getMapper(XUserConvert.class); XUserConvert INSTANCE = Mappers.getMapper(XUserConvert.class);
@@ -18,7 +17,7 @@ public interface XUserConvert {
XUser toDO (UserAddRequest request); XUser toDO (UserAddRequest request);
XUser toDO (UserUpdateRequest request); XUser toDO (UserUpdateRequest request);
List<UserResp> toRespList(List<XUser> list); PageInfo<UserResp> toPage(PageInfo<XUser> list);
UserResp toResp(XUser user); UserResp toResp(XUser user);
} }

View File

@@ -95,4 +95,13 @@ public class XUser implements Serializable {
* 修改时间 * 修改时间
*/ */
private LocalDateTime updateTime; private LocalDateTime updateTime;
/**
* token
*/
private String token;
/**
* 刷新token
*/
private String refreshToken;
} }

View File

@@ -1,5 +1,6 @@
package com.xiang.xservice.auth.service.service; package com.xiang.xservice.auth.service.service;
import com.github.pagehelper.PageInfo;
import com.xiang.xservice.auth.api.dto.req.LoginRequest; import com.xiang.xservice.auth.api.dto.req.LoginRequest;
import com.xiang.xservice.auth.api.dto.req.RegisterRequest; import com.xiang.xservice.auth.api.dto.req.RegisterRequest;
import com.xiang.xservice.auth.api.dto.req.user.UserAddRequest; import com.xiang.xservice.auth.api.dto.req.user.UserAddRequest;
@@ -22,7 +23,7 @@ public interface XUserService {
RegisterResp userRegister(RegisterRequest request); RegisterResp userRegister(RegisterRequest request);
List<UserResp> getUserList(UserQueryRequest request); PageInfo<UserResp> getUserList(UserQueryRequest request);
UserResp getUserInfo(Long id); UserResp getUserInfo(Long id);

View File

@@ -1,5 +1,7 @@
package com.xiang.xservice.auth.service.service.impl; package com.xiang.xservice.auth.service.service.impl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.xiang.xservice.auth.api.code.Code01UserErrorCode; import com.xiang.xservice.auth.api.code.Code01UserErrorCode;
import com.xiang.xservice.auth.api.code.Code02RoleErrorCode; import com.xiang.xservice.auth.api.code.Code02RoleErrorCode;
@@ -15,6 +17,7 @@ import com.xiang.xservice.auth.api.dto.resp.LoginResp;
import com.xiang.xservice.auth.api.dto.resp.RegisterResp; import com.xiang.xservice.auth.api.dto.resp.RegisterResp;
import com.xiang.xservice.auth.api.dto.resp.UserDTO; import com.xiang.xservice.auth.api.dto.resp.UserDTO;
import com.xiang.xservice.auth.api.dto.resp.UserResp; import com.xiang.xservice.auth.api.dto.resp.UserResp;
import com.xiang.xservice.auth.service.constants.RedisConstant;
import com.xiang.xservice.auth.service.convert.XDeptConvert; import com.xiang.xservice.auth.service.convert.XDeptConvert;
import com.xiang.xservice.auth.service.convert.XPermissionConvert; import com.xiang.xservice.auth.service.convert.XPermissionConvert;
import com.xiang.xservice.auth.service.convert.XRoleConvert; import com.xiang.xservice.auth.service.convert.XRoleConvert;
@@ -37,15 +40,20 @@ import com.xiang.xservice.auth.service.repository.mapper.XUserRoleMapper;
import com.xiang.xservice.auth.service.service.XUserService; import com.xiang.xservice.auth.service.service.XUserService;
import com.xiang.xservice.basic.enums.DelStatusEnum; import com.xiang.xservice.basic.enums.DelStatusEnum;
import com.xiang.xservice.basic.exception.BusinessException; import com.xiang.xservice.basic.exception.BusinessException;
import com.xiang.xservice.basic.utils.PrimaryKeyUtils;
import com.xiang.xservice.cache.service.IRedisService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet; import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder; import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters; import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -56,6 +64,7 @@ import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@@ -77,6 +86,8 @@ public class XUserServiceImpl implements XUserService {
private final XRolePermissionMapper rolePermissionMapper; private final XRolePermissionMapper rolePermissionMapper;
private final XPermissionMapper permissionMapper; private final XPermissionMapper permissionMapper;
private final XPermissionConvert permissionConvert; private final XPermissionConvert permissionConvert;
private final IRedisService redisService;
private final JwtDecoder jwtDecoder;
@Override @Override
public LoginResp login(LoginRequest request) { public LoginResp login(LoginRequest request) {
@@ -103,6 +114,21 @@ public class XUserServiceImpl implements XUserService {
} }
roleCodes.addAll(roles.stream().map(XRole::getCode).toList()); roleCodes.addAll(roles.stream().map(XRole::getCode).toList());
} }
if (StringUtils.isNotBlank(user.getToken())) {
try {
Jwt jwt = jwtDecoder.decode(user.getToken());
if (Objects.nonNull(jwt.getExpiresAt())) {
if (jwt.getExpiresAt().isAfter(Instant.now())) {
LoginResp loginResp = new LoginResp();
loginResp.setToken(user.getToken());
loginResp.setUsername(request.getUsername());
return loginResp;
}
}
} catch (Exception e) {
log.info("jwt解析token失败", e);
}
}
// 生成 token // 生成 token
Instant now = Instant.now(); Instant now = Instant.now();
@@ -118,11 +144,26 @@ public class XUserServiceImpl implements XUserService {
.claim("authorities", roleCodes) .claim("authorities", roleCodes)
.build(); .build();
JwtClaimsSet refreshClaims = JwtClaimsSet.builder()
// 对应 ProviderSettings.issuer
.issuedAt(now)
.expiresAt(now.plus(24, ChronoUnit.HOURS))
// 自定义 scope
.claim("tenantId", user.getTenantId())
.build();
// 2. 编码生成 token // 2. 编码生成 token
String token = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue(); String token = jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
String refreshToken = jwtEncoder.encode(JwtEncoderParameters.from(refreshClaims)).getTokenValue();
LoginResp loginResp = new LoginResp(); LoginResp loginResp = new LoginResp();
loginResp.setToken(token); loginResp.setToken(token);
loginResp.setUsername(request.getUsername()); loginResp.setUsername(request.getUsername());
// 3. redis缓存token
redisService.set(RedisConstant.LOGIN_TOKEN + request.getUsername(), token, 3, TimeUnit.HOURS);
// 4. db 存储token
user.setToken(token);
user.setRefreshToken(refreshToken);
userMapper.update(user);
return loginResp; return loginResp;
} }
@@ -179,12 +220,14 @@ public class XUserServiceImpl implements XUserService {
} }
@Override @Override
public List<UserResp> getUserList(UserQueryRequest request) { public PageInfo<UserResp> getUserList(UserQueryRequest request) {
PageHelper.startPage(request.getCurrent(), request.getPageSize());
List<XUser> userList = userMapper.getUserList(userConvert.toDO(request)); List<XUser> userList = userMapper.getUserList(userConvert.toDO(request));
if (CollectionUtils.isEmpty(userList)) { if (CollectionUtils.isEmpty(userList)) {
return Lists.newArrayList(); return new PageInfo<>();
} }
return userConvert.toRespList(userList); PageInfo<XUser> pageInfo = new PageInfo<>(userList);
return userConvert.toPage(pageInfo);
} }
@Override @Override
@@ -196,6 +239,7 @@ public class XUserServiceImpl implements XUserService {
@Override @Override
public Boolean addUser(UserAddRequest request) { public Boolean addUser(UserAddRequest request) {
XUser user = userConvert.toDO(request); XUser user = userConvert.toDO(request);
user.setTenantId(PrimaryKeyUtils.snowflakeId());
user.setCreateBy(request.getOperator()); user.setCreateBy(request.getOperator());
user.setCreateTime(request.getDateTime()); user.setCreateTime(request.getDateTime());
user.setUpdateBy(request.getOperator()); user.setUpdateBy(request.getOperator());

View File

@@ -19,7 +19,7 @@ public class NumberCaptchaCode implements ICaptchaService {
@Override @Override
public CaptchaDTO getCaptchaImage(CaptchaImageRequest request) { public CaptchaDTO getCaptchaImage(CaptchaImageRequest request) {
String randomCode = RandomCodeUtils.getMixtureRandomCode(request.getNumber()); String randomCode = RandomCodeUtils.getNumberRandomCode(request.getNumber());
redisService.set(RedisConstant.XS_SMS_CODE_KEY + request.getUsername(), randomCode, 5, TimeUnit.MINUTES); redisService.set(RedisConstant.XS_SMS_CODE_KEY + request.getUsername(), randomCode, 5, TimeUnit.MINUTES);
CaptchaDTO captchaDTO = new CaptchaDTO(); CaptchaDTO captchaDTO = new CaptchaDTO();
captchaDTO.setCaptchaImageCode(randomCode); captchaDTO.setCaptchaImageCode(randomCode);

View File

@@ -20,6 +20,8 @@
<result column="update_by" property="updateBy" /> <result column="update_by" property="updateBy" />
<result column="update_time" property="updateTime" /> <result column="update_time" property="updateTime" />
<result column="tenant_id" property="tenantId"/> <result column="tenant_id" property="tenantId"/>
<result column="token" property="token"/>
<result column="refresh_token" property="refreshToken"/>
</resultMap> </resultMap>
<sql id="Base_Column_List"> <sql id="Base_Column_List">
@@ -38,7 +40,9 @@
create_time, create_time,
update_by, update_by,
update_time, update_time,
tenant_id tenant_id,
token,
refresh_token
</sql> </sql>
<insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id" parameterType="com.xiang.xservice.auth.service.entity.XUser"> <insert id="insert" useGeneratedKeys="true" keyColumn="id" keyProperty="id" parameterType="com.xiang.xservice.auth.service.entity.XUser">
@@ -87,7 +91,13 @@
update_time, update_time,
</if> </if>
<if test="tenantId != null"> <if test="tenantId != null">
tenant_id tenant_id,
</if>
<if test="token != null and token != ''">
token,
</if>
<if test="refreshToken != null and refreshToken != ''">
refreshToken
</if> </if>
</trim> </trim>
<trim prefix="values (" suffix=")" suffixOverrides=","> <trim prefix="values (" suffix=")" suffixOverrides=",">
@@ -134,7 +144,13 @@
#{updateTime}, #{updateTime},
</if> </if>
<if test="tenantId != null"> <if test="tenantId != null">
#{tenantId} #{tenantId},
</if>
<if test="token != null and token != ''">
#{token},
</if>
<if test="refreshToken != null and refreshToken != ''">
#{refreshToken}
</if> </if>
</trim> </trim>
</insert> </insert>
@@ -165,7 +181,9 @@
<if test="null != createTime ">create_time = #{createTime},</if> <if test="null != createTime ">create_time = #{createTime},</if>
<if test="null != updateBy and '' != updateBy">update_by = #{updateBy},</if> <if test="null != updateBy and '' != updateBy">update_by = #{updateBy},</if>
<if test="null != updateTime ">update_time = #{updateTime},</if> <if test="null != updateTime ">update_time = #{updateTime},</if>
<if test="null != tenantId ">tenant_id = #{tenantId}</if> <if test="null != tenantId ">tenant_id = #{tenantId},</if>
<if test="null != token and '' != token ">token = #{token},</if>
<if test="null != refreshToken and '' != refreshToken ">refresh_token = #{refreshToken}</if>
</set> </set>
WHERE id = #{id} WHERE id = #{id}
</update> </update>