Spring OAuth2 Authorization Code流程:令牌无法关联登录用户问题
解决Spring Security OAuth2授权回调关联登录用户的问题
核心原因
你遇到的问题本质是:发起OAuth2授权请求时,没有将当前登录用户的身份与授权请求绑定,回调时Google的重定向请求无法携带原请求的认证信息(如Bearer Token),导致回调处理时无法识别原用户,最终令牌被关联到匿名用户。
解决方案
1. 自定义授权请求存储仓库
通过实现OAuth2AuthorizationRequestRepository,将授权请求与当前登录用户的认证信息绑定存储(推荐用会话或Redis,根据你的认证模式选择)。示例代码:
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRepository; import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.stereotype.Component; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @Component public class CustomAuthorizationRequestRepository implements OAuth2AuthorizationRequestRepository<OAuth2AuthorizationRequest> { private static final String AUTH_REQUEST_KEY = "oauth2_auth_request"; private static final String USER_AUTH_KEY = "linked_user_auth"; @Override public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest request) { return (OAuth2AuthorizationRequest) request.getSession().getAttribute(AUTH_REQUEST_KEY); } @Override public void saveAuthorizationRequest(OAuth2AuthorizationRequest authorizationRequest, HttpServletRequest request, HttpServletResponse response) { if (authorizationRequest == null) { request.getSession().removeAttribute(AUTH_REQUEST_KEY); request.getSession().removeAttribute(USER_AUTH_KEY); return; } // 保存授权请求 request.getSession().setAttribute(AUTH_REQUEST_KEY, authorizationRequest); // 绑定当前登录用户的认证信息 request.getSession().setAttribute(USER_AUTH_KEY, request.getUserPrincipal()); } @Override public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest request, HttpServletResponse response) { OAuth2AuthorizationRequest authRequest = this.loadAuthorizationRequest(request); this.saveAuthorizationRequest(null, request, response); return authRequest; } // 获取绑定的用户认证信息 public Object getLinkedUserAuthentication(HttpServletRequest request) { return request.getSession().getAttribute(USER_AUTH_KEY); } }
2. 配置SecurityFilterChain注入自定义仓库
修改安全配置,将自定义仓库注入OAuth2授权流程,并在回调时取出绑定的用户信息,完成令牌与用户的关联:
import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationCodeGrantFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, CustomAuthorizationRequestRepository authRequestRepo) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .oauth2Client(oauth2 -> oauth2 .authorizationCodeGrant(codeGrant -> codeGrant // 设置自定义授权请求仓库 .authorizationRequestRepository(authRequestRepo) // 自定义回调处理逻辑 .authorizationResponseHandler((request, response, authorizationExchange) -> { // 取出绑定的原登录用户认证信息 Object userAuth = authRequestRepo.getLinkedUserAuthentication(request); if (userAuth != null) { // 将令牌与用户关联存储(替换成你的存储逻辑) OAuth2Authorization authorization = authorizationExchange.getAuthorization(); // saveTokenToDatabase(userAuth.getName(), authorization.getAccessToken(), authorization.getRefreshToken()); } // 执行默认回调处理 new OAuth2AuthorizationCodeGrantFilter.DefaultAuthorizationResponseHandler().handle(request, response, authorizationExchange); }) ) ); return http.build(); }
3. 调整回调地址权限配置
删除原来的.requestMatchers(GOOGLE_AUTH_CALLBACK).permitAll()配置:
- 如果是会话认证模式:回调请求会自动携带会话Cookie,Spring Security能识别当前用户,无需开放匿名访问
- 如果是JWT无状态模式:不要用会话存储,改用Redis等分布式存储,将
state作为唯一key,存储授权请求和用户JWT信息,回调时通过state参数取出用户信息重建认证
关键注意事项
- 会话模式下:确保回调地址与服务同域名,或配置Cookie的
SameSite=None+Secure属性(跨域名场景) - 无状态模式下:注意
state参数的长度限制,建议用state作为Redis的key,存储用户JWT和授权请求信息,并设置合理的过期时间 - 存储的授权请求和用户信息要及时清理,避免资源浪费
内容的提问来源于stack exchange,提问作者Myroslava Dzikovska
相关产品推荐
相关产品推荐

