security-oauth2自定义登录页面和校验规则

引用依赖

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-authorization-server</artifactId>
            <version>0.4.2</version>
        </dependency>

SecurityConfig

/**
 * 安全配置类
 * 配置应用程序的基本安全策略,包括请求授权规则和用户认证服务
 */
@Configuration
@EnableWebSecurity
@Slf4j
public class SecurityConfig {

    private final CustomAuthenticationProvider customAuthenticationProvider;

    private final CustomUserDetailsService customUserDetailsService;

    public SecurityConfig(CustomAuthenticationProvider customAuthenticationProvider, CustomUserDetailsService customUserDetailsService) {
        this.customAuthenticationProvider = customAuthenticationProvider;
        this.customUserDetailsService = customUserDetailsService;
        log.info("初始化 Security 配置");
    }

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http,
                                                                  CustomUserDetailsService customUserDetailsService,
                                                                  RegisteredClientRepository registeredClientRepository) throws Exception {

    // 应用 OAuth2 授权服务器默认安全配置
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);

    // 设置用户详情服务
    http.userDetailsService(customUserDetailsService);

    // 共享 RegisteredClientRepository
    http.setSharedObject(RegisteredClientRepository.class, registeredClientRepository);

    // ===== 关键修改:覆盖默认的表单登录配置 =====
    http.formLogin(form -> form
                    .loginPage("/auth/login")          // 自定义登录页
                    .loginProcessingUrl("/server-login") // 自定义登录处理 URL
                    .permitAll()
    );

    // ===== 添加你的自定义认证过滤器 =====
    // 注意:必须替换掉默认的 UsernamePasswordAuthenticationFilter
    http.addFilterAt(customUsernamePasswordAuthenticationFilter(),
            UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

    // ===== 2. 自定义过滤器 Bean =====
    @Bean
    public CustomUsernamePasswordAuthenticationFilter customUsernamePasswordAuthenticationFilter() {
        CustomUsernamePasswordAuthenticationFilter filter = new CustomUsernamePasswordAuthenticationFilter();
        filter.setFilterProcessesUrl("/server-login");
        filter.setAuthenticationManager(authenticationManager());
        return filter;
    }

    // ===== 3. 认证管理器(供自定义过滤器使用)=====
    @Bean
    public AuthenticationManager authenticationManager() {
        // 创建ProviderManager,包含自定义的AuthenticationProvider
        return new ProviderManager(customAuthenticationProvider);
    }

    // ===== 4. 资源服务器 FilterChain(低优先级)=====
    @Bean
    @Order(1)
    public SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorizeRequests -> authorizeRequests
                        .requestMatchers(new AntPathRequestMatcher("/userInfo")).authenticated()
                )
                .csrf(csrf -> csrf.disable())
                .oauth2ResourceServer(oauth2 -> oauth2
                        .jwt(Customizer.withDefaults())
                );
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // 支持 {noop}, {bcrypt}, {scrypt} 等多种格式
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

AuthorizationServerConfig

/**
 * 配置类注解,标识此类为配置类,其中包含Bean定义
 */
@Configuration
public class AuthorizationServerConfig {


    /**
     * 创建JWK(JSON Web Key)源
     * 用于JWT签名和验证的密钥源
     * @return JWK源实例
     */
    @Bean
    public JWKSource<SecurityContext> jwkSource() {
        // 生成RSA密钥对
        KeyPair keyPair = generateRsaKey();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 构建RSA密钥对象
        RSAKey rsaKey = new RSAKey.Builder(publicKey)
                .privateKey(privateKey)  // 设置私钥
                .keyID(UUID.randomUUID().toString())  // 设置密钥ID
                .build();
        // 创建JWK集合
        JWKSet jwkSet = new JWKSet(rsaKey);
        // 返回不可变的JWK源
        return new ImmutableJWKSet<>(jwkSet);
    }

    /**
     * 生成RSA密钥对
     * 用于JWT签名的加密密钥
     * @return 密钥对对象
     */
    private static KeyPair generateRsaKey() {
        KeyPair keyPair;
        try {
            // 获取RSA密钥生成器实例
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
            // 初始化密钥长度为2048位
            keyPairGenerator.initialize(2048);
            // 生成密钥对
            keyPair = keyPairGenerator.generateKeyPair();
        } catch (Exception ex) {
            // 如果密钥生成失败,抛出非法状态异常
            throw new IllegalStateException(ex);
        }
        return keyPair;
    }

    /**
     * 创建客户端注册仓库
     * 存储和管理OAuth2客户端的信息
     * @return 客户端注册仓库实例
     */
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        // 创建消息客户端配置
        RegisteredClient messagingClient = RegisteredClient.withId(UUID.randomUUID().toString())
                .clientId("20210903")  // 客户端ID
                .clientSecret("{noop}cas123456")  // 客户端密钥
                .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)  // 客户端认证方式
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)  // 授权类型:授权码模式
                .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)  // 授权类型:刷新令牌
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)  // 授权类型:客户端凭证
                .redirectUri("http://127.0.0.1:8082/client/callbackCode")  // 重定向URI
                .scope("openid")  // 授权范围
                .scope("message:read")  // 授权范围
                .scope("message:write")  // 授权范围
                .clientSettings(ClientSettings.builder().requireAuthorizationConsent(false).build())  // 客户端设置
                .build();

        // 返回内存中的客户端注册仓库,包含两个客户端
        return new InMemoryRegisteredClientRepository(messagingClient);
    }

    /**
     * 创建授权服务器设置
     * 配置授权服务器的基本设置,如发行者URL
     * @return 授权服务器设置实例
     */
    @Bean
    public AuthorizationServerSettings authorizationServerSettings() {
        return AuthorizationServerSettings.builder()
                .issuer("http://127.0.0.1:8080")  // 设置发行者URL
                .build();
    }

}

