将 Spring Boot oAuth2 应用程序作为资源服务器运行并提供 Web 内容

Posted

技术标签:

【中文标题】将 Spring Boot oAuth2 应用程序作为资源服务器运行并提供 Web 内容【英文标题】:Run a Spring Boot oAuth2 application as resource server AND serving web content 【发布时间】:2018-10-23 04:07:29 【问题描述】:

我正在使用 Spring Boot 1.5.13 以及 Spring Security 4.2.6 和 Spring Security oAuth2 2.0.15。

我想为我们的 Spring Boot 应用程序找到一个最佳实践设置,这些应用程序提供混合的内容集:一个 REST API,以及一些为开发人员提供便利的“登陆页面”的网页,上面有一些链接,还有 Swagger基于 API 的文档,也是网页内容。

我的配置允许我使用适当的授权代码流运行应用程序,因此我可以通过浏览器访问所有 Web 内容并通过配置的 IdP(在我的情况下为 PingFederate)进行身份验证,另外我可以从在浏览器中,即直接或使用 REST 客户端,例如使用 RESTClient。

这是我的安全配置:

@Slf4j
@Configuration
@EnableWebSecurity
@EnableOAuth2Sso // this annotation must stay here!
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.authorizeRequests()
                .antMatchers("/login**", "/webjars/**", "/css/**").permitAll()
                .antMatchers("/cfhealth").permitAll()
                .antMatchers("/").permitAll()
                .antMatchers("/protected", "/api/**").authenticated();
    

    @Bean
    public RequestContextListener requestContextListener() 
        return new RequestContextListener();
    


以及 oAuth2 配置:

@Configuration
@Slf4j
public class OAuth2Config extends ResourceServerConfigurerAdapter 

    @Value("$pingfederate.pk-uri")
    String pingFederatePublicKeyUri;

    @Autowired
    PingFederateKeyUtils pingFederateKeyUtils;

    @Override
    public void configure(ResourceServerSecurityConfigurer config) 
        config.tokenServices(tokenServices());
    

    @Bean
    public TokenStore tokenStore() 
        return new JwtTokenStore(accessTokenConverter());
    

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() 
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        String certificate = pingFederateKeyUtils.getKeyFromServer(pingFederatePublicKeyUri);
        String publicKey = pingFederateKeyUtils.extractPublicKey(certificate);
        converter.setVerifier(pingFederateKeyUtils.createSignatureVerifier(publicKey));
        return converter;
    

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() 
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        return defaultTokenServices;
    


但是当我想以编程方式/在浏览器外部调用 REST API 时,标题中带有不记名令牌,例如使用 curl,授权代码流启动并重定向到本地登录端点。我想要的是 API 调用接受承载令牌进行身份验证,而不创建会话,并且浏览器中的所有 Web 内容/mvc 调用都建立会话。

curl -i -H "Accept: application/json" -H "Authorization: Bearer $TOKEN" -X GET http://localhost:8080/authdemo/api/hello

在上面的SecurityConfig类中添加@EnableResourceServer注解(并在应用程序属性文件中添加security.oauth2.resource.filter-order=3,我可以使curl命令工作,但随后授权代码流被破坏,对于我的应用程序中的所有 URL,我在浏览器中得到以下输出:

<oauth> <error_description> Full authentication is required to access this resource </error_description> <error>unauthorized</error> </oauth>

现在有没有办法让这个场景很好地工作?如果是,那会是什么样子?还是只有更高版本的Spring Boot+Security+oAuth2才支持?

Spring Boot with Security OAuth2 - how to use resource server with web login form?的问题很相似

【问题讨论】:

【参考方案1】:

我找到了解决方案:它需要多个 HttpSecurity 配置。我通过阅读 Matt Raible 在https://developer.okta.com/blog/2018/02/13/secure-spring-microservices-with-oauth 撰写的精彩文章发现了这一点,他向我介绍了 requestMatchers(.) 的概念。这就是我最终实现它的方式:

 @Configuration
 @EnableResourceServer
 @EnableWebSecurity(debug = true)
 @EnableOAuth2Sso
 public class ResourceServerConfig extends ResourceServerConfigurerAdapter 

     @Bean
     public RequestContextListener requestContextListener() 
         return new RequestContextListener();
     

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

 

这样我就可以使用浏览器访问服务,从而产生授权代码流。但是访问 API(或者实际上是服务的任何部分)会导致对所提供的 Bearer 令牌的验证。

为了说明在这种情况下如何排除/公开某些端点,下面是我如何配置执行器端点和我自己添加的一个非常简单的“ping”端点:

 @Configuration
 @Order(1)
 public class ActuatorSecurity extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.requestMatcher(new OrRequestMatcher(EndpointRequest.to("health", "info"),
                 new AntPathRequestMatcher("/cfhealth"))).authorizeRequests().anyRequest().permitAll();
    

 

还有我对 /cfhealth 端点的实现:

 @Controller
 @Slf4j
 public class MainController 

     @GetMapping(value = "/cfhealth")
     @ResponseBody
     public String cfhealth() 
         return "ok";
     

 

如果这是 Spring Security 配置的最佳实践方式或者是否有更好的方法,我很高兴向其他人学习。在过去的几周里,我在这个主题上花费了相当长的时间,掌握基本的 Spring Security 概念需要相当多的努力。

【讨论】:

我也在努力让同一个应用程序作为资源服务器和 SSO 客户端工作。为什么你的ResourceServerConfig 类用EnableOAuth2Sso 注释? Oauth2 客户端部分没有单独的配置吗?

以上是关于将 Spring Boot oAuth2 应用程序作为资源服务器运行并提供 Web 内容的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot OAuth2 将内部用户与 Facebook/Google 登录链接

带有 Spring Boot 的 OAuth2 SSO 没有授权屏幕

将 Spring Boot oAuth2 应用程序作为资源服务器运行并提供 Web 内容

Spring Boot 2 和 OAuth2/JWT 配置

Angular 4/5 + Spring Boot + Oauth2 支持

如何将 oAuth2.0 与 Spring boot 1.5.8.RELEASE 集成