fix(auth): resolve DingTalk OAuth2 login failures

- Change SecurityConfig to @Configuration(proxyBeanMethods=false) to avoid
  CGLIB proxy issues with constructor-injected beans
- Add @Autowired to DingTalkOAuth2UserService public constructor so Spring
  resolves the correct constructor when multiple constructors exist
- Change DingTalk scope from openid to corpid: DingTalk does not return
  id_token in its token response, so openid scope causes Spring Security
  to fail with invalid_id_token error. corpid scope works correctly with
  DingTalk's authorization endpoint
- Add error logging to OAuth2LoginFailureHandler for easier debugging
- Add DingTalk client-id/client-secret env vars to application-local.yml

Signed-off-by: konglong87 <38234954+konglong87@users.noreply.github.com>
This commit is contained in:
konglong87 2026-06-02 16:24:42 +08:00
parent 0216cdfdee
commit d89fa32c2d
6 changed files with 15 additions and 6 deletions

View file

@ -22,6 +22,9 @@ spring:
github:
client-id: ${OAUTH2_GITHUB_CLIENT_ID:local-placeholder}
client-secret: ${OAUTH2_GITHUB_CLIENT_SECRET:local-placeholder}
dingtalk:
client-id: ${OAUTH2_DINGTALK_CLIENT_ID:local-placeholder}
client-secret: ${OAUTH2_DINGTALK_CLIENT_SECRET:local-placeholder}
skillhub:
auth:
@ -54,5 +57,4 @@ skillhub:
logging:
level:
com.iflytek.skillhub: INFO
org.springframework.security: WARN
com.iflytek.skillhub.auth: DEBUG

View file

@ -73,7 +73,7 @@ spring:
client-id: ${OAUTH2_DINGTALK_CLIENT_ID:placeholder}
client-secret: ${OAUTH2_DINGTALK_CLIENT_SECRET:placeholder}
scope:
- openid
- corpid
authorization-grant-type: authorization_code
redirect-uri: "{baseUrl}/login/oauth2/code/{registrationId}"
client-name: ${OAUTH2_DINGTALK_DISPLAY_NAME:钉钉}

View file

@ -48,7 +48,7 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
* Central Spring Security configuration for browser sessions, API tokens, and
* public versus protected endpoints.
*/
@Configuration
@Configuration(proxyBeanMethods = false)
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

View file

@ -18,6 +18,7 @@ import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@ -41,6 +42,7 @@ public class DingTalkOAuth2UserService implements OAuth2UserService<OAuth2UserRe
private final DingTalkClaimsExtractor claimsExtractor;
private final OAuthLoginFlowService oauthLoginFlowService;
@Autowired
public DingTalkOAuth2UserService(DingTalkClaimsExtractor claimsExtractor,
OAuthLoginFlowService oauthLoginFlowService) {
this.restTemplate = buildRestTemplate();

View file

@ -3,6 +3,8 @@ package com.iflytek.skillhub.auth.oauth;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;
@ -16,6 +18,8 @@ import java.io.IOException;
@Component
public class OAuth2LoginFailureHandler extends SimpleUrlAuthenticationFailureHandler {
private static final Logger log = LoggerFactory.getLogger(OAuth2LoginFailureHandler.class);
private final OAuthLoginFlowService oauthLoginFlowService;
public OAuth2LoginFailureHandler(OAuthLoginFlowService oauthLoginFlowService) {
@ -26,6 +30,7 @@ public class OAuth2LoginFailureHandler extends SimpleUrlAuthenticationFailureHan
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception)
throws IOException, ServletException {
log.error("OAuth2 login failed: type={}, message={}", exception.getClass().getSimpleName(), exception.getMessage(), exception);
String returnTo = oauthLoginFlowService.consumeReturnTo(request.getSession(false));
String redirectTarget = oauthLoginFlowService.resolveFailureRedirect(exception, returnTo);
if (redirectTarget != null) {

View file

@ -7,8 +7,8 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
import org.springframework.stereotype.Component;
/**
* OAuth2 authorization request resolver that preserves a sanitized post-login redirect target in
* the HTTP session.
* OAuth2 authorization request resolver that preserves a sanitized post-login
* redirect target in the HTTP session.
*/
@Component
public class SkillHubOAuth2AuthorizationRequestResolver