Spring授权服务器中使用refresh_token获取用户信息的问题
我基于Spring新授权服务器实现自定义认证服务,需求是通过CustomAccessTokenResponseHandler在返回的JSON响应中添加额外用户信息。目前已实现AuthenticationSuccessHandler并重写onAuthenticationSuccess方法完成密码模式下的信息补充,同时配置了自定义授权类型(含认证转换器、提供者、令牌)及TokenCustomizer为令牌添加额外声明。
但在refresh_token授权场景下出现异常:使用refresh_token请求新令牌时,未传入用户名密码,不启用自定义响应处理器时令牌可正常生成,启用后报错。当前Authentication对象包含访问令牌,但无法从其Principal中获取用户详情,且不想通过解码JWT(正在实现JWE编码器)的方式获取信息,仅需拿到用户名即可查询数据库补充响应。
报错代码
@Transactional public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = (OAuth2AccessTokenAuthenticationToken) authentication; OAuth2ClientAuthenticationToken oAuth2ClientAuthenticationToken = (OAuth2ClientAuthenticationToken) accessTokenAuthentication.getPrincipal(); CustomPasswordUser user = (CustomPasswordUser) oAuth2ClientAuthenticationToken.getDetails(); // ... 后续补充响应逻辑 }
报错原因
强转后获取的oAuth2ClientAuthenticationToken详情中无用户信息,结构如下:
OAuth2ClientAuthenticationToken [Principal=eQCLrL7JVHw1GRzP, Credentials=[PROTECTED], Authenticated=true, Details=WebAuthenticationDetails [RemoteIpAddress=0:0:0:0:0:0:0:1, SessionId=null], Granted Authorities=[]]
同时SecurityContextHolder.getContext().getAuthentication().getDetails()仅返回WebAuthenticationDetails,不含用户数据。
方案1:从OAuth2RefreshTokenAuthenticationToken获取原始用户认证信息
refresh_token流程中,当前Authentication实际是OAuth2RefreshTokenAuthenticationToken,内部包含了生成refresh_token时对应的原始OAuth2AccessTokenAuthenticationToken,可从中提取用户信息:
@Transactional public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { OAuth2AccessTokenAuthenticationToken accessTokenAuth; // 区分refresh_token模式与其他授权模式 if (authentication instanceof OAuth2RefreshTokenAuthenticationToken) { OAuth2RefreshTokenAuthenticationToken refreshTokenAuth = (OAuth2RefreshTokenAuthenticationToken) authentication; // 获取生成refresh_token时的原始访问令牌认证信息 accessTokenAuth = refreshTokenAuth.getAccessTokenAuthentication(); } else { // 处理密码模式等其他场景 accessTokenAuth = (OAuth2AccessTokenAuthenticationToken) authentication; } // 从原始认证信息中提取用户详情(需根据你自定义认证的实际存储结构调整) Authentication userAuth = accessTokenAuth.getPrincipal(); if (userAuth instanceof UsernamePasswordAuthenticationToken) { CustomPasswordUser user = (CustomPasswordUser) userAuth.getPrincipal(); // 用user对象补充响应信息 } }
方案2:通过OAuth2AuthorizationService查询refresh_token关联的用户信息
若原始认证信息未保留用户数据,可借助授权服务查询refresh_token对应的授权记录,从中获取用户名后查询数据库:
@Autowired private OAuth2AuthorizationService authorizationService; @Autowired private UserRepository userRepository; // 你的用户数据访问层 @Transactional public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String refreshToken = request.getParameter("refresh_token"); if (refreshToken != null) { // 根据refresh_token查询授权记录 OAuth2Authorization authorization = authorizationService.findByToken( refreshToken, OAuth2TokenType.REFRESH_TOKEN); if (authorization != null) { // 获取用户名,再查询用户详情 String username = authorization.getPrincipalName(); CustomPasswordUser user = userRepository.findByUsername(username); // 用user对象补充响应信息 } } else { // 处理非refresh_token模式的原有逻辑 OAuth2AccessTokenAuthenticationToken accessTokenAuth = (OAuth2AccessTokenAuthenticationToken) authentication; OAuth2ClientAuthenticationToken clientAuth = (OAuth2ClientAuthenticationToken) accessTokenAuth.getPrincipal(); CustomPasswordUser user = (CustomPasswordUser) clientAuth.getDetails(); // ... 后续逻辑 } }
内容的提问来源于stack exchange,提问作者Abhishek Mishra