自定义认证功能

@Component
@Slf4j
public class CustomAuthenticationProvider implements AuthenticationProvider {

    private final LoginService loginService;

    public CustomAuthenticationProvider(LoginService loginService) {
        this.loginService = loginService;
    }


    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        if (!supports(authentication.getClass())) {
            return null; // 让其他 Provider 尝试
        }

        // 强制转换为自定义的 Token
        CustomAuthenticationToken customAuthRequest = (CustomAuthenticationToken) authentication;

        String username = customAuthRequest.getName();
        String password = StrUtil.toString(customAuthRequest.getCredentials());
        String code = customAuthRequest.getCode();
        String verifyToken = customAuthRequest.getVerifyToken();
        log.info("CustomAuthenticationProvider authenticate username: {}, password: {}, code: {}, verifyToken: {}", username, password, code, verifyToken);
		//自定义校验规则
        return new CustomAuthenticationToken(username, password, java.util.Collections.emptyList(), code, verifyToken);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        // 指定此 Provider 支持哪种类型的 Authentication
        return CustomAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

自定义认证令牌

@EqualsAndHashCode(callSuper = true)
@Data
public class CustomAuthenticationToken extends UsernamePasswordAuthenticationToken {

    private final String code;
    private final String verifyToken;

    public CustomAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities,
                                     String code, String verifyToken) {
        super(principal, credentials, authorities);
        this.code = code;
        this.verifyToken = verifyToken;
    }
}

自定义拦截器

public class CustomUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private final String code = "code";

    private final String verifyToken = "verifyToken";

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        String username = obtainUsername(request);
        String password = obtainPassword(request);
        String code = request.getParameter(this.code);
        String verifyToken = request.getParameter(this.verifyToken);
        CustomAuthenticationToken token = new CustomAuthenticationToken(username, password, CollUtil.newArrayList(), code, verifyToken);
        setDetails(request, token);
        return this.getAuthenticationManager().authenticate(token);
    }
}

自定义登录页面

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>用户登录</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f5f5f5;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        .login-container {
            background: white;
            padding: 30px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            width: 350px;
        }
        .login-container h2 {
            text-align: center;
            margin-bottom: 20px;
            color: #333;
        }
        .form-group {
            margin-bottom: 15px;
        }
        .form-group label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
            color: #555;
        }
        .form-group input {
            width: 100%;
            padding: 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            box-sizing: border-box;
        }
        .btn-login {
            width: 100%;
            padding: 10px;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
        }
        .btn-login:hover {
            background-color: #0069d9;
        }
        .error-message {
            color: red;
            text-align: center;
            margin-top: 10px;
        }
    </style>
</head>
<body>

<div class="login-container">
    <h2>用户登录</h2>

    <!-- 错误信息显示(由 Spring Security 重定向时带 error 参数) -->
    <div class="error-message" id="errorMessage" style="display:none;">
        用户名或密码错误,请重试。
    </div>

    <!-- 登录表单:关键!method="post",action="/server-login" -->
    <form action="/server-login" method="post">
        <!-- 用户名 -->
        <div class="form-group">
            <label for="username">用户名</label>
            <input type="text" id="username" name="username" required />
        </div>

        <!-- 密码 -->
        <div class="form-group">
            <label for="password">密码</label>
            <input type="password" id="password" name="password" required />
        </div>

        <!-- 手机号(可选) -->
        <div class="form-group">
            <label for="phone">手机号</label>
            <input type="text" id="phone" name="phone" placeholder="可选" />
        </div>

        <!-- 验证码 -->
        <div class="form-group">
            <label for="code">验证码</label>
            <input type="text" id="code" name="code" placeholder="请输入验证码" required />
        </div>

        <!-- verifyToken(通常由后端生成并写入页面) -->
        <input type="hidden" id="verifyToken" name="verifyToken" value="YOUR_VERIFY_TOKEN_HERE" />

        <!-- 提交按钮 -->
        <button type="submit" class="btn-login">登录</button>
    </form>
</div>

<script>
    // 可选:如果 verifyToken 是动态的(比如从 cookie 或 URL 获取),可以用 JS 设置
    // 示例:从 URL 参数获取 verifyToken
    const urlParams = new URLSearchParams(window.location.search);
    const tokenFromUrl = urlParams.get('verifyToken');
    if (tokenFromUrl) {
        document.getElementById('verifyToken').value = tokenFromUrl;
    }

    // 显示错误信息(如果 URL 中有 error 参数)
    if (urlParams.has('error')) {
        document.getElementById('errorMessage').style.display = 'block';
    }
</script>

</body>
</html>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值