Spring security Spring Oauth2客户端,自动刷新过期的访问令牌

Spring security Spring Oauth2客户端,自动刷新过期的访问令牌,spring-security,oauth-2.0,spring-security-oauth2,spring-oauth2,Spring Security,Oauth 2.0,Spring Security Oauth2,Spring Oauth2,让我解释一下我的用例 我需要一个SpringBootOAuth2客户机应用程序(不是资源服务器,因为我们已经有了一个单独的资源服务器)。我还有以下要求: 对于每个发送到资源服务器的请求,我们需要发送id_令牌。(通过自定义resttemplate完成) 对于任何请求,无论是否调用资源服务器,如果访问令牌过期,我的应用程序必须自动刷新它(无需任何用户干预,如弹出或重定向)。 如果刷新令牌也已过期,则用户必须注销 问题: 对于第2点和第3点,我花了很多时间阅读文档、代码和堆栈溢出,但未能找到解决方案

让我解释一下我的用例

我需要一个SpringBootOAuth2客户机应用程序(不是资源服务器,因为我们已经有了一个单独的资源服务器)。我还有以下要求:

  • 对于每个发送到资源服务器的请求,我们需要发送id_令牌。(通过自定义resttemplate完成)

  • 对于任何请求,无论是否调用资源服务器,如果访问令牌过期,我的应用程序必须自动刷新它(无需任何用户干预,如弹出或重定向)。

  • 如果刷新令牌也已过期,则用户必须注销

  • 问题:

    对于第2点和第3点,我花了很多时间阅读文档、代码和堆栈溢出,但未能找到解决方案(或不理解)。所以我决定把我在许多博客和文档中找到的所有片段放在一起,并提出我的解决方案。下面是我对第2点的解决方案

  • 我们可以看一下下面的代码,并建议这种方法是否有任何问题吗

  • 如何解决第三点我正在考虑扩展第二点的解决方案,但不确定我需要编写什么代码,有人能指导我吗
  • /**
    * 
    *@作者阿甘
    *
    */
    @组成部分
    公共类ExpiredTokenFilter扩展OncePerRequestFilter{
    私有静态最终记录器log=LoggerFactory.getLogger(ExpiredTokenFilter.class);
    私有持续时间accessTokenExpiresSkew=持续时间(百万分之1000);
    专用时钟=Clock.systemUTC();
    @自动连线
    专用OAuth2AuthorizedClient服务OAuth2AuthorizedClient服务;
    @自动连线
    CustomOidcUserService用户服务;
    私有DefaultRefreshTokenResponseClient访问TokenResponseClient;
    私人JwtDecoderFactory JwtDecoderFactory;
    私有静态最终字符串无效\u ID\u令牌\u错误\u CODE=“无效\u ID\u令牌”;
    公共过期TokenFilter(){
    超级();
    this.accessTokenResponseClient=新的DefaultRefreshTokenResponseClient();
    this.jwtDecoderFactory=新的OidcIdTokenDecoderFactory();
    }
    @凌驾
    受保护的void doFilterInternal(HttpServletRequest请求、HttpServletResponse响应、FilterChain FilterChain)
    抛出ServletException、IOException{
    调试(“我的自定义过滤器被调用”);
    /**
    *检查身份验证是否完成。
    */
    if(null!=SecurityContextHolder.getContext().getAuthentication()){
    OAuth2AuthenticationToken currentUser=(OAuth2AuthenticationToken)SecurityContextHolder.getContext()
    .getAuthentication();
    OAuth2AuthorizedClient authorizedClient=this.OAuth2AuthorizedClient服务
    .loadAuthorizedClient(currentUser.GetAuthorizedClient注册ID(),currentUser.getName());
    /**
    *检查现有令牌是否过期。
    */
    if(isExpired(authorizedClient.getAccessToken()){
    /*
    *做一些事情来获取新的访问令牌
    */
    log.debug(
    “=======================================================令牌已过期!!要刷新===========================================================”;
    ClientRegistration ClientRegistration=authorizedClient.getClientRegistration();
    /*
    *调用身份验证服务器令牌端点以刷新令牌。
    */
    OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest=新OAuth2RefreshTokenGrantRequest(
    clientRegistration、authorizedClient.getAccessToken()、authorizedClient.getRefreshToken());
    OAuth2AccessTokenResponse accessTokenResponse=this.accessTokenResponseClient
    .getTokenResponse(刷新令牌请求);
    /*
    *将id_令牌转换为OidcToken。
    */
    OidcIdToken idToken=createOidcToken(clientRegistration,accessTokenResponse);
    /*
    *因为我已经实现了自定义的OidcUserService,所以重用现有的
    *获取新用户的代码。
    */
    OidcUser OidcUser=this.userService.loadUser(新的OidcUserRequest(clientRegistration,
    accessTokenResponse.getAccessToken()、idToken、accessTokenResponse.getAdditionalParameters());
    log.debug(
    “=============================================================================================================”;
    /*
    *打印新旧id_令牌,以防万一。
    */
    DefaultOidcUser user=(DefaultOidcUser)currentUser.getPrincipal();
    debug(“新id标记为”+oidcUser.getIdToken().getTokenValue());
    debug(“旧id标记为”+user.getIdToken().getTokenValue());
    /*
    *创建新的身份验证(OAuth2AuthenticationToken)。
    */
    OAuth2AuthenticationToken UpdateUser=新的OAuth2AuthenticationToken(oidcUser,
    oidcUser.GetAuthories(),currentUser.GetAuthorizedClient注册ID();
    /*
    *通过保存新的授权客户端来更新访问\u令牌和刷新\u令牌。
    */
    OAuth2AuthorizedClient updatedAuthorizedClient=新的OAuth2AuthorizedClient(clientRegistration,
    currentUser.getName(),accessTokenResponse.getAccessToken(),
    accessTokenResponse.getRefreshToken());
    this.oAuth2AuthorizedClientService.saveAuthorizedClient(updateauthorizedclient,updateuser);
    /*
    *在SecurityContextHolder中设置新身份验证。
    */
    SecurityContextHolder.getContext().setAuthen
    
    /**
     * 
     * @author agam
     *
     */
    @Component
    public class ExpiredTokenFilter extends OncePerRequestFilter {
    
        private static final Logger log = LoggerFactory.getLogger(ExpiredTokenFilter.class);
    
        private Duration accessTokenExpiresSkew = Duration.ofMillis(1000);
    
        private Clock clock = Clock.systemUTC();
    
        @Autowired
        private OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
    
        @Autowired
        CustomOidcUserService userService;
    
        private DefaultRefreshTokenTokenResponseClient accessTokenResponseClient;
    
        private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory;
    
        private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
    
        public ExpiredTokenFilter() {
            super();
            this.accessTokenResponseClient = new DefaultRefreshTokenTokenResponseClient();
            this.jwtDecoderFactory = new OidcIdTokenDecoderFactory();
        }
    
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
            log.debug("my custom filter called ");
            /**
             * check if authentication is done.
             */
            if (null != SecurityContextHolder.getContext().getAuthentication()) {
                OAuth2AuthenticationToken currentUser = (OAuth2AuthenticationToken) SecurityContextHolder.getContext()
                        .getAuthentication();
                OAuth2AuthorizedClient authorizedClient = this.oAuth2AuthorizedClientService
                        .loadAuthorizedClient(currentUser.getAuthorizedClientRegistrationId(), currentUser.getName());
                /**
                 * Check if token existing token is expired.
                 */
                if (isExpired(authorizedClient.getAccessToken())) {
    
                    /*
                     * do something to get new access token
                     */
                    log.debug(
                            "=========================== Token Expired !! going to refresh ================================================");
                    ClientRegistration clientRegistration = authorizedClient.getClientRegistration();
                    /*
                     * Call Auth server token endpoint to refresh token. 
                     */
                    OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(
                            clientRegistration, authorizedClient.getAccessToken(), authorizedClient.getRefreshToken());
                    OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseClient
                            .getTokenResponse(refreshTokenGrantRequest);
                    /*
                     * Convert id_token to OidcToken.
                     */
                    OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
                    /*
                     * Since I have already implemented a custom OidcUserService, reuse existing
                     * code to get new user. 
                     */
                    OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(clientRegistration,
                            accessTokenResponse.getAccessToken(), idToken, accessTokenResponse.getAdditionalParameters()));
    
                    log.debug(
                            "=========================== Token Refresh Done !! ================================================");
                    /*
                     * Print old and new id_token, just in case.
                     */
                    DefaultOidcUser user = (DefaultOidcUser) currentUser.getPrincipal();
                    log.debug("new id token is " + oidcUser.getIdToken().getTokenValue());
                    log.debug("old id token was " + user.getIdToken().getTokenValue());
                    /*
                     * Create new authentication(OAuth2AuthenticationToken).
                     */
                    OAuth2AuthenticationToken updatedUser = new OAuth2AuthenticationToken(oidcUser,
                            oidcUser.getAuthorities(), currentUser.getAuthorizedClientRegistrationId());
                    /*
                     * Update access_token and refresh_token by saving new authorized client.
                     */
                    OAuth2AuthorizedClient updatedAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration,
                            currentUser.getName(), accessTokenResponse.getAccessToken(),
                            accessTokenResponse.getRefreshToken());
                    this.oAuth2AuthorizedClientService.saveAuthorizedClient(updatedAuthorizedClient, updatedUser);
                    /*
                     * Set new authentication in SecurityContextHolder.
                     */
                    SecurityContextHolder.getContext().setAuthentication(updatedUser);
                }
    
            }
            filterChain.doFilter(request, response);
        }
    
        private Boolean isExpired(OAuth2AccessToken oAuth2AccessToken) {
            Instant now = this.clock.instant();
            Instant expiresAt = oAuth2AccessToken.getExpiresAt();
            return now.isAfter(expiresAt.minus(this.accessTokenExpiresSkew));
        }
    
        private OidcIdToken createOidcToken(ClientRegistration clientRegistration,
                OAuth2AccessTokenResponse accessTokenResponse) {
            JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
            Jwt jwt;
            try {
                jwt = jwtDecoder
                        .decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
            } catch (JwtException ex) {
                OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(), null);
                throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), ex);
            }
            OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(),
                    jwt.getClaims());
            return idToken;
        }
    }
    
    @Bean
    public WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientService authorizedClientService) {
    
        AuthorizedClientServiceOAuth2AuthorizedClientManager manager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientService);
        manager.setAuthorizedClientProvider(new DelegatingOAuth2AuthorizedClientProvider(
                new RefreshTokenOAuth2AuthorizedClientProvider(),
                new ClientCredentialsOAuth2AuthorizedClientProvider()));
    
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = new ServletOAuth2AuthorizedClientExchangeFilterFunction(manager);
    
        oauth2.setDefaultClientRegistrationId("your-client-registratioin-id");
    
        return WebClient.builder()
                .filter(oauth2)
                .apply(oauth2.oauth2Configuration())
                .build();
    }
    
    @Autowire
    private final WebClient webClient;
    
    ...
    
    webClient.get()
        .uri("http://localhost:8081/api/message")
                .retrieve()
                .bodyToMono(String.class)
                .map(string -> "Retrieved using password grant: " + string)
                .subscribe(log::info);