单点登录springcloud+mysql

https://github.com/lvan-jone/springcloudalibaba_sso_authcode/tree/mysql_sso

复制代码
单点登录mysql
springcloud2023.x+springboot3.x+mysql+nacos3.x

和上一篇单点登录差不多,也是springcloud2023版本,区别的数据储存从测试的内存存储改为mysql存储

auth项目下的

复制代码
AuthServerConfig.java
java 复制代码
package com.dp.auth.config;

import com.dp.auth.service.CustomUserDetailsService;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
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.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;

import javax.sql.DataSource;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * 认证服务器配置(新版 Spring Authorization Server)
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class AuthServerConfig {
    private final DataSource dataSource;
    private final CustomUserDetailsService customUserDetailsService;


    // ==================== 1. 用户认证配置(保持不变) ====================

    /**
     * 密码编码器. 使用 BCrypt 加密,存储时自动加盐
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 创建支持多种编码方式的密码编码器
        String defaultEncoding = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put("bcrypt", new BCryptPasswordEncoder());
        encoders.put("noop", NoOpPasswordEncoder.getInstance());
        DelegatingPasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(defaultEncoding, encoders);
        delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder());
        return delegatingPasswordEncoder;
    }

    /**
     * 用户详情服务
     * 定义测试用户:admin/123456, user/123456
     * 密码使用 BCrypt 加密
     */
//    @Bean
//    public UserDetailsService userDetailsService() {
//        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
//
//        // 密码: 123456 的 BCrypt 加密结果
//        UserDetails admin = User.withUsername("admin")
//                .password("$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi")
//                .roles("ADMIN", "USER")
//                .build();
//
//        UserDetails user = User.withUsername("user")
//                .password("$2a$10$N.zmdr9k7uOCQb376NoUnuTJ8iAt6Z5EHsM8lE9lBOsl7iKTVKIUi")
//                .roles("USER")
//                .build();
//
//        manager.createUser(admin);
//        manager.createUser(user);
//        return manager;
//    }
    @Bean
    public UserDetailsService userDetailsService() {
        return customUserDetailsService;
    }
    // ==================== 2. OAuth2 客户端配置(替代 AuthorizationServerConfig) ====================

    /**
     * 创建 JdbcTemplate 用于数据库操作
     */
    @Bean
    public JdbcOperations jdbcOperations() {
        return new JdbcTemplate(dataSource);
    }

    /**
     * 客户端注册信息仓库
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository(JdbcOperations jdbcOperations) {
        JdbcRegisteredClientRepository repository = new JdbcRegisteredClientRepository(jdbcOperations);

        // 检查是否已存在客户端,如果不存在则初始化默认客户端
        if (repository.findByClientId("gateway-client") == null) {
            // 创建默认客户端(实际生产环境应该通过 SQL 初始化)
            // 这里保留代码逻辑作为备选,但推荐使用 SQL 脚本初始化
        }
        return repository;
    }
//    @Bean
//    public RegisteredClientRepository registeredClientRepository() {
//        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
//        RegisteredClient gatewayClient = RegisteredClient.withId(UUID.randomUUID().toString())
//                .clientId("gateway-client")
//                .clientSecret(encoder.encode("gateway-secret"))
//                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
//                // 添加密码授权类型
//                .authorizationGrantType(AuthorizationGrantType.PASSWORD)  // 新增
//                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
//                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
//                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
//                .redirectUri("https://oauth.pstmn.io/v1/callback")
//                // 添加 admin scope
//                .scope("openid")
//                .scope("profile")
//                .scope("admin")  // 新增 admin scope
//                .clientSettings(ClientSettings.builder()
//                        .requireAuthorizationConsent(false)
//                        .build())
//                .tokenSettings(TokenSettings.builder()
//                        .accessTokenTimeToLive(Duration.ofHours(1))
//                        .refreshTokenTimeToLive(Duration.ofDays(7))
//                        .build())
//                .build();
//
//        return new InMemoryRegisteredClientRepository(gatewayClient);
//    }

    // ==================== 3. JWT 配置(替代 JwtAccessTokenConverter) ====================

    /**
     * OAuth2 授权记录服务(存储到 MySQL)
     */
    @Bean
    public OAuth2AuthorizationService authorizationService(JdbcOperations jdbcOperations,
                                                           RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationService(jdbcOperations, registeredClientRepository);
    }

    /**
     * OAuth2 授权确认服务(存储到 MySQL)
     */
    @Bean
    public OAuth2AuthorizationConsentService authorizationConsentService(JdbcOperations jdbcOperations,
                                                                         RegisteredClientRepository registeredClientRepository) {
        return new JdbcOAuth2AuthorizationConsentService(jdbcOperations, registeredClientRepository);
    }

    /**
     * JWT 密钥源 - 生成 RSA 密钥对
     * 替代旧版的 JwtAccessTokenConverter
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();

        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)
                .keyID(UUID.randomUUID().toString())
                .build();

        JWKSet jwkSet = new JWKSet(rsaKey);
        return new ImmutableJWKSet<>(jwkSet);
    }

    /**
     * 生成 RSA 密钥对
     * 用于 JWT 签名和验证
     */
    private static KeyPair generateRsaKey() {
        try {
            KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
            generator.initialize(2048);
            return generator.generateKeyPair();
        } catch (Exception ex) {
            throw new IllegalStateException(ex);
        }
    }

    /**
     * JWT 解码器
     * 用于验证 Token
     */
    @Bean
    public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
        return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
    }

    /**
     * Token 存储(新版使用 JwtDecoder,不需要单独的 TokenStore)
     * 注意:新版不需要 TokenStore Bean,JwtDecoder 已经处理
     */

    // ==================== 4. 安全过滤器链配置(替代 WebSecurityConfigurerAdapter) ====================

    /**
     * OAuth2 授权服务器安全配置(优先级最高)
     * 替代旧版的 AuthorizationServerSecurityConfigurer
     */
    @Bean
    @Order(1)
    public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
        // 应用默认的 OAuth2 授权服务器配置
        OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
        // 启用 OIDC 协议
        http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)
                .oidc(Customizer.withDefaults());
        // 启用表单登录(替代旧版的 allowFormAuthenticationForClients)
        return http.formLogin(Customizer.withDefaults()).build();
    }

    /**
     * 默认安全配置(优先级次之)
     * 替代旧版的 SecurityConfig 内部类
     * 对应旧版的 configure(HttpSecurity http) 方法
     */
    @Bean
    @Order(2)
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        RequestCache requestCache = new HttpSessionRequestCache();

        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/.well-known/**", "/favicon.ico", "/error").permitAll()
                        .anyRequest().authenticated()
                )
                .userDetailsService(customUserDetailsService)
                // 不指定 loginPage,使用 Spring Security 默认的登录页面
                .formLogin(form -> form
                        .successHandler((request, response, authentication) -> {
                            SavedRequest savedRequest = (SavedRequest) request.getSession()
                                    .getAttribute("SPRING_SECURITY_SAVED_REQUEST");

                            if (savedRequest != null) {
                                String targetUrl = savedRequest.getRedirectUrl();
                                response.sendRedirect(targetUrl);
                            } else {
                                response.sendRedirect("/");
                            }
                        })
                        .permitAll()
                )
                .logout(logout -> logout
                        .logoutSuccessUrl("/login?logout")
                        .permitAll()
                )
                .requestCache(cache -> cache.requestCache(requestCache));

        return http.build();
    }

    /**
     * 授权服务器设置
     * 配置授权服务器的基础 URL 和端点
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .issuer("http://localhost:9001")                    // 服务签发者
                .authorizationEndpoint("/oauth2/authorize")         // 授权端点(对应旧版的 /oauth/authorize)
                .tokenEndpoint("/oauth2/token")                     // Token 端点(对应旧版的 /oauth/token)
                .jwkSetEndpoint("/oauth2/jwks")                     // JWK 端点
                .oidcUserInfoEndpoint("/userinfo")                  // 用户信息端点
                .build();
    }
}
相关推荐
helx825 小时前
SpringBoot中自定义Starter
java·spring boot·后端
rleS IONS5 小时前
SpringBoot获取bean的几种方式
java·spring boot·后端
lifewange5 小时前
Go语言-开源编程语言
开发语言·后端·golang
白毛大侠6 小时前
深入理解 Go:用户态和内核态
开发语言·后端·golang
九皇叔叔6 小时前
003-SpringSecurity-Demo 统一响应类
java·javascript·spring·springsecurity
王码码20356 小时前
Go语言中的数据库操作:从sqlx到ORM
后端·golang·go·接口
星辰_mya7 小时前
雪花算法和时区的关系
数据库·后端·面试·架构师
计算机学姐8 小时前
基于SpringBoot的兴趣家教平台系统
java·spring boot·后端·spring·信息可视化·tomcat·intellij-idea
0xDevNull8 小时前
Java 11 新特性概览与实战教程
java·开发语言·后端