基于XML配置的Spring MVC应用迁移spring-authorization-server的配置问题
一、同一端口配置授权服务器与资源服务器
1. 修正授权服务器SecurityFilterChain配置
你当前配置中,exceptionHandling设置了LoginUrlAuthenticationEntryPoint,会导致客户端访问令牌端点时直接跳转到登录页,而客户端认证需要使用专属的OAuth2AuthenticationEntryPoint。另外,必须调用.apply(authorizationServerConfigurer)加载授权服务器核心过滤器,否则端点无法被正确处理。
修正后的配置:
@Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = OAuth2AuthorizationServerConfigurer.authorizationServer(); // 兼容旧端点/api/security/oauth/token,与授权服务器默认端点合并匹配 RequestMatcher endpointsMatcher = new OrRequestMatcher( authorizationServerConfigurer.getEndpointsMatcher(), AntPathRequestMatcher.antMatcher("/api/security/oauth/token") ); http .securityMatcher(endpointsMatcher) .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()) .csrf(csrf -> csrf.disable()) // 替换为客户端认证入口,避免跳转到登录页 .exceptionHandling(exceptions -> exceptions .authenticationEntryPoint(new OAuth2AuthenticationEntryPoint()) ) .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())) .apply(authorizationServerConfigurer); // 加载授权服务器核心配置 return http.build(); }
2. 添加资源服务器SecurityFilterChain
创建优先级更低的FilterChain,专门处理资源API请求(如/api/**),实现同一端口下授权与资源服务共存:
@Bean @Order(2) public SecurityFilterChain resourceServerSecurityFilterChain(HttpSecurity http) throws Exception { http .securityMatcher("/api/**") .authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()) .oauth2ResourceServer(oauth2 -> // 若继续使用自定义tokenServices,配置不透明令牌校验 oauth2.opaqueToken(token -> token.introspector(tokenStr -> { // 调用原customTokenServices验证令牌并返回认证信息 return customTokenServices.loadAuthentication(tokenStr); }) ) // 若改用JWT令牌,替换为:oauth2.jwt(Customizer.withDefaults()) ); return http.build(); }
3. 客户端详情与令牌服务配置
替换原XML中的customClientDetailsService和customTokenServices,示例如下:
@Bean public ClientDetailsService clientDetailsService() { // 复用原有客户端逻辑,此处以内存实现为例 InMemoryClientDetailsService clientService = new InMemoryClientDetailsService(); ClientRegistration client = ClientRegistration.withRegistrationId("your-client-id") .clientId("your-client-id") .clientSecret("{noop}your-client-secret") // 生产环境需加密存储 .authorizationGrantType(AuthorizationGrantType.PASSWORD) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .scope("read", "write") .build(); clientService.saveClient(client); return clientService; } // 自定义令牌生成器(若需兼容原有令牌格式,可替换为自定义TokenServices) @Bean public OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator() { JwtGenerator jwtGenerator = new JwtGenerator(jwtEncoder()); OAuth2AccessTokenGenerator accessTokenGenerator = new OAuth2AccessTokenGenerator(); OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator(); return new DelegatingOAuth2TokenGenerator(jwtGenerator, accessTokenGenerator, refreshTokenGenerator); } // JWT编码器配置(使用JWT令牌时需要) @Bean public JwtEncoder jwtEncoder() { KeyPair rsaKeyPair = KeyPairGeneratorUtils.generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) rsaKeyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) rsaKeyPair.getPrivate(); RSAKey jwk = new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk)); return new NimbusJwtEncoder(jwkSource); }
二、访问/api/oauth生成访问令牌的流程(以密码模式为例)
假设已兼容旧端点/api/security/oauth/token,流程如下:
客户端发起令牌请求
通过POST请求发送x-www-form-urlencoded格式参数到/api/security/oauth/token:grant_type=password:指定授权类型为密码模式username=用户账号password=用户密码client_id=客户端IDclient_secret=客户端密钥scope=read write:请求的权限范围
客户端身份校验
服务器先验证客户端的client_id和client_secret,校验失败返回401 Unauthorized。用户身份校验
客户端认证通过后,服务器使用用户账号密码调用AuthenticationManager完成身份验证,校验失败返回400 Bad Request。生成并返回令牌
用户身份验证通过后,服务器根据配置的令牌生成器生成访问令牌(Access Token)和刷新令牌(Refresh Token),返回JSON格式响应:{ "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "O3t8y...", "scope": "read write" }
若使用客户端凭证模式,只需将grant_type设为client_credentials,无需用户账号密码,直接验证客户端身份后生成令牌;若使用授权码模式,需先引导用户访问/oauth2/authorize完成授权获取授权码,再用授权码换取令牌。
内容的提问来源于stack exchange,提问作者hellokk

