BF-202308171519\Administrator před 2 roky
rodič
revize
d1d2c2b83a
45 změnil soubory, kde provedl 2114 přidání a 168 odebrání
  1. 10 0
      kxs-admin/pom.xml
  2. 21 0
      kxs-admin/src/main/java/com/kxs/admin/api/AdminApplication.java
  3. 67 0
      kxs-admin/src/main/resources/application.yml
  4. 77 0
      kxs-admin/src/main/resources/logback-spring.xml
  5. 45 0
      kxs-common/kxs-common-auth/pom.xml
  6. 134 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/config/AuthorizationServerConfiguration.java
  7. 60 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/config/WebSecurityConfiguration.java
  8. 119 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/CustomeOAuth2AccessTokenGenerator.java
  9. 97 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationConverter.java
  10. 287 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java
  11. 61 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationToken.java
  12. 4 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/base/package-info.java
  13. 38 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/core/CustomeOAuth2TokenCustomizer.java
  14. 32 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/core/FormIdentityLoginConfigurer.java
  15. 193 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/core/SkyDaoAuthenticationProvider.java
  16. 37 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/handler/FormAuthenticationFailureHandler.java
  17. 92 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/handler/SkyAuthenticationFailureEventHandler.java
  18. 105 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/handler/SkyAuthenticationSuccessEventHandler.java
  19. 76 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/handler/SkyLogoutSuccessEventHandler.java
  20. 41 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/handler/SsoLogoutSuccessHandler.java
  21. 63 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java
  22. 64 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java
  23. 21 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationToken.java
  24. 4 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/password/package-info.java
  25. 57 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationConverter.java
  26. 66 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationProvider.java
  27. 21 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationToken.java
  28. 4 0
      kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/sms/package-info.java
  29. 4 0
      kxs-common/kxs-common-auth/src/main/resources/static/css/bootstrap.min.css
  30. 66 0
      kxs-common/kxs-common-auth/src/main/resources/static/css/signin.css
  31. 56 0
      kxs-common/kxs-common-auth/src/main/resources/templates/ftl/confirm.ftl
  32. 32 0
      kxs-common/kxs-common-auth/src/main/resources/templates/ftl/login.ftl
  33. 4 0
      kxs-common/kxs-common-core/src/main/java/com/kxs/common/core/constant/SecurityConstants.java
  34. 5 0
      kxs-common/kxs-common-mybatis/pom.xml
  35. 1 2
      kxs-common/kxs-common-security/src/main/java/com/kxs/common/security/annotation/EnableSkyResourceServer.java
  36. 0 4
      kxs-common/kxs-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  37. 1 0
      kxs-common/pom.xml
  38. 4 0
      kxs-system/kxs-system-api/pom.xml
  39. 0 98
      kxs-system/kxs-system-api/src/main/java/com/kxs/system/api/util/DictResolver.java
  40. 0 60
      kxs-system/kxs-system-api/src/main/java/com/kxs/system/api/util/ParamResolver.java
  41. 8 0
      kxs-system/kxs-system-biz/pom.xml
  42. 21 0
      kxs-system/kxs-system-biz/src/main/java/com/kxs/system/biz/AdminApplication.java
  43. 2 2
      kxs-system/kxs-system-biz/src/main/java/com/kxs/system/biz/controller/SysUserController.java
  44. 4 2
      kxs-system/kxs-system-biz/src/main/java/com/kxs/system/biz/service/impl/SysUserServiceImpl.java
  45. 10 0
      pom.xml

+ 10 - 0
kxs-admin/pom.xml

@@ -20,9 +20,19 @@
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     </properties>
     <dependencies>
+        <!--undertow容器-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-undertow</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.kxs</groupId>
             <artifactId>kxs-system-biz</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+
     </dependencies>
 </project>

+ 21 - 0
kxs-admin/src/main/java/com/kxs/admin/api/AdminApplication.java

@@ -0,0 +1,21 @@
+package com.kxs.admin.api;
+
+import com.kxs.common.security.annotation.EnableSkyResourceServer;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author lengleng
+ * @date 2018年06月21日
+ * <p>
+ * 用户统一管理系统
+ */
+@EnableSkyResourceServer
+@SpringBootApplication
+public class AdminApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(AdminApplication.class, args);
+	}
+
+}

+ 67 - 0
kxs-admin/src/main/resources/application.yml

@@ -0,0 +1,67 @@
+server:
+  port: 8080
+
+spring:
+  application:
+    name: kxs-admin
+  data:
+    redis:
+      host: localhost
+      repositories:
+        enabled: false # 项目未使用到 Spring Data Redis 的 Repository,所以直接禁用,保证启动速度\
+  datasource:
+    type: com.zaxxer.hikari.HikariDataSource
+    driver-class-name: com.mysql.cj.jdbc.Driver
+    username: root
+    password: root
+    url: jdbc:mysql://sky-mysql:3306/kxs?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true&allowPublicKeyRetrieval=true
+
+  profiles:
+    active: dev
+
+  # Servlet 配置
+  servlet:
+    # 文件上传相关配置项
+    multipart:
+      max-file-size: 16MB # 单个文件大小
+      max-request-size: 32MB # 设置总上传的文件大小
+
+  # Cache 配置项
+  cache:
+    type: REDIS
+    redis:
+      time-to-live: 1h # 设置过期时间为 1 小时
+
+--- #################### 接口文档配置 ####################
+
+springdoc:
+  api-docs:
+    enabled: true
+    path: /v3/api-docs
+  swagger-ui:
+    enabled: true
+    path: /swagger-ui
+  default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档
+
+knife4j:
+  enable: true
+  setting:
+    language: zh_cn
+
+# mybaits-plus配置
+mybatis-plus:
+  mapper-locations: classpath:/mapper/*Mapper.xml
+  global-config:
+    banner: false # 关闭控制台的 Banner 打印
+    db-config:
+      id-type: auto
+      table-underline: true
+      logic-delete-value: 1  # 逻辑已删除值(默认为 1)
+      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
+  type-handlers-package: com.kxs.common.mybatis.handler
+  configuration:
+    map-underscore-to-camel-case: true
+    shrink-whitespaces-in-sql: true
+
+
+

+ 77 - 0
kxs-admin/src/main/resources/logback-spring.xml

@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (c) 2020 sky4cloud Authors. All Rights Reserved.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<configuration debug="false" scan="false">
+	<springProperty scop="context" name="spring.application.name" source="spring.application.name" defaultValue=""/>
+	<property name="log.path" value="logs/${spring.application.name}"/>
+	<!-- 彩色日志格式 -->
+	<property name="CONSOLE_LOG_PATTERN"
+			  value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
+	<!-- 彩色日志依赖的渲染类 -->
+	<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
+	<conversionRule conversionWord="wex"
+					converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
+	<conversionRule conversionWord="wEx"
+					converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
+	<!-- Console log output -->
+	<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
+		<encoder>
+			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- Log file debug output -->
+	<appender name="debug" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/debug.log</file>
+		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/%d{yyyy-MM, aux}/debug.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
+			<maxFileSize>50MB</maxFileSize>
+			<maxHistory>30</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
+		</encoder>
+	</appender>
+
+	<!-- Log file error output -->
+	<appender name="error" class="ch.qos.logback.core.rolling.RollingFileAppender">
+		<file>${log.path}/error.log</file>
+		<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+			<fileNamePattern>${log.path}/%d{yyyy-MM}/error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
+			<maxFileSize>50MB</maxFileSize>
+			<maxHistory>30</maxHistory>
+		</rollingPolicy>
+		<encoder>
+			<pattern>${CONSOLE_LOG_PATTERN}</pattern>
+		</encoder>
+		<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+			<level>ERROR</level>
+		</filter>
+	</appender>
+
+	<!--nacos 心跳 INFO 屏蔽-->
+	<logger name="com.alibaba.nacos" level="OFF">
+		<appender-ref ref="error"/>
+	</logger>
+
+	<!-- Level: FATAL 0  ERROR 3  WARN 4  INFO 6  DEBUG 7 -->
+	<root level="debug">
+		<appender-ref ref="console"/>
+		<appender-ref ref="debug"/>
+		<appender-ref ref="error"/>
+	</root>
+</configuration>

+ 45 - 0
kxs-common/kxs-common-auth/pom.xml

@@ -0,0 +1,45 @@
+<?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">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>com.kxs</groupId>
+        <artifactId>kxs-common</artifactId>
+        <version>1.1.0</version>
+    </parent>
+
+    <artifactId>kxs-common-auth</artifactId>
+
+    <properties>
+        <maven.compiler.source>17</maven.compiler.source>
+        <maven.compiler.target>17</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <!--工具类核心包-->
+        <dependency>
+            <groupId>com.kxs</groupId>
+            <artifactId>kxs-common-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.kxs</groupId>
+            <artifactId>kxs-system-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.kxs</groupId>
+            <artifactId>kxs-common-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <!-- log -->
+        <dependency>
+            <groupId>com.kxs</groupId>
+            <artifactId>kxs-common-log</artifactId>
+        </dependency>
+    </dependencies>
+
+</project>

+ 134 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/config/AuthorizationServerConfiguration.java

@@ -0,0 +1,134 @@
+package com.kxs.common.auth.config;
+
+
+import com.kxs.common.auth.support.CustomeOAuth2AccessTokenGenerator;
+import com.kxs.common.auth.support.core.CustomeOAuth2TokenCustomizer;
+import com.kxs.common.auth.support.core.FormIdentityLoginConfigurer;
+import com.kxs.common.auth.support.core.SkyDaoAuthenticationProvider;
+import com.kxs.common.auth.support.handler.SkyAuthenticationFailureEventHandler;
+import com.kxs.common.auth.support.handler.SkyAuthenticationSuccessEventHandler;
+import com.kxs.common.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationConverter;
+import com.kxs.common.auth.support.password.OAuth2ResourceOwnerPasswordAuthenticationProvider;
+import com.kxs.common.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationConverter;
+import com.kxs.common.auth.support.sms.OAuth2ResourceOwnerSmsAuthenticationProvider;
+import com.kxs.common.core.constant.SecurityConstants;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
+import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
+import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
+import org.springframework.security.oauth2.server.authorization.web.authentication.*;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+
+import java.util.Arrays;
+
+/**
+ * @author lengleng
+ * @date 2022/5/27
+ *
+ * 认证服务器配置
+ */
+@Configuration
+@RequiredArgsConstructor
+public class AuthorizationServerConfiguration {
+
+	private final OAuth2AuthorizationService authorizationService;
+
+	@Bean
+	@Order(Ordered.HIGHEST_PRECEDENCE)
+	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
+		OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
+
+		http.apply(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {// 个性化认证授权端点
+			tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter()) // 注入自定义的授权认证Converter
+				.accessTokenResponseHandler(new SkyAuthenticationSuccessEventHandler()) // 登录成功处理器
+				.errorResponseHandler(new SkyAuthenticationFailureEventHandler());// 登录失败处理器
+		}).clientAuthentication(oAuth2ClientAuthenticationConfigurer -> // 个性化客户端认证
+		oAuth2ClientAuthenticationConfigurer.errorResponseHandler(new SkyAuthenticationFailureEventHandler()))// 处理客户端认证异常
+			.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint// 授权码端点个性化confirm页面
+				.consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)));
+
+		AntPathRequestMatcher[] requestMatchers = new AntPathRequestMatcher[] {
+				AntPathRequestMatcher.antMatcher("/token/**"), AntPathRequestMatcher.antMatcher("/actuator/**"),
+				AntPathRequestMatcher.antMatcher("/css/**"), AntPathRequestMatcher.antMatcher("/error") };
+
+		http.authorizeHttpRequests(authorizeRequests -> {
+			// 自定义接口、端点暴露
+			authorizeRequests.requestMatchers(requestMatchers).permitAll();
+			authorizeRequests.anyRequest().authenticated();
+		})
+			.apply(authorizationServerConfigurer.authorizationService(authorizationService)// redis存储token的实现
+				.authorizationServerSettings(
+						AuthorizationServerSettings.builder().issuer(SecurityConstants.PROJECT_LICENSE).build()));
+		http.apply(new FormIdentityLoginConfigurer());
+		DefaultSecurityFilterChain securityFilterChain = http.build();
+
+		// 注入自定义授权模式实现
+		addCustomOAuth2GrantAuthenticationProvider(http);
+		return securityFilterChain;
+	}
+
+	/**
+	 * 令牌生成规则实现 </br>
+	 * client:username:uuid
+	 * @return OAuth2TokenGenerator
+	 */
+	@Bean
+	public OAuth2TokenGenerator oAuth2TokenGenerator() {
+		CustomeOAuth2AccessTokenGenerator accessTokenGenerator = new CustomeOAuth2AccessTokenGenerator();
+		// 注入Token 增加关联用户信息
+		accessTokenGenerator.setAccessTokenCustomizer(new CustomeOAuth2TokenCustomizer());
+		return new DelegatingOAuth2TokenGenerator(accessTokenGenerator, new OAuth2RefreshTokenGenerator());
+	}
+
+	/**
+	 * request -> xToken 注入请求转换器
+	 * @return DelegatingAuthenticationConverter
+	 */
+	private AuthenticationConverter accessTokenRequestConverter() {
+		return new DelegatingAuthenticationConverter(Arrays.asList(
+				new OAuth2ResourceOwnerPasswordAuthenticationConverter(),
+				new OAuth2ResourceOwnerSmsAuthenticationConverter(), new OAuth2RefreshTokenAuthenticationConverter(),
+				new OAuth2ClientCredentialsAuthenticationConverter(),
+				new OAuth2AuthorizationCodeAuthenticationConverter(),
+				new OAuth2AuthorizationCodeRequestAuthenticationConverter()));
+	}
+
+	/**
+	 * 注入授权模式实现提供方
+	 *
+	 * 1. 密码模式 </br>
+	 * 2. 短信登录 </br>
+	 *
+	 */
+	@SuppressWarnings("unchecked")
+	private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) {
+		AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
+		OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
+
+		OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider(
+				authenticationManager, authorizationService, oAuth2TokenGenerator());
+
+		OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider(
+				authenticationManager, authorizationService, oAuth2TokenGenerator());
+
+		// 处理 UsernamePasswordAuthenticationToken
+		http.authenticationProvider(new SkyDaoAuthenticationProvider());
+		// 处理 OAuth2ResourceOwnerPasswordAuthenticationToken
+		http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider);
+		// 处理 OAuth2ResourceOwnerSmsAuthenticationToken
+		http.authenticationProvider(resourceOwnerSmsAuthenticationProvider);
+	}
+
+}

+ 60 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/config/WebSecurityConfiguration.java

@@ -0,0 +1,60 @@
+package com.kxs.common.auth.config;
+
+import com.kxs.common.auth.support.core.FormIdentityLoginConfigurer;
+import com.kxs.common.auth.support.core.SkyDaoAuthenticationProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
+import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+
+/**
+ * 服务安全相关配置
+ *
+ * @author lengleng
+ * @date 2022/1/12
+ */
+@EnableWebSecurity
+public class WebSecurityConfiguration {
+
+	/**
+	 * spring security 默认的安全策略
+	 * @param http security注入点
+	 * @return SecurityFilterChain
+	 * @throws Exception
+	 */
+	@Bean
+	SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
+		http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers("/token/*")
+			.permitAll()// 开放自定义的部分端点
+			.anyRequest()
+			.authenticated()).headers(header -> header.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)// 避免iframe同源无法登录许iframe
+		).apply(new FormIdentityLoginConfigurer()); // 表单登录个性化
+		// 处理 UsernamePasswordAuthenticationToken
+		http.authenticationProvider(new SkyDaoAuthenticationProvider());
+		return http.build();
+	}
+
+	/**
+	 * 暴露静态资源
+	 * <p>
+	 * <a href="https://github.com/spring-projects/spring-security/issues/10938">...</a>
+	 * @param http
+	 * @return
+	 * @throws Exception
+	 */
+	@Bean
+	@Order(0)
+	SecurityFilterChain resources(HttpSecurity http) throws Exception {
+		http.securityMatchers((matchers) -> matchers.requestMatchers("/actuator/**", "/css/**", "/error"))
+			.authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll())
+			.requestCache(RequestCacheConfigurer::disable)
+			.securityContext(AbstractHttpConfigurer::disable)
+			.sessionManagement(AbstractHttpConfigurer::disable);
+		return http.build();
+	}
+
+}

+ 119 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/CustomeOAuth2AccessTokenGenerator.java

@@ -0,0 +1,119 @@
+package com.kxs.common.auth.support;
+
+import org.springframework.lang.Nullable;
+import org.springframework.security.oauth2.core.ClaimAccessor;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
+import org.springframework.security.oauth2.server.authorization.token.*;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * @author lengleng
+ * @date 2022/5/29
+ */
+public class CustomeOAuth2AccessTokenGenerator implements OAuth2TokenGenerator<OAuth2AccessToken> {
+
+	private OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer;
+
+	@Nullable
+	@Override
+	public OAuth2AccessToken generate(OAuth2TokenContext context) {
+		if (!OAuth2TokenType.ACCESS_TOKEN.equals(context.getTokenType()) || !OAuth2TokenFormat.REFERENCE
+			.equals(context.getRegisteredClient().getTokenSettings().getAccessTokenFormat())) {
+			return null;
+		}
+
+		String issuer = null;
+		if (context.getAuthorizationServerContext() != null) {
+			issuer = context.getAuthorizationServerContext().getIssuer();
+		}
+		RegisteredClient registeredClient = context.getRegisteredClient();
+
+		Instant issuedAt = Instant.now();
+		Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getAccessTokenTimeToLive());
+
+		// @formatter:off
+        OAuth2TokenClaimsSet.Builder claimsBuilder = OAuth2TokenClaimsSet.builder();
+        if (StringUtils.hasText(issuer)) {
+            claimsBuilder.issuer(issuer);
+        }
+        claimsBuilder
+                .subject(context.getPrincipal().getName())
+                .audience(Collections.singletonList(registeredClient.getClientId()))
+                .issuedAt(issuedAt)
+                .expiresAt(expiresAt)
+                .notBefore(issuedAt)
+                .id(UUID.randomUUID().toString());
+        if (!CollectionUtils.isEmpty(context.getAuthorizedScopes())) {
+            claimsBuilder.claim(OAuth2ParameterNames.SCOPE, context.getAuthorizedScopes());
+        }
+        // @formatter:on
+
+		if (this.accessTokenCustomizer != null) {
+			// @formatter:off
+            OAuth2TokenClaimsContext.Builder accessTokenContextBuilder = OAuth2TokenClaimsContext.with(claimsBuilder)
+                    .registeredClient(context.getRegisteredClient())
+                    .principal(context.getPrincipal())
+                    .authorizationServerContext(context.getAuthorizationServerContext())
+                    .authorizedScopes(context.getAuthorizedScopes())
+                    .tokenType(context.getTokenType())
+                    .authorizationGrantType(context.getAuthorizationGrantType());
+            if (context.getAuthorization() != null) {
+                accessTokenContextBuilder.authorization(context.getAuthorization());
+            }
+            if (context.getAuthorizationGrant() != null) {
+                accessTokenContextBuilder.authorizationGrant(context.getAuthorizationGrant());
+            }
+            // @formatter:on
+
+			OAuth2TokenClaimsContext accessTokenContext = accessTokenContextBuilder.build();
+			this.accessTokenCustomizer.customize(accessTokenContext);
+		}
+
+		OAuth2TokenClaimsSet accessTokenClaimsSet = claimsBuilder.build();
+		return new OAuth2AccessTokenClaims(OAuth2AccessToken.TokenType.BEARER,
+				UUID.randomUUID().toString(), accessTokenClaimsSet.getIssuedAt(), accessTokenClaimsSet.getExpiresAt(),
+				context.getAuthorizedScopes(), accessTokenClaimsSet.getClaims());
+	}
+
+	/**
+	 * Sets the {@link OAuth2TokenCustomizer} that customizes the
+	 * {@link OAuth2TokenClaimsContext#getClaims() claims} for the
+	 * {@link OAuth2AccessToken}.
+	 * @param accessTokenCustomizer the {@link OAuth2TokenCustomizer} that customizes the
+	 * claims for the {@code OAuth2AccessToken}
+	 */
+	public void setAccessTokenCustomizer(OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer) {
+		Assert.notNull(accessTokenCustomizer, "accessTokenCustomizer cannot be null");
+		this.accessTokenCustomizer = accessTokenCustomizer;
+	}
+
+	private static final class OAuth2AccessTokenClaims extends OAuth2AccessToken implements ClaimAccessor {
+
+		private final Map<String, Object> claims;
+
+		private OAuth2AccessTokenClaims(TokenType tokenType, String tokenValue, Instant issuedAt, Instant expiresAt,
+				Set<String> scopes, Map<String, Object> claims) {
+			super(tokenType, tokenValue, issuedAt, expiresAt, scopes);
+			this.claims = claims;
+		}
+
+		@Override
+		public Map<String, Object> getClaims() {
+			return this.claims;
+		}
+
+	}
+
+}

+ 97 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationConverter.java

@@ -0,0 +1,97 @@
+package com.kxs.common.auth.support.base;
+
+import com.kxs.common.security.util.OAuth2EndpointUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.web.authentication.AuthenticationConverter;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * @author jumuning
+ * @date 2022-06-02
+ *
+ * 自定义模式认证转换器
+ */
+public abstract class OAuth2ResourceOwnerBaseAuthenticationConverter<T extends OAuth2ResourceOwnerBaseAuthenticationToken>
+		implements AuthenticationConverter {
+
+	/**
+	 * 是否支持此convert
+	 * @param grantType 授权类型
+	 * @return
+	 */
+	public abstract boolean support(String grantType);
+
+	/**
+	 * 校验参数
+	 * @param request 请求
+	 */
+	public void checkParams(HttpServletRequest request) {
+
+	}
+
+	/**
+	 * 构建具体类型的token
+	 * @param clientPrincipal
+	 * @param requestedScopes
+	 * @param additionalParameters
+	 * @return
+	 */
+	public abstract T buildToken(Authentication clientPrincipal, Set<String> requestedScopes,
+			Map<String, Object> additionalParameters);
+
+	@Override
+	public Authentication convert(HttpServletRequest request) {
+
+		// grant_type (REQUIRED)
+		String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
+		if (!support(grantType)) {
+			return null;
+		}
+
+		MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
+		// scope (OPTIONAL)
+		String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
+		if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
+			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE,
+					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
+		}
+
+		Set<String> requestedScopes = null;
+		if (StringUtils.hasText(scope)) {
+			requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
+		}
+
+		// 校验个性化参数
+		checkParams(request);
+
+		// 获取当前已经认证的客户端信息
+		Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication();
+		if (clientPrincipal == null) {
+			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ErrorCodes.INVALID_CLIENT,
+					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
+		}
+
+		// 扩展信息
+		Map<String, Object> additionalParameters = parameters.entrySet()
+			.stream()
+			.filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE)
+					&& !e.getKey().equals(OAuth2ParameterNames.SCOPE))
+			.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0)));
+
+		// 创建token
+		return buildToken(clientPrincipal, requestedScopes, additionalParameters);
+
+	}
+
+}

+ 287 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationProvider.java

@@ -0,0 +1,287 @@
+package com.kxs.common.auth.support.base;
+
+import cn.hutool.extra.spring.SpringUtil;
+import com.kxs.common.security.util.OAuth2ErrorCodesExpand;
+import com.kxs.common.security.util.ScopeException;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.context.support.MessageSourceAccessor;
+import org.springframework.security.authentication.*;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.oauth2.core.*;
+import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
+import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
+import org.springframework.util.Assert;
+import org.springframework.util.CollectionUtils;
+
+import java.security.Principal;
+import java.time.Instant;
+import java.util.*;
+import java.util.function.Supplier;
+
+/**
+ * @author jumuning
+ * @description
+ *
+ * 处理自定义授权
+ */
+public abstract class OAuth2ResourceOwnerBaseAuthenticationProvider<T extends OAuth2ResourceOwnerBaseAuthenticationToken>
+		implements AuthenticationProvider {
+
+	private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerBaseAuthenticationProvider.class);
+
+	private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
+
+	private final OAuth2AuthorizationService authorizationService;
+
+	private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
+
+	private final AuthenticationManager authenticationManager;
+
+	private final MessageSourceAccessor messages;
+
+	@Deprecated
+	private Supplier<String> refreshTokenGenerator;
+
+	/**
+	 * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the
+	 * provided parameters.
+	 * @param authorizationService the authorization service
+	 * @param tokenGenerator the token generator
+	 * @since 0.2.3
+	 */
+	public OAuth2ResourceOwnerBaseAuthenticationProvider(AuthenticationManager authenticationManager,
+			OAuth2AuthorizationService authorizationService,
+			OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
+		Assert.notNull(authorizationService, "authorizationService cannot be null");
+		Assert.notNull(tokenGenerator, "tokenGenerator cannot be null");
+		this.authenticationManager = authenticationManager;
+		this.authorizationService = authorizationService;
+		this.tokenGenerator = tokenGenerator;
+
+		// 国际化配置
+		this.messages = new MessageSourceAccessor(SpringUtil.getBean("securityMessageSource"), Locale.CHINA);
+	}
+
+	@Deprecated
+	public void setRefreshTokenGenerator(Supplier<String> refreshTokenGenerator) {
+		Assert.notNull(refreshTokenGenerator, "refreshTokenGenerator cannot be null");
+		this.refreshTokenGenerator = refreshTokenGenerator;
+	}
+
+	public abstract UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters);
+
+	/**
+	 * 当前provider是否支持此令牌类型
+	 * @param authentication
+	 * @return
+	 */
+	@Override
+	public abstract boolean supports(Class<?> authentication);
+
+	/**
+	 * 当前的请求客户端是否支持此模式
+	 * @param registeredClient
+	 */
+	public abstract void checkClient(RegisteredClient registeredClient);
+
+	/**
+	 * Performs authentication with the same contract as
+	 * {@link AuthenticationManager#authenticate(Authentication)} .
+	 * @param authentication the authentication request object.
+	 * @return a fully authenticated object including credentials. May return
+	 * <code>null</code> if the <code>AuthenticationProvider</code> is unable to support
+	 * authentication of the passed <code>Authentication</code> object. In such a case,
+	 * the next <code>AuthenticationProvider</code> that supports the presented
+	 * <code>Authentication</code> class will be tried.
+	 * @throws AuthenticationException if authentication fails.
+	 */
+	@Override
+	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+
+		T resouceOwnerBaseAuthentication = (T) authentication;
+
+		OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(
+				resouceOwnerBaseAuthentication);
+
+		RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
+		checkClient(registeredClient);
+
+		Set<String> authorizedScopes;
+		// Default to configured scopes
+		if (!CollectionUtils.isEmpty(resouceOwnerBaseAuthentication.getScopes())) {
+			for (String requestedScope : resouceOwnerBaseAuthentication.getScopes()) {
+				if (!registeredClient.getScopes().contains(requestedScope)) {
+					throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE);
+				}
+			}
+			authorizedScopes = new LinkedHashSet<>(resouceOwnerBaseAuthentication.getScopes());
+		}
+		else {
+			authorizedScopes = new LinkedHashSet<>();
+		}
+
+		Map<String, Object> reqParameters = resouceOwnerBaseAuthentication.getAdditionalParameters();
+		try {
+
+			UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = buildToken(reqParameters);
+
+			LOGGER.debug("got usernamePasswordAuthenticationToken=" + usernamePasswordAuthenticationToken);
+
+			Authentication usernamePasswordAuthentication = authenticationManager
+				.authenticate(usernamePasswordAuthenticationToken);
+
+			// @formatter:off
+			DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder()
+					.registeredClient(registeredClient)
+					.principal(usernamePasswordAuthentication)
+					.authorizationServerContext(AuthorizationServerContextHolder.getContext())
+					.authorizedScopes(authorizedScopes)
+					.authorizationGrantType(AuthorizationGrantType.PASSWORD)
+					.authorizationGrant(resouceOwnerBaseAuthentication);
+			// @formatter:on
+
+			OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization
+				.withRegisteredClient(registeredClient)
+				.principalName(usernamePasswordAuthentication.getName())
+				.authorizationGrantType(AuthorizationGrantType.PASSWORD)
+				// 0.4.0 新增的方法
+				.authorizedScopes(authorizedScopes);
+
+			// ----- Access token -----
+			OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build();
+			OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext);
+			if (generatedAccessToken == null) {
+				OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
+						"The token generator failed to generate the access token.", ERROR_URI);
+				throw new OAuth2AuthenticationException(error);
+			}
+			OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
+					generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(),
+					generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes());
+			if (generatedAccessToken instanceof ClaimAccessor) {
+				authorizationBuilder.id(accessToken.getTokenValue())
+					.token(accessToken,
+							(metadata) -> metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME,
+									((ClaimAccessor) generatedAccessToken).getClaims()))
+					// 0.4.0 新增的方法
+					.authorizedScopes(authorizedScopes)
+					.attribute(Principal.class.getName(), usernamePasswordAuthentication);
+			}
+			else {
+				authorizationBuilder.id(accessToken.getTokenValue()).accessToken(accessToken);
+			}
+
+			// ----- Refresh token -----
+			OAuth2RefreshToken refreshToken = null;
+			if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) &&
+			// Do not issue refresh token to public client
+					!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) {
+
+				if (this.refreshTokenGenerator != null) {
+					Instant issuedAt = Instant.now();
+					Instant expiresAt = issuedAt.plus(registeredClient.getTokenSettings().getRefreshTokenTimeToLive());
+					refreshToken = new OAuth2RefreshToken(this.refreshTokenGenerator.get(), issuedAt, expiresAt);
+				}
+				else {
+					tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build();
+					OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext);
+					if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) {
+						OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
+								"The token generator failed to generate the refresh token.", ERROR_URI);
+						throw new OAuth2AuthenticationException(error);
+					}
+					refreshToken = (OAuth2RefreshToken) generatedRefreshToken;
+				}
+				authorizationBuilder.refreshToken(refreshToken);
+			}
+
+			OAuth2Authorization authorization = authorizationBuilder.build();
+
+			this.authorizationService.save(authorization);
+
+			LOGGER.debug("returning OAuth2AccessTokenAuthenticationToken");
+
+			return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken,
+					refreshToken, Objects.requireNonNull(authorization.getAccessToken().getClaims()));
+
+		}
+		catch (Exception ex) {
+			LOGGER.error("problem in authenticate", ex);
+			throw oAuth2AuthenticationException(authentication, (AuthenticationException) ex);
+		}
+
+	}
+
+	/**
+	 * 登录异常转换为oauth2异常
+	 * @param authentication 身份验证
+	 * @param authenticationException 身份验证异常
+	 * @return {@link OAuth2AuthenticationException}
+	 */
+	private OAuth2AuthenticationException oAuth2AuthenticationException(Authentication authentication,
+			AuthenticationException authenticationException) {
+		if (authenticationException instanceof UsernameNotFoundException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USERNAME_NOT_FOUND,
+					this.messages.getMessage("JdbcDaoImpl.notFound", new Object[] { authentication.getName() },
+							"Username {0} not found"),
+					""));
+		}
+		if (authenticationException instanceof BadCredentialsException) {
+			return new OAuth2AuthenticationException(
+					new OAuth2Error(OAuth2ErrorCodesExpand.BAD_CREDENTIALS, this.messages.getMessage(
+							"AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"), ""));
+		}
+		if (authenticationException instanceof LockedException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_LOCKED, this.messages
+				.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked"), ""));
+		}
+		if (authenticationException instanceof DisabledException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_DISABLE,
+					this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled"),
+					""));
+		}
+		if (authenticationException instanceof AccountExpiredException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.USER_EXPIRED, this.messages
+				.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired"), ""));
+		}
+		if (authenticationException instanceof CredentialsExpiredException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodesExpand.CREDENTIALS_EXPIRED,
+					this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired",
+							"User credentials have expired"),
+					""));
+		}
+		if (authenticationException instanceof ScopeException) {
+			return new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_SCOPE,
+					this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "invalid_scope"), ""));
+		}
+		return new OAuth2AuthenticationException(OAuth2ErrorCodesExpand.UN_KNOW_LOGIN_ERROR);
+	}
+
+	private OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(
+			Authentication authentication) {
+
+		OAuth2ClientAuthenticationToken clientPrincipal = null;
+
+		if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) {
+			clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal();
+		}
+
+		if (clientPrincipal != null && clientPrincipal.isAuthenticated()) {
+			return clientPrincipal;
+		}
+
+		throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT);
+	}
+
+}

+ 61 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/base/OAuth2ResourceOwnerBaseAuthenticationToken.java

@@ -0,0 +1,61 @@
+package com.kxs.common.auth.support.base;
+
+import lombok.Getter;
+import org.springframework.lang.Nullable;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.util.Assert;
+
+import java.util.*;
+
+/**
+ * @author lengleng
+ * @date 2022/6/2
+ *
+ * 自定义授权模式抽象
+ */
+public abstract class OAuth2ResourceOwnerBaseAuthenticationToken extends AbstractAuthenticationToken {
+
+	@Getter
+	private final AuthorizationGrantType authorizationGrantType;
+
+	@Getter
+	private final Authentication clientPrincipal;
+
+	@Getter
+	private final Set<String> scopes;
+
+	@Getter
+	private final Map<String, Object> additionalParameters;
+
+	public OAuth2ResourceOwnerBaseAuthenticationToken(AuthorizationGrantType authorizationGrantType,
+			Authentication clientPrincipal, @Nullable Set<String> scopes,
+			@Nullable Map<String, Object> additionalParameters) {
+		super(Collections.emptyList());
+		Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null");
+		Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
+		this.authorizationGrantType = authorizationGrantType;
+		this.clientPrincipal = clientPrincipal;
+		this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
+		this.additionalParameters = Collections.unmodifiableMap(
+				additionalParameters != null ? new HashMap<>(additionalParameters) : Collections.emptyMap());
+	}
+
+	/**
+	 * 扩展模式一般不需要密码
+	 */
+	@Override
+	public Object getCredentials() {
+		return "";
+	}
+
+	/**
+	 * 获取用户名
+	 */
+	@Override
+	public Object getPrincipal() {
+		return this.clientPrincipal;
+	}
+
+}

+ 4 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/base/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 自定义认证模式接入的抽象实现
+ */
+package com.kxs.common.auth.support.base;

+ 38 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/core/CustomeOAuth2TokenCustomizer.java

@@ -0,0 +1,38 @@
+package com.kxs.common.auth.support.core;
+
+import com.kxs.common.core.constant.SecurityConstants;
+import com.kxs.common.security.model.KxsUser;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsSet;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
+
+/**
+ * token 输出增强
+ *
+ * @author lengleng
+ * @date 2022/6/3
+ */
+public class CustomeOAuth2TokenCustomizer implements OAuth2TokenCustomizer<OAuth2TokenClaimsContext> {
+
+	/**
+	 * Customize the OAuth 2.0 Token attributes.
+	 * @param context the context containing the OAuth 2.0 Token attributes
+	 */
+	@Override
+	public void customize(OAuth2TokenClaimsContext context) {
+		OAuth2TokenClaimsSet.Builder claims = context.getClaims();
+		claims.claim(SecurityConstants.DETAILS_LICENSE, SecurityConstants.PROJECT_LICENSE);
+		String clientId = context.getAuthorizationGrant().getName();
+		claims.claim(SecurityConstants.CLIENT_ID, clientId);
+		// 客户端模式不返回具体用户信息
+		if (SecurityConstants.CLIENT_CREDENTIALS.equals(context.getAuthorizationGrantType().getValue())) {
+			return;
+		}
+
+		KxsUser skyUser = (KxsUser) context.getPrincipal().getPrincipal();
+		claims.claim(SecurityConstants.DETAILS_USER, skyUser);
+		claims.claim(SecurityConstants.DETAILS_USER_ID, skyUser.getId());
+		claims.claim(SecurityConstants.USERNAME, skyUser.getUsername());
+	}
+
+}

+ 32 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/core/FormIdentityLoginConfigurer.java

@@ -0,0 +1,32 @@
+package com.kxs.common.auth.support.core;
+
+import com.kxs.common.auth.support.handler.FormAuthenticationFailureHandler;
+import com.kxs.common.auth.support.handler.SsoLogoutSuccessHandler;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+
+/**
+ * @author lengleng
+ * @data 2022-06-04
+ *
+ * 基于授权码模式 统一认证登录 spring security & sas 都可以使用 所以抽取成 HttpConfigurer
+ */
+public final class FormIdentityLoginConfigurer
+		extends AbstractHttpConfigurer<FormIdentityLoginConfigurer, HttpSecurity> {
+
+	@Override
+	public void init(HttpSecurity http) throws Exception {
+		http.formLogin(formLogin -> {
+			formLogin.loginPage("/token/login");
+			formLogin.loginProcessingUrl("/token/form");
+			formLogin.failureHandler(new FormAuthenticationFailureHandler());
+
+		})
+			.logout(logout -> logout.logoutSuccessHandler(new SsoLogoutSuccessHandler())
+				.deleteCookies("JSESSIONID")
+				.invalidateHttpSession(true)) // SSO登出成功处理
+
+			.csrf(AbstractHttpConfigurer::disable);
+	}
+
+}

+ 193 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/core/SkyDaoAuthenticationProvider.java

@@ -0,0 +1,193 @@
+package com.kxs.common.auth.support.core;
+
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.kxs.common.core.constant.SecurityConstants;
+import com.kxs.common.core.util.WebUtils;
+import com.kxs.common.security.service.KxsUserDetailsService;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.SneakyThrows;
+import org.springframework.core.Ordered;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.InternalAuthenticationServiceException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsPasswordService;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.security.crypto.factory.PasswordEncoderFactories;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.web.authentication.www.BasicAuthenticationConverter;
+import org.springframework.util.Assert;
+
+import java.util.Comparator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/**
+ * @author lengleng
+ * @date 2022-06-04
+ */
+public class SkyDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
+
+	/**
+	 * The plaintext password used to perform PasswordEncoder#matches(CharSequence,
+	 * String)} on when the user is not found to avoid SEC-2056.
+	 */
+	private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
+
+	private final static BasicAuthenticationConverter basicConvert = new BasicAuthenticationConverter();
+
+	private PasswordEncoder passwordEncoder;
+
+	/**
+	 * The password used to perform {@link PasswordEncoder#matches(CharSequence, String)}
+	 * on when the user is not found to avoid SEC-2056. This is necessary, because some
+	 * {@link PasswordEncoder} implementations will short circuit if the password is not
+	 * in a valid format.
+	 */
+	private volatile String userNotFoundEncodedPassword;
+
+	private UserDetailsService userDetailsService;
+
+	private UserDetailsPasswordService userDetailsPasswordService;
+
+	public SkyDaoAuthenticationProvider() {
+		setMessageSource(SpringUtil.getBean("securityMessageSource"));
+		setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
+	}
+
+	@Override
+	@SuppressWarnings("deprecation")
+	protected void additionalAuthenticationChecks(UserDetails userDetails,
+			UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
+
+		// app 模式不用校验密码
+		String grantType = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.GRANT_TYPE);
+		if (StrUtil.equals(SecurityConstants.MOBILE, grantType)) {
+			return;
+		}
+
+		if (authentication.getCredentials() == null) {
+			this.logger.debug("Failed to authenticate since no credentials provided");
+			throw new BadCredentialsException(this.messages
+				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
+		}
+		String presentedPassword = authentication.getCredentials().toString();
+		if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
+			this.logger.debug("Failed to authenticate since password does not match stored value");
+			throw new BadCredentialsException(this.messages
+				.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
+		}
+	}
+
+	@SneakyThrows
+	@Override
+
+	protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
+		prepareTimingAttackProtection();
+		HttpServletRequest request = WebUtils.getRequest()
+			.orElseThrow(
+					(Supplier<Throwable>) () -> new InternalAuthenticationServiceException("web request is empty"));
+
+		String grantType = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.GRANT_TYPE);
+		String clientId = WebUtils.getRequest().get().getParameter(OAuth2ParameterNames.CLIENT_ID);
+
+		if (StrUtil.isBlank(clientId)) {
+			clientId = basicConvert.convert(request).getName();
+		}
+
+		Map<String, KxsUserDetailsService> userDetailsServiceMap = SpringUtil
+			.getBeansOfType(KxsUserDetailsService.class);
+
+		String finalClientId = clientId;
+		Optional<KxsUserDetailsService> optional = userDetailsServiceMap.values()
+			.stream()
+			.filter(service -> service.support(finalClientId, grantType))
+			.max(Comparator.comparingInt(Ordered::getOrder));
+
+		if (!optional.isPresent()) {
+			throw new InternalAuthenticationServiceException("UserDetailsService error , not register");
+		}
+
+		try {
+			UserDetails loadedUser = optional.get().loadUserByUsername(username);
+			if (loadedUser == null) {
+				throw new InternalAuthenticationServiceException(
+						"UserDetailsService returned null, which is an interface contract violation");
+			}
+			return loadedUser;
+		}
+		catch (UsernameNotFoundException ex) {
+			mitigateAgainstTimingAttack(authentication);
+			throw ex;
+		}
+		catch (InternalAuthenticationServiceException ex) {
+			throw ex;
+		}
+		catch (Exception ex) {
+			throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
+		}
+	}
+
+	@Override
+	protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
+			UserDetails user) {
+		boolean upgradeEncoding = this.userDetailsPasswordService != null
+				&& this.passwordEncoder.upgradeEncoding(user.getPassword());
+		if (upgradeEncoding) {
+			String presentedPassword = authentication.getCredentials().toString();
+			String newPassword = this.passwordEncoder.encode(presentedPassword);
+			user = this.userDetailsPasswordService.updatePassword(user, newPassword);
+		}
+		return super.createSuccessAuthentication(principal, authentication, user);
+	}
+
+	private void prepareTimingAttackProtection() {
+		if (this.userNotFoundEncodedPassword == null) {
+			this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
+		}
+	}
+
+	private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
+		if (authentication.getCredentials() != null) {
+			String presentedPassword = authentication.getCredentials().toString();
+			this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
+		}
+	}
+
+	/**
+	 * Sets the PasswordEncoder instance to be used to encode and validate passwords. If
+	 * not set, the password will be compared using
+	 * {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
+	 * @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
+	 * types.
+	 */
+	public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
+		Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
+		this.passwordEncoder = passwordEncoder;
+		this.userNotFoundEncodedPassword = null;
+	}
+
+	protected PasswordEncoder getPasswordEncoder() {
+		return this.passwordEncoder;
+	}
+
+	public void setUserDetailsService(UserDetailsService userDetailsService) {
+		this.userDetailsService = userDetailsService;
+	}
+
+	protected UserDetailsService getUserDetailsService() {
+		return this.userDetailsService;
+	}
+
+	public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
+		this.userDetailsPasswordService = userDetailsPasswordService;
+	}
+
+}

+ 37 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/handler/FormAuthenticationFailureHandler.java

@@ -0,0 +1,37 @@
+package com.kxs.common.auth.support.handler;
+
+import cn.hutool.core.util.CharsetUtil;
+import com.kxs.common.core.util.WebUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+
+/**
+ * @author lengleng
+ * @date 2022-06-02
+ * <p>
+ * 表单登录失败处理逻辑
+ */
+@Slf4j
+public class FormAuthenticationFailureHandler implements AuthenticationFailureHandler {
+
+	/**
+	 * Called when an authentication attempt fails.
+	 * @param request the request during which the authentication attempt occurred.
+	 * @param response the response.
+	 * @param exception the exception which was thrown to reject the authentication
+	 */
+	@Override
+	@SneakyThrows
+	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
+			AuthenticationException exception) {
+		log.debug("表单登录失败:{}", exception.getLocalizedMessage());
+//		String url = HttpUtil.encodeParams(String.format("/token/login?error=%s", exception.getMessage()),
+//				CharsetUtil.CHARSET_UTF_8);
+		WebUtils.getResponse().sendRedirect("url");
+	}
+
+}

+ 92 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/handler/SkyAuthenticationFailureEventHandler.java

@@ -0,0 +1,92 @@
+package com.kxs.common.auth.support.handler;
+
+import cn.hutool.core.util.StrUtil;
+import com.kxs.common.core.constant.CommonConstants;
+import com.kxs.common.core.constant.SecurityConstants;
+import com.kxs.common.core.util.R;
+import com.kxs.common.core.util.SpringContextHolder;
+import com.kxs.common.log.event.SysLogEvent;
+import com.kxs.common.log.util.LogTypeEnum;
+import com.kxs.common.log.util.SysLogUtils;
+import com.kxs.system.api.model.SysLog;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.http.server.ServletServerHttpResponse;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.web.authentication.AuthenticationFailureHandler;
+
+import java.io.IOException;
+
+/**
+ * @author lengleng
+ * @date 2022-06-02
+ */
+@Slf4j
+public class SkyAuthenticationFailureEventHandler implements AuthenticationFailureHandler {
+
+	private final MappingJackson2HttpMessageConverter errorHttpResponseConverter = new MappingJackson2HttpMessageConverter();
+
+	/**
+	 * Called when an authentication attempt fails.
+	 * @param request the request during which the authentication attempt occurred.
+	 * @param response the response.
+	 * @param exception the exception which was thrown to reject the authentication
+	 * request.
+	 */
+	@Override
+	@SneakyThrows
+	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
+			AuthenticationException exception) {
+		String username = request.getParameter(OAuth2ParameterNames.USERNAME);
+
+		log.info("用户:{} 登录失败,异常:{}", username, exception.getLocalizedMessage());
+		SysLog logVo = SysLogUtils.getSysLog();
+		logVo.setTitle("登录失败");
+		logVo.setLogType(LogTypeEnum.ERROR.getType());
+		logVo.setException(exception.getLocalizedMessage());
+		// 发送异步日志事件
+		String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME);
+		if (StrUtil.isNotBlank(startTimeStr)) {
+			Long startTime = Long.parseLong(startTimeStr);
+			Long endTime = System.currentTimeMillis();
+			logVo.setTime(endTime - startTime);
+		}
+		logVo.setCreateBy(username);
+		SpringContextHolder.publishEvent(new SysLogEvent(logVo));
+		// 写出错误信息
+		sendErrorResponse(request, response, exception);
+	}
+
+	private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
+			AuthenticationException exception) throws IOException {
+		ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
+		httpResponse.setStatusCode(HttpStatus.UNAUTHORIZED);
+		String errorMessage;
+
+		if (exception instanceof OAuth2AuthenticationException) {
+			OAuth2AuthenticationException authorizationException = (OAuth2AuthenticationException) exception;
+			errorMessage = StrUtil.isBlank(authorizationException.getError().getDescription())
+					? authorizationException.getError().getErrorCode()
+					: authorizationException.getError().getDescription();
+		}
+		else {
+			errorMessage = exception.getLocalizedMessage();
+		}
+
+		// 手机号登录
+		String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE);
+		if (SecurityConstants.MOBILE.equals(grantType)) {
+			errorMessage = "客户端验证失败";
+		}
+
+		this.errorHttpResponseConverter.write(R.failed(errorMessage), MediaType.APPLICATION_JSON, httpResponse);
+	}
+
+}

+ 105 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/handler/SkyAuthenticationSuccessEventHandler.java

@@ -0,0 +1,105 @@
+package com.kxs.common.auth.support.handler;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import com.kxs.common.core.constant.CommonConstants;
+import com.kxs.common.core.constant.SecurityConstants;
+import com.kxs.common.core.util.SpringContextHolder;
+import com.kxs.common.log.event.SysLogEvent;
+import com.kxs.common.log.util.SysLogUtils;
+import com.kxs.common.security.component.SkyCustomOAuth2AccessTokenResponseHttpMessageConverter;
+import com.kxs.common.security.model.KxsUser;
+import com.kxs.system.api.model.SysLog;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.http.server.ServletServerHttpResponse;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.core.OAuth2AccessToken;
+import org.springframework.security.oauth2.core.OAuth2RefreshToken;
+import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
+import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken;
+import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
+import org.springframework.util.CollectionUtils;
+
+import java.io.IOException;
+import java.time.temporal.ChronoUnit;
+import java.util.Map;
+
+/**
+ * @author lengleng
+ * @date 2022-06-02
+ */
+@Slf4j
+public class SkyAuthenticationSuccessEventHandler implements AuthenticationSuccessHandler {
+
+	private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new SkyCustomOAuth2AccessTokenResponseHttpMessageConverter();
+
+	/**
+	 * Called when a user has been successfully authenticated.
+	 * @param request the request which caused the successful authentication
+	 * @param response the response
+	 * @param authentication the <tt>Authentication</tt> object which was created during
+	 * the authentication process.
+	 */
+	@SneakyThrows
+	@Override
+	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
+			Authentication authentication) {
+		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
+		Map<String, Object> map = accessTokenAuthentication.getAdditionalParameters();
+		if (MapUtil.isNotEmpty(map)) {
+			// 发送异步日志事件
+			KxsUser userInfo = (KxsUser) map.get(SecurityConstants.DETAILS_USER);
+			log.info("用户:{} 登录成功", userInfo.getName());
+			SecurityContextHolder.getContext().setAuthentication(accessTokenAuthentication);
+			SysLog logVo = SysLogUtils.getSysLog();
+			logVo.setTitle("登录成功");
+			String startTimeStr = request.getHeader(CommonConstants.REQUEST_START_TIME);
+			if (StrUtil.isNotBlank(startTimeStr)) {
+				Long startTime = Long.parseLong(startTimeStr);
+				Long endTime = System.currentTimeMillis();
+				logVo.setTime(endTime - startTime);
+			}
+			logVo.setCreateBy(userInfo.getName());
+			SpringContextHolder.publishEvent(new SysLogEvent(logVo));
+		}
+
+		// 输出token
+		sendAccessTokenResponse(request, response, authentication);
+	}
+
+	private void sendAccessTokenResponse(HttpServletRequest request, HttpServletResponse response,
+			Authentication authentication) throws IOException {
+
+		OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication;
+
+		OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
+		OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
+		Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
+
+		OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
+			.tokenType(accessToken.getTokenType())
+			.scopes(accessToken.getScopes());
+		if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
+			builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
+		}
+		if (refreshToken != null) {
+			builder.refreshToken(refreshToken.getTokenValue());
+		}
+		if (!CollectionUtils.isEmpty(additionalParameters)) {
+			builder.additionalParameters(additionalParameters);
+		}
+		OAuth2AccessTokenResponse accessTokenResponse = builder.build();
+		ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
+
+		// 无状态 注意删除 context 上下文的信息
+		SecurityContextHolder.clearContext();
+
+		this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
+	}
+
+}

+ 76 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/handler/SkyLogoutSuccessEventHandler.java

@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2020 sky4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.kxs.common.auth.support.handler;
+
+import com.kxs.common.core.util.SpringContextHolder;
+import com.kxs.common.core.util.WebUtils;
+import com.kxs.common.log.event.SysLogEvent;
+import com.kxs.common.log.util.SysLogUtils;
+import com.kxs.system.api.model.SysLog;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.ApplicationListener;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.authentication.event.LogoutSuccessEvent;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author zhangran
+ * @date 2022-06-02
+ *
+ * 事件机制处理退出相关
+ */
+@Slf4j
+@Component
+public class SkyLogoutSuccessEventHandler implements ApplicationListener<LogoutSuccessEvent> {
+
+	@Override
+	public void onApplicationEvent(LogoutSuccessEvent event) {
+		Authentication authentication = (Authentication) event.getSource();
+		if (authentication instanceof PreAuthenticatedAuthenticationToken) {
+			handle(authentication);
+		}
+	}
+
+	/**
+	 * 处理退出成功方法
+	 * <p>
+	 * 获取到登录的authentication 对象
+	 * @param authentication 登录对象
+	 */
+	public void handle(Authentication authentication) {
+		log.info("用户:{} 退出成功", authentication.getPrincipal());
+		SysLog logVo = SysLogUtils.getSysLog();
+		logVo.setTitle("退出成功");
+		// 发送异步日志事件
+		Long startTime = System.currentTimeMillis();
+		Long endTime = System.currentTimeMillis();
+		logVo.setTime(endTime - startTime);
+
+		// 设置对应的token
+		WebUtils.getRequest().ifPresent(request -> logVo.setParams(request.getHeader(HttpHeaders.AUTHORIZATION)));
+
+		// 这边设置ServiceId
+		if (authentication instanceof PreAuthenticatedAuthenticationToken) {
+			logVo.setServiceId(authentication.getCredentials().toString());
+		}
+		logVo.setCreateBy(authentication.getName());
+		SpringContextHolder.publishEvent(new SysLogEvent(logVo));
+	}
+
+}

+ 41 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/handler/SsoLogoutSuccessHandler.java

@@ -0,0 +1,41 @@
+package com.kxs.common.auth.support.handler;
+
+import cn.hutool.core.util.StrUtil;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
+
+import java.io.IOException;
+
+/**
+ * @author lengleng
+ * @date 2022-06-02
+ * <p>
+ * sso 退出功能 ,根据客户端传入跳转
+ */
+public class SsoLogoutSuccessHandler implements LogoutSuccessHandler {
+
+	private static final String REDIRECT_URL = "redirect_url";
+
+	@Override
+	public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
+			throws IOException {
+		if (response == null) {
+			return;
+		}
+
+		// 获取请求参数中是否包含 回调地址
+		String redirectUrl = request.getParameter(REDIRECT_URL);
+		if (StrUtil.isNotBlank(redirectUrl)) {
+			response.sendRedirect(redirectUrl);
+		}
+		else if (StrUtil.isNotBlank(request.getHeader(HttpHeaders.REFERER))) {
+			// 默认跳转referer 地址
+			String referer = request.getHeader(HttpHeaders.REFERER);
+			response.sendRedirect(referer);
+		}
+	}
+
+}

+ 63 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationConverter.java

@@ -0,0 +1,63 @@
+package com.kxs.common.auth.support.password;
+
+import com.kxs.common.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter;
+import com.kxs.common.security.util.OAuth2EndpointUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author jumuning
+ * @date 2022-06-02
+ *
+ * 密码认证转换器
+ */
+public class OAuth2ResourceOwnerPasswordAuthenticationConverter
+		extends OAuth2ResourceOwnerBaseAuthenticationConverter<OAuth2ResourceOwnerPasswordAuthenticationToken> {
+
+	/**
+	 * 支持密码模式
+	 * @param grantType 授权类型
+	 */
+	@Override
+	public boolean support(String grantType) {
+		return AuthorizationGrantType.PASSWORD.getValue().equals(grantType);
+	}
+
+	@Override
+	public OAuth2ResourceOwnerPasswordAuthenticationToken buildToken(Authentication clientPrincipal,
+			Set requestedScopes, Map additionalParameters) {
+		return new OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType.PASSWORD, clientPrincipal,
+				requestedScopes, additionalParameters);
+	}
+
+	/**
+	 * 校验扩展参数 密码模式密码必须不为空
+	 * @param request 参数列表
+	 */
+	@Override
+	public void checkParams(HttpServletRequest request) {
+		MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
+		// username (REQUIRED)
+		String username = parameters.getFirst(OAuth2ParameterNames.USERNAME);
+		if (!StringUtils.hasText(username) || parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) {
+			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.USERNAME,
+					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
+		}
+
+		// password (REQUIRED)
+		String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD);
+		if (!StringUtils.hasText(password) || parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) {
+			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.PASSWORD,
+					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
+		}
+	}
+
+}

+ 64 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationProvider.java

@@ -0,0 +1,64 @@
+package com.kxs.common.auth.support.password;
+
+import com.kxs.common.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.OAuth2Token;
+import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
+
+import java.util.Map;
+
+/**
+ * @author jumuning
+ * @description 处理用户名密码授权
+ */
+public class OAuth2ResourceOwnerPasswordAuthenticationProvider
+		extends OAuth2ResourceOwnerBaseAuthenticationProvider<OAuth2ResourceOwnerPasswordAuthenticationToken> {
+
+	private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerPasswordAuthenticationProvider.class);
+
+	/**
+	 * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the
+	 * provided parameters.
+	 * @param authenticationManager
+	 * @param authorizationService the authorization service
+	 * @param tokenGenerator the token generator
+	 * @since 0.2.3
+	 */
+	public OAuth2ResourceOwnerPasswordAuthenticationProvider(AuthenticationManager authenticationManager,
+			OAuth2AuthorizationService authorizationService,
+			OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
+		super(authenticationManager, authorizationService, tokenGenerator);
+	}
+
+	@Override
+	public UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters) {
+		String username = (String) reqParameters.get(OAuth2ParameterNames.USERNAME);
+		String password = (String) reqParameters.get(OAuth2ParameterNames.PASSWORD);
+		return new UsernamePasswordAuthenticationToken(username, password);
+	}
+
+	@Override
+	public boolean supports(Class<?> authentication) {
+		boolean supports = OAuth2ResourceOwnerPasswordAuthenticationToken.class.isAssignableFrom(authentication);
+		LOGGER.debug("supports authentication=" + authentication + " returning " + supports);
+		return supports;
+	}
+
+	@Override
+	public void checkClient(RegisteredClient registeredClient) {
+		assert registeredClient != null;
+		if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
+		}
+	}
+
+}

+ 21 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/password/OAuth2ResourceOwnerPasswordAuthenticationToken.java

@@ -0,0 +1,21 @@
+package com.kxs.common.auth.support.password;
+
+import com.kxs.common.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author jumuning
+ * @description 密码授权token信息
+ */
+public class OAuth2ResourceOwnerPasswordAuthenticationToken extends OAuth2ResourceOwnerBaseAuthenticationToken {
+
+	public OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType authorizationGrantType,
+			Authentication clientPrincipal, Set<String> scopes, Map<String, Object> additionalParameters) {
+		super(authorizationGrantType, clientPrincipal, scopes, additionalParameters);
+	}
+
+}

+ 4 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/password/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 密码模式
+ */
+package com.kxs.common.auth.support.password;

+ 57 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationConverter.java

@@ -0,0 +1,57 @@
+package com.kxs.common.auth.support.sms;
+
+import com.kxs.common.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationConverter;
+import com.kxs.common.core.constant.SecurityConstants;
+import com.kxs.common.security.util.OAuth2EndpointUtils;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.util.MultiValueMap;
+import org.springframework.util.StringUtils;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author lengleng
+ * @date 2022-05-31
+ *
+ * 短信登录转换器
+ */
+public class OAuth2ResourceOwnerSmsAuthenticationConverter
+		extends OAuth2ResourceOwnerBaseAuthenticationConverter<OAuth2ResourceOwnerSmsAuthenticationToken> {
+
+	/**
+	 * 是否支持此convert
+	 * @param grantType 授权类型
+	 * @return
+	 */
+	@Override
+	public boolean support(String grantType) {
+		return SecurityConstants.MOBILE.equals(grantType);
+	}
+
+	@Override
+	public OAuth2ResourceOwnerSmsAuthenticationToken buildToken(Authentication clientPrincipal, Set requestedScopes,
+			Map additionalParameters) {
+		return new OAuth2ResourceOwnerSmsAuthenticationToken(new AuthorizationGrantType(SecurityConstants.MOBILE),
+				clientPrincipal, requestedScopes, additionalParameters);
+	}
+
+	/**
+	 * 校验扩展参数 密码模式密码必须不为空
+	 * @param request 参数列表
+	 */
+	@Override
+	public void checkParams(HttpServletRequest request) {
+		MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
+		// PHONE (REQUIRED)
+		String phone = parameters.getFirst(SecurityConstants.SMS_PARAMETER_NAME);
+		if (!StringUtils.hasText(phone) || parameters.get(SecurityConstants.SMS_PARAMETER_NAME).size() != 1) {
+			OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, SecurityConstants.SMS_PARAMETER_NAME,
+					OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
+		}
+	}
+
+}

+ 66 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationProvider.java

@@ -0,0 +1,66 @@
+package com.kxs.common.auth.support.sms;
+
+import com.kxs.common.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationProvider;
+import com.kxs.common.core.constant.SecurityConstants;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
+import org.springframework.security.oauth2.core.OAuth2Token;
+import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
+import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
+import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
+
+import java.util.Map;
+
+/**
+ * @author lengleng
+ * @date date
+ *
+ * 短信登录的核心处理
+ */
+public class OAuth2ResourceOwnerSmsAuthenticationProvider
+		extends OAuth2ResourceOwnerBaseAuthenticationProvider<OAuth2ResourceOwnerSmsAuthenticationToken> {
+
+	private static final Logger LOGGER = LogManager.getLogger(OAuth2ResourceOwnerSmsAuthenticationProvider.class);
+
+	/**
+	 * Constructs an {@code OAuth2AuthorizationCodeAuthenticationProvider} using the
+	 * provided parameters.
+	 * @param authenticationManager
+	 * @param authorizationService the authorization service
+	 * @param tokenGenerator the token generator
+	 * @since 0.2.3
+	 */
+	public OAuth2ResourceOwnerSmsAuthenticationProvider(AuthenticationManager authenticationManager,
+			OAuth2AuthorizationService authorizationService,
+			OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) {
+		super(authenticationManager, authorizationService, tokenGenerator);
+	}
+
+	@Override
+	public boolean supports(Class<?> authentication) {
+		boolean supports = OAuth2ResourceOwnerSmsAuthenticationToken.class.isAssignableFrom(authentication);
+		LOGGER.debug("supports authentication=" + authentication + " returning " + supports);
+		return supports;
+	}
+
+	@Override
+	public void checkClient(RegisteredClient registeredClient) {
+		assert registeredClient != null;
+		if (!registeredClient.getAuthorizationGrantTypes()
+			.contains(new AuthorizationGrantType(SecurityConstants.MOBILE))) {
+			throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
+		}
+	}
+
+	@Override
+	public UsernamePasswordAuthenticationToken buildToken(Map<String, Object> reqParameters) {
+		String phone = (String) reqParameters.get(SecurityConstants.SMS_PARAMETER_NAME);
+		return new UsernamePasswordAuthenticationToken(phone, null);
+	}
+
+}

+ 21 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/sms/OAuth2ResourceOwnerSmsAuthenticationToken.java

@@ -0,0 +1,21 @@
+package com.kxs.common.auth.support.sms;
+
+import com.kxs.common.auth.support.base.OAuth2ResourceOwnerBaseAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.AuthorizationGrantType;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author lengleng
+ * @description 短信登录token信息
+ */
+public class OAuth2ResourceOwnerSmsAuthenticationToken extends OAuth2ResourceOwnerBaseAuthenticationToken {
+
+	public OAuth2ResourceOwnerSmsAuthenticationToken(AuthorizationGrantType authorizationGrantType,
+			Authentication clientPrincipal, Set<String> scopes, Map<String, Object> additionalParameters) {
+		super(authorizationGrantType, clientPrincipal, scopes, additionalParameters);
+	}
+
+}

+ 4 - 0
kxs-common/kxs-common-auth/src/main/java/com/kxs/common/auth/support/sms/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * 短信模式
+ */
+package com.kxs.common.auth.support.sms;

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 4 - 0
kxs-common/kxs-common-auth/src/main/resources/static/css/bootstrap.min.css


+ 66 - 0
kxs-common/kxs-common-auth/src/main/resources/static/css/signin.css

@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2020 pig4cloud Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+.sign_body {
+  padding-top: 40px;
+  padding-bottom: 40px;
+  background-color: #eee;
+}
+
+.form-signin {
+  max-width: 330px;
+  padding: 15px;
+  margin: 0 auto;
+}
+.form-margin-top {
+  margin-top: 50px;
+}
+.form-signin .form-signin-heading,
+.form-signin .checkbox {
+  margin-bottom: 10px;
+}
+.form-signin .checkbox {
+  font-weight: normal;
+}
+.form-signin .form-control {
+  position: relative;
+  height: auto;
+  -webkit-box-sizing: border-box;
+     -moz-box-sizing: border-box;
+          box-sizing: border-box;
+  padding: 10px;
+  font-size: 16px;
+}
+.form-signin .form-control:focus {
+  z-index: 2;
+}
+.form-signin input[type="email"] {
+  margin-bottom: -1px;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+.form-signin input[type="password"] {
+  margin-bottom: 10px;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+footer{
+  text-align: center;
+  position:absolute;
+  bottom:0;
+  width:100%;
+  height:100px;
+}

+ 56 - 0
kxs-common/kxs-common-auth/src/main/resources/templates/ftl/confirm.ftl

@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html>
+<html>
+<head>
+	<meta charset="UTF-8"/>
+	<meta name="viewport"
+	      content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
+	<title>Sky 第三方授权</title>
+	<link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css"/>
+	<link rel="stylesheet" type="text/css" href="/css/signin.css"/>
+</head>
+
+<body>
+<nav class="navbar navbar-default container-fluid">
+	<div class="container">
+		<div class="navbar-header">
+			<a class="navbar-brand" href="#">开放平台</a>
+		</div>
+		<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-5">
+			<p class="navbar-text navbar-right">
+				<a target="_blank" href="https://sky4cloud.com">技术支持</a>
+			</p>
+			<p class="navbar-text navbar-right">
+                <#if principalName=="anonymousUser">
+					未登录
+                <#else>
+					<a target="_blank" href="https://sky4cloud.com">${principalName}</a>
+                </#if>
+			</p>
+		</div>
+	</div>
+</nav>
+<div style="padding-top: 80px;width: 300px; color: #555; margin:0px auto;">
+	<form id='confirmationForm' name='confirmationForm' action="/oauth2/authorize" method='post'>
+		<input type="hidden" name="client_id" value="${clientId}">
+		<input type="hidden" name="state" value="${state}">
+
+		<p>
+			将获得以下权限:</p>
+		<ul class="list-group">
+			<li class="list-group-item"> <span>
+              <#list scopeList as scope>
+	              <input type="checkbox" checked="checked" name="scope" value="${scope}"/><label>${scope}</label>
+              </#list>
+		</ul>
+		<p class="help-block">授权后表明你已同意 <a>服务协议</a></p>
+		<button class="btn btn-success pull-right" type="submit" id="write-email-btn">授权</button>
+		</p>
+	</form>
+</div>
+<footer>
+	<p>support by: sky4cloud.com</p>
+	<p>email: <a href="mailto:wangiegie@gmail.com">wangiegie@gmail.com</a>.</p>
+</footer>
+</body>
+</html>

+ 32 - 0
kxs-common/kxs-common-auth/src/main/resources/templates/ftl/login.ftl

@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+	<meta charset="utf-8">
+	<meta http-equiv="X-UA-Compatible" content="IE=edge">
+	<meta name="viewport" content="width=device-width, initial-scale=1">
+	<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
+	<meta name="description" content="">
+	<meta name="author" content="">
+
+	<title>Sky 微服务统一认证</title>
+
+	<link href="/css/bootstrap.min.css" rel="stylesheet">
+	<link href="/css/signin.css" rel="stylesheet">
+</head>
+
+<body class="sign_body">
+<div class="container form-margin-top">
+	<form class="form-signin" action="/token/form" method="post">
+		<h2 class="form-signin-heading" align="center">统一认证系统</h2>
+		<input type="hidden" name="client_id" class="form-control" value="sky" placeholder="所属客户端">
+		<input type="hidden" name="grant_type" class="form-control" value="password" placeholder="所属客户端">
+		<input type="text" name="username" class="form-control form-margin-top" placeholder="账号" required autofocus>
+		<input type="password" name="password" class="form-control" placeholder="密码" required>
+		<button class="btn btn-lg btn-primary btn-block" type="submit">sign in</button>
+        <#if error??>
+			<span style="color: red; ">${error}</span>
+        </#if>
+	</form>
+</div>
+</body>
+</html>

+ 4 - 0
kxs-common/kxs-common-core/src/main/java/com/kxs/common/core/constant/SecurityConstants.java

@@ -117,4 +117,8 @@ public interface SecurityConstants {
 	 */
 	String CUSTOM_CONSENT_PAGE_URI = "/token/confirm_access";
 
+	/**
+	 * 项目的license
+	 */
+	String PROJECT_LICENSE = "";
 }

+ 5 - 0
kxs-common/kxs-common-mybatis/pom.xml

@@ -32,6 +32,11 @@
             <groupId>com.baomidou</groupId>
             <artifactId>mybatis-plus-extension</artifactId>
         </dependency>
+        <!--swagger  注解-->
+        <dependency>
+            <groupId>io.swagger.core.v3</groupId>
+            <artifactId>swagger-annotations-jakarta</artifactId>
+        </dependency>
         <!--server-api-->
         <dependency>
             <groupId>jakarta.servlet</groupId>

+ 1 - 2
kxs-common/kxs-common-security/src/main/java/com/kxs/common/security/annotation/EnableSkyResourceServer.java

@@ -18,8 +18,7 @@ import java.lang.annotation.*;
 @Inherited
 @Target({ ElementType.TYPE })
 @Retention(RetentionPolicy.RUNTIME)
-@Import({ SkyResourceServerAutoConfiguration.class, SkyResourceServerConfiguration.class,
-		SkyFeignClientConfiguration.class })
+@Import({ SkyResourceServerAutoConfiguration.class, SkyResourceServerConfiguration.class})
 public @interface EnableSkyResourceServer {
 
 }

+ 0 - 4
kxs-common/kxs-common-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@@ -1,8 +1,4 @@
-com.kxs.system.biz.security.KxsUserDetailsServiceImpl
-com.kxs.common.security.service.SkyAppUserDetailsServiceImpl
-com.kxs.common.security.service.SkyCustomUserDetailsServiceImpl
 com.kxs.common.security.service.KxsRedisOAuth2AuthorizationService
 com.kxs.common.security.service.KxsRedisOAuth2AuthorizationConsentService
 com.kxs.common.security.component.SkySecurityInnerAspect
 com.kxs.common.security.component.SkySecurityMessageSourceConfiguration
-com.kxs.system.biz.security.KxsRemoteRegisteredClientRepository

+ 1 - 0
kxs-common/pom.xml

@@ -21,6 +21,7 @@
         <module>kxs-common-security</module>
         <module>kxs-common-log</module>
         <module>kxs-common-mybatis</module>
+        <module>kxs-common-auth</module>
     </modules>
     <dependencyManagement>
         <dependencies>

+ 4 - 0
kxs-system/kxs-system-api/pom.xml

@@ -39,5 +39,9 @@
             <groupId>com.github.xiaoymin</groupId>
             <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springdoc</groupId>
+            <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
+        </dependency>
     </dependencies>
 </project>

+ 0 - 98
kxs-system/kxs-system-api/src/main/java/com/kxs/system/api/util/DictResolver.java

@@ -1,98 +0,0 @@
-package com.kxs.system.api.util;
-
-import cn.hutool.core.lang.Assert;
-import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
-import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
-import com.baomidou.mybatisplus.core.toolkit.StringPool;
-import com.baomidou.mybatisplus.core.toolkit.StringUtils;
-import com.kxs.common.core.util.SpringContextHolder;
-import com.kxs.system.api.model.SysDictItem;
-import lombok.experimental.UtilityClass;
-
-import java.util.List;
-
-/**
- * @author fxz
- * @date 2022/3/24 字典解析器
- */
-@UtilityClass
-public class DictResolver {
-
-	/**
-	 * 根据字典类型获取所有字典项
-	 * @param type 字典类型
-	 * @return 字典数据项集合
-	 */
-	public List<SysDictItem> getDictItemsByType(String type) {
-		Assert.isTrue(StringUtils.isNotBlank(type), "参数不合法");
-
-		RemoteDictService remoteDictService = SpringContextHolder.getBean(RemoteDictService.class);
-
-		return remoteDictService.getDictByType(type).getData();
-	}
-
-	/**
-	 * 根据字典类型以及字典项字典值获取字典标签
-	 * @param type 字典类型
-	 * @param itemValue 字典项字典值
-	 * @return 字典项标签值
-	 */
-	public String getDictItemLabel(String type, String itemValue) {
-		Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemValue), "参数不合法");
-
-		SysDictItem sysDictItem = getDictItemByItemValue(type, itemValue);
-
-		return ObjectUtils.isNotEmpty(sysDictItem) ? sysDictItem.getLabel() : StringPool.EMPTY;
-	}
-
-	/**
-	 * 根据字典类型以及字典标签获取字典值
-	 * @param type 字典类型
-	 * @param itemLabel 字典数据标签
-	 * @return 字典数据项值
-	 */
-	public String getDictItemValue(String type, String itemLabel) {
-		Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemLabel), "参数不合法");
-
-		SysDictItem sysDictItem = getDictItemByItemLabel(type, itemLabel);
-
-		return ObjectUtils.isNotEmpty(sysDictItem) ? sysDictItem.getItemValue() : StringPool.EMPTY;
-	}
-
-	/**
-	 * 根据字典类型以及字典值获取字典项
-	 * @param type 字典类型
-	 * @param itemValue 字典数据值
-	 * @return 字典数据项
-	 */
-	public SysDictItem getDictItemByItemValue(String type, String itemValue) {
-		Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemValue), "参数不合法");
-
-		List<SysDictItem> dictItemList = getDictItemsByType(type);
-
-		if (CollectionUtils.isNotEmpty(dictItemList)) {
-			return dictItemList.stream().filter(item -> itemValue.equals(item.getItemValue())).findFirst().orElse(null);
-		}
-
-		return null;
-	}
-
-	/**
-	 * 根据字典类型以及字典标签获取字典项
-	 * @param type 字典类型
-	 * @param itemLabel 字典数据项标签
-	 * @return 字典数据项
-	 */
-	public SysDictItem getDictItemByItemLabel(String type, String itemLabel) {
-		Assert.isTrue(StringUtils.isNotBlank(type) && StringUtils.isNotBlank(itemLabel), "参数不合法");
-
-		List<SysDictItem> dictItemList = getDictItemsByType(type);
-
-		if (CollectionUtils.isNotEmpty(dictItemList)) {
-			return dictItemList.stream().filter(item -> itemLabel.equals(item.getLabel())).findFirst().orElse(null);
-		}
-
-		return null;
-	}
-
-}

+ 0 - 60
kxs-system/kxs-system-api/src/main/java/com/kxs/system/api/util/ParamResolver.java

@@ -1,60 +0,0 @@
-package com.kxs.system.api.util;
-
-import cn.hutool.core.convert.Convert;
-import cn.hutool.core.util.StrUtil;
-import com.kxs.common.core.constant.SecurityConstants;
-import com.kxs.common.core.util.SpringContextHolder;
-import com.kxs.system.api.feign.RemoteParamService;
-import lombok.experimental.UtilityClass;
-
-/**
- * @author lengleng
- * @date 2020/5/12
- * <p>
- * 系统参数配置解析器
- */
-@UtilityClass
-public class ParamResolver {
-
-	/**
-	 * 根据key 查询value 配置
-	 * @param key key
-	 * @param defaultVal 默认值
-	 * @return value
-	 */
-	public Long getLong(String key, Long... defaultVal) {
-		return checkAndGet(key, Long.class, defaultVal);
-	}
-
-	/**
-	 * 根据key 查询value 配置
-	 * @param key key
-	 * @param defaultVal 默认值
-	 * @return value
-	 */
-	public String getStr(String key, String... defaultVal) {
-		return checkAndGet(key, String.class, defaultVal);
-	}
-
-	private <T> T checkAndGet(String key, Class<T> clazz, T... defaultVal) {
-		// 校验入参是否合法
-		if (StrUtil.isBlank(key) || defaultVal.length > 1) {
-			throw new IllegalArgumentException("参数不合法");
-		}
-
-		RemoteParamService remoteParamService = SpringContextHolder.getBean(RemoteParamService.class);
-
-		String result = remoteParamService.getByKey(key, SecurityConstants.FROM_IN).getData();
-
-		if (StrUtil.isNotBlank(result)) {
-			return Convert.convert(clazz, result);
-		}
-
-		if (defaultVal.length == 1) {
-			return Convert.convert(clazz, defaultVal.clone()[0]);
-
-		}
-		return null;
-	}
-
-}

+ 8 - 0
kxs-system/kxs-system-biz/pom.xml

@@ -33,14 +33,20 @@
             <groupId>com.pig4cloud.excel</groupId>
             <artifactId>excel-spring-boot-starter</artifactId>
         </dependency>
+        <!--日志处理-->
         <dependency>
             <groupId>com.kxs</groupId>
             <artifactId>kxs-common-log</artifactId>
         </dependency>
+        <!--安全模块-->
         <dependency>
             <groupId>com.kxs</groupId>
             <artifactId>kxs-common-security</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.kxs</groupId>
+            <artifactId>kxs-common-auth</artifactId>
+        </dependency>
         <!-- orm 模块-->
         <dependency>
             <groupId>com.baomidou</groupId>
@@ -50,9 +56,11 @@
             <groupId>com.mysql</groupId>
             <artifactId>mysql-connector-j</artifactId>
         </dependency>
+        <!--接口文档-->
         <dependency>
             <groupId>com.github.xiaoymin</groupId>
             <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
         </dependency>
+
     </dependencies>
 </project>

+ 21 - 0
kxs-system/kxs-system-biz/src/main/java/com/kxs/system/biz/AdminApplication.java

@@ -0,0 +1,21 @@
+package com.kxs.system.biz;
+
+import com.kxs.common.security.annotation.EnableSkyResourceServer;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author lengleng
+ * @date 2018年06月21日
+ * <p>
+ * 用户统一管理系统
+ */
+@EnableSkyResourceServer
+@SpringBootApplication
+public class AdminApplication {
+
+	public static void main(String[] args) {
+		SpringApplication.run(AdminApplication.class, args);
+	}
+
+}

+ 2 - 2
kxs-admin/src/main/java/com/kxs/admin/controller/SysUserController.java → kxs-system/kxs-system-biz/src/main/java/com/kxs/system/biz/controller/SysUserController.java

@@ -1,4 +1,4 @@
-package com.kxs.admin.controller;
+package com.kxs.system.biz.controller;
 
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -43,7 +43,7 @@ public class SysUserController {
 		String username = SecurityUtils.getUser().getUsername();
 		SysUser user = userService.getOne(Wrappers.<SysUser>query().lambda().eq(SysUser::getUsername, username));
 		if (user == null) {
-			return R.failed(MsgUtils.getMessage(ErrorCodes.SYS_USER_QUERY_ERROR));
+			return R.failed("用户不存在");
 		}
 		return R.ok(userService.findUserInfo(user));
 	}

+ 4 - 2
kxs-system/kxs-system-biz/src/main/java/com/kxs/system/biz/service/impl/SysUserServiceImpl.java

@@ -16,12 +16,12 @@ import com.kxs.common.security.util.SecurityUtils;
 import com.kxs.system.api.dto.UserDTO;
 import com.kxs.system.api.dto.UserInfo;
 import com.kxs.system.api.model.*;
-import com.kxs.system.api.util.ParamResolver;
 import com.kxs.system.api.vo.UserExcelVO;
 import com.kxs.system.api.vo.UserVO;
 import com.kxs.system.biz.mapper.SysUserMapper;
 import com.kxs.system.biz.mapper.SysUserRoleMapper;
 import com.kxs.system.biz.service.SysMenuService;
+import com.kxs.system.biz.service.SysPublicParamService;
 import com.kxs.system.biz.service.SysRoleService;
 import com.kxs.system.biz.service.SysUserService;
 import lombok.AllArgsConstructor;
@@ -61,6 +61,8 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
 
 	private final CacheManager cacheManager;
 
+	private final SysPublicParamService sysPublicParamService;
+
 	/**
 	 * 保存用户信息
 	 * @param userDto DTO 对象
@@ -78,7 +80,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
 		// 如果角色为空,赋默认角色
 		if (CollUtil.isEmpty(userDto.getRole())) {
 			// 获取默认角色编码
-			String defaultRole = ParamResolver.getStr("USER_DEFAULT_ROLE");
+			String defaultRole = sysPublicParamService.getSysPublicParamKeyToValue("USER_DEFAULT_ROLE");
 			// 默认角色
 			SysRole sysRole = sysRoleService
 				.getOne(Wrappers.<SysRole>lambdaQuery().eq(SysRole::getRoleCode, defaultRole));

+ 10 - 0
pom.xml

@@ -126,6 +126,11 @@
                 <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
                 <version>${springdoc.version}</version>
             </dependency>
+            <dependency>
+                <groupId>io.swagger.core.v3</groupId>
+                <artifactId>swagger-annotations-jakarta</artifactId>
+                <version>${swagger.core.version}</version>
+            </dependency>
             <!--orm 相关-->
             <dependency>
                 <groupId>com.baomidou</groupId>
@@ -165,6 +170,11 @@
                 <artifactId>kxs-common-security</artifactId>
                 <version>${project.version}</version>
             </dependency>
+            <dependency>
+                <groupId>com.kxs</groupId>
+                <artifactId>kxs-common-auth</artifactId>
+                <version>${project.version}</version>
+            </dependency>
             <dependency>
                 <groupId>com.kxs</groupId>
                 <artifactId>kxs-common-mybatis</artifactId>

Některé soubory nejsou zobrazeny, neboť je v těchto rozdílových datech změněno mnoho souborů