我的 SparkJava 资源服务器在尝试验证访问令牌时出现 403 错误

My SparkJava resource server gets 403 errors when trying to validate access tokens

我想使用 Spark-java 设置一个非常基本的 REST API,它只检查从我自己的授权服务器获得的访问令牌。它向授权服务器的 /oauth/authorize 端点创建一个 GET 请求,后跟 ?token=$ACCESS_TOKEN.

每当我尝试这个时,我都会被转移到 /error 端点和 403 错误。

这是我的 API class:

import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spark.utils.StringUtils;

import java.io.IOException;

import static spark.Spark.*;

public class SampleAPI {
  private static final Logger logger = LoggerFactory.getLogger("SampleAPI");

  public static void main(String[] args) {
    // Run on port 9782
    port(9782);
    // Just returns "Hello there" to the client's console
    before((preRequest, preResponse) -> {

      System.out.println("Getting token from request");
      final String authHeader = preRequest.headers("Authorization");
      //todo validate token, don't just accept it because it's not null/empty
      if(StringUtils.isEmpty(authHeader) || !isAuthenticated(authHeader)){
        halt(401, "Access not authorised");
      } else {
        System.out.println("Token = " + authHeader);
      }
    });
    get("/", (res, req) -> "Hello there");
  }

  private static boolean isAuthenticated(final String authHeader) {
    String url = "http://localhost:9780/oauth/authorize";
    //"Bearer " is before the actual token in authHeader, so we need to extract the token itself as a substring
    String token = authHeader.substring(7);
    HttpGet getAuthRequest = new HttpGet(url + "?token=" + token);
    getAuthRequest.setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType());
    CloseableHttpClient httpClient = HttpClients.createMinimal();
    try {
      CloseableHttpResponse response = httpClient.execute(getAuthRequest);
      int statusCode = response.getStatusLine().getStatusCode();
      System.out.println("Status code " + statusCode + " returned for access token " + authHeader);
      return statusCode == 200;
    } catch (IOException ioException) {
      System.out.println("Exception when trying to validate access token " + ioException);
    }
    return false;
  }
}

System.out.println 语句仅用于调试。

这是我的授权服务器的 WebSecurityConfigurerAdapter class:

    package main.config;
    
    import main.service.ClientAppDetailsService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.builders.WebSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.core.userdetails.UserDetailsService;
    import org.springframework.security.crypto.factory.PasswordEncoderFactories;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    @EnableWebSecurity(debug = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
      @Autowired
      private UserDetailsService userDetailsService;
    
      @Override
      @Bean
      public AuthenticationManager authenticationManagerBean() throws Exception {
        //returns AuthenticationManager from the superclass for authenticating users
        return super.authenticationManagerBean();
      }
    
      @Bean
      public PasswordEncoder getPasswordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
      }
    
      @Override
      public void configure(WebSecurity web) throws Exception {
        //Allow for DB access without any credentials
        web.ignoring().antMatchers("/h2-console/**");
      }
    
      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //configures user details, and uses the custom UserDetailsService to check user credentials
        auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
      }
    
      @Override
      protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated();
    
        //disable CORS and CSRF protection for Postman testing
        http.cors().disable().anonymous().disable();
        http.headers().frameOptions().disable();
        http.csrf().disable();
      }
    }

这是我的授权服务器 application.properties:

server.port=9780

#in-memory database, will get populated using data.sql
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=admin
spring.datasource.password=syst3m
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.jpa.properties.hibernate.format_sql=true
#adds to existing DB instead of tearing it down and re-populating it every time the app is started
spring.jpa.hibernate.ddl-auto=update

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
spring.h2.console.settings.trace=false
spring.h2.console.settings.web-allow-others=false

我做错了什么?我是否需要使用 Spring 安全性将我的 API 指定为资源服务器?我需要将它添加到授权服务器的 application.properties 吗?

如果您想使用 Spring 作为安全框架,那么最常见的选择是将其配置为资源服务器。这里有一个getting started tutorial。 API 将永远不会被重定向。

对于 Spark,另一种选择是仅提供一个使用 JWT 验证库(例如 jose4j)的基本过滤器。这往往可以更好地控制错误响应,并让您更好地了解正在发生的事情。请参阅 this Kotlin example,这很容易转换为 Java。