如何通过受 Keycloak 保护的 Spring restTemplate 调用 rest 端点

How to call rest endpoint via Spring restTemplate that is protected by Keycloak

先决条件

我有两个 Java Spring 应用程序(App 'A' 和 App 'B')是通过 JHipster(单体应用程序)创建的。这两个应用程序都为 authentication/authorization 使用 keycloak。 这两个应用程序都有一个 angular 前端并支持通过 ouath (spring-security) 登录。这是我的 SecurityConfiguration 申请 A 和 B:

@Configuration
@Import(SecurityProblemSupport.class)
@EnableOAuth2Sso
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    private final CorsFilter corsFilter;

    private final SecurityProblemSupport problemSupport;

    public SecurityConfiguration(CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
        this.corsFilter = corsFilter;
        this.problemSupport = problemSupport;
    }

    @Bean
    public AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler() {
        return new AjaxLogoutSuccessHandler();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
        return new SecurityEvaluationContextExtension();
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
            .antMatchers(HttpMethod.OPTIONS, "/**")
            .antMatchers("/app/**/*.{js,html}")
            .antMatchers("/i18n/**")
            .antMatchers("/content/**")
            .antMatchers("/swagger-ui/index.html")
            .antMatchers("/test/**")
            .antMatchers("/h2-console/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
            .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
        .and()
            .addFilterBefore(corsFilter, CsrfFilter.class)
            .exceptionHandling()
            .authenticationEntryPoint(problemSupport)
            .accessDeniedHandler(problemSupport)
        .and()
            .logout()
            .logoutUrl("/api/logout")
            .logoutSuccessHandler(ajaxLogoutSuccessHandler())
            .permitAll()
        .and()
            .headers()
            .frameOptions()
            .disable()
        .and()
            .authorizeRequests()
            .antMatchers("/api/profile-info").permitAll()
            .antMatchers("/api/**").authenticated()
            .antMatchers("/management/health").permitAll()
            .antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
            .antMatchers("/v2/api-docs/**").permitAll()
            .antMatchers("/swagger-resources/configuration/ui").permitAll()
            .antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN);
    }


}

在 App B 中我也有一个 ResourceServerConfiguration。这将检查 header 是否包含 "Authorization" 键。如果为 true,则用户可以通过 JWT(Bearer Authentication)登录。我通过 Postman 对此进行了测试,它工作正常:

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(final HttpSecurity http) throws Exception {
        http
            .requestMatcher(new RequestHeaderRequestMatcher("Authorization")).authorizeRequests()
            .anyRequest().authenticated();
    }

}

此外,这两个应用程序都在同一个 keycloak 领域并且具有 access-type "public".

问题:

现在我想通过应用程序 A 的 Spring RestTemplate 调用应用程序 B 的端点。问题是,我没有 access_token 可以放在我的休息中 request/restTemplate。当我查看从前端发送的请求时,我只有一个 JSESSIONID。 header中没有access_token/JWT。

问题

有没有办法让当前用户的 access_token 脱离 JSESSIONID/the HttpSession 或 spring 安全上下文?我是否需要像 Tokenstore 这样的东西来存储来自 keycloak 的每个令牌?

有没有其他人遇到过类似的问题或者知道我该如何解决这个问题?

经过一些研究发现问题出在生成的 jhipster 代码中。 我按照应用程序中的身份验证过程看到,身份验证后直接调用了 /account 端点,从中检索了用户信息。该调用由前端触发。第一次调用此端点时,有一个主体和可用的不记名令牌。在 /account 端点内,调用带有主体对象的 userService。更多精确

getUserFromAuthentication(OAuth2Authentication authentication)

被调用。在此方法中,有一部分将 OAuth2Authentication 替换为新的 UsernamePasswordAuthenticationToken 并将其插入到 SecurityContext 中:

UsernamePasswordAuthenticationToken token = getToken(details, user, 
grantedAuthorities);
authentication = new OAuth2Authentication(authentication.getOAuth2Request(), token);
SecurityContextHolder.getContext().setAuthentication(authentication);

所以在那之后,access_token 丢失了。我不太确定为什么它被新的 OAuth2Authentication 取代,但我倾向于扩展这部分并将 access_token 保留在我的 securityContext 中以供进一步调用。