Spring - oauth 2 - 无法将访问令牌转换为 JSON

Posted

技术标签:

【中文标题】Spring - oauth 2 - 无法将访问令牌转换为 JSON【英文标题】:Spring - oauth 2 - Cannot convert access token to JSON 【发布时间】:2020-04-07 22:09:43 【问题描述】:

我正在尝试验证资源服务器上的访问令牌。

jwt.io 使用签名验证访问令牌,所以我猜问题是弹簧配置

这是我的 pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>uy.edu.anep</groupId>
    <artifactId>aplicacion-funcionarios-service</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>aplicacion-funcionarios-service</name>
    <description>Demo project for Spring Boot</description>


    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.11.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <start-class>edu.anep.familia.aplicacionfuncionarios.Application</start-class>

        <org.mapstruct.version>1.2.0.Final</org.mapstruct.version>
        <java.version>1.8</java.version>        
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>

        <oauth-autoconfig.version>2.1.11.RELEASE</oauth-autoconfig.version>
        <spring-cloud.version>Greenwich.SR4</spring-cloud.version>

    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>


        <!-- oauth -->
        <dependency>
            <groupId>org.springframework.security.oauth.boot</groupId>
            <artifactId>spring-security-oauth2-autoconfigure</artifactId>
        </dependency>




        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>



        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.8.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-sleuth</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>



        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-envers</artifactId>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>        
        <dependency>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-core</artifactId>
        </dependency>                    

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>      
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
        </dependency>  

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>$org.mapstruct.version</version> 
        </dependency>           


        <!-- https://***.com/a/43574427/1989579 -->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.2.11</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.2.11</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.2.11</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>

        <dependency>
            <groupId>edu.anep.microservicios</groupId>  
            <artifactId>spring-DMZ-Utils</artifactId>
            <version>0.0.8-spring_2.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.12.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>        
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.12.1.GA</version>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>$spring-cloud.version</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>



    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.6.0</version>
                <executions>
                    <execution>
                        <id>recreate-docker</id>
                        <configuration>
                            <environmentVariables>
                                <COMPOSE_PATH>./</COMPOSE_PATH>
                                <COMPOSE_SERVICE_NAME>aplicacion-funcionarios-service</COMPOSE_SERVICE_NAME>
                                <JAR_FILE>target/$project.build.finalName.jar</JAR_FILE>
                            </environmentVariables>   
                            <executable>rebuild.sh</executable>
                        </configuration>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                    </execution>  
                </executions>  
            </plugin> 
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>1.8</source>  
                    <target>1.8</target> 
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>$org.mapstruct.version</version>
                        </path>
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.16.22</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>




    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/libs-milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
        <repository>
            <id>spring-snapshots</id>
            <name>Spring Snapshots</name>
            <url>https://repo.spring.io/libs-snapshot</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>








</project>

这是我的 application.yml

server:
    port: 8080
endpoints:
    shutdown:
        enabled: true
    restart:
        enabled: true

server.servlet.context-path: /aplicacion-funcionarios-service


security:
    basic:
        enabled: false
    oauth2:
        resource:
            jwt:
                key-value: |
                    -----BEGIN PUBLIC KEY-----
                    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvVSBre7DgM7FS1d9gzwBMaOY6j40AjcFHq3s9zx/0QMGAmRrVD2Eiuc7YdIZu9gRpCAohKDz1v0wmeE9Nafqw9XjcxJX2Te4+TTF8/Pia8adSyKVjpFMvlvCu83fdT+vgM3P08QLHtm19ToImTsI5oNZhH/iZNm8bjMJL4D4DXv3rOKwhKp5Sb2Hn8Qwes8MJSFO2YtVtqLCc60L2ERxPd5vZ/7s4mEIhI1bw/U/n5n5yPVHpXsZoP3Eru2LksDsWoWK/jKjJIhRtEZxbDuycMYEYiCaVaxLJ2Bwu41bob+FrS3YOlSqzQDpyTcpuACOdUmRFmtnRtORF1wnlDiyYQIDAQAB
                    -----END PUBLIC KEY-----


spring:
    cloud.circuit.breaker.enabled: false
    application:
      name: aplicacion-funcionarios-service
    #el formato en que van las fechas por defecto, sin zona horaria
    jackson:
        date-format: yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
        time-zone: America/Montevideo
    # Spring JDBC configuration
    datasource:
        url: ...
        username: ...
        password: ..     
    # Spring Data JPA configuration
    jpa:   
        properties:
            org.hibernate.envers.audit_table_suffix: _aud
            org.hibernate.envers.default_schema: audit
            hibernate:
                #format_sql: true
                #do_not_audit_optimistic_locking_field: false
                dialect: org.hibernate.dialect.PostgreSQLDialect
                #https://github.com/spring-projects/spring-boot/issues/12007#issuecomment-369388646
                jdbc.lob.non_contextual_creation: true
        hibernate:
            # To be updated in real production usage! update create-drop none
            #ddl-auto:  update
            show-sql: false
    sleuth:
        baggage-keys:
          - x-request-id
          - x-b3-traceid
          - x-b3-spanid
          - x-b3-parentspanid
          - x-b3-sampled
          - x-b3-flags
          - x-ot-span-context
              #configuracion del cache de redis
    cache:
        type: none

#Disable Ribbon 
ribbon.eureka.enabled: false
#se habilita liquidbase
spring.liquibase.change-log: classpath:db/changelog/db.changelog-master.xml



logging.level.root: INFO
logging.level.org.springframework.web: DEBUG
logging.level.org.springframework.security: DEBUG
logging.level.org.springframework.security.oauth2: DEBUG
#logging.level.org.apache: trace

这是我的配置类

@Configuration
@EnableResourceServer
public class OAuth2SecurityConfig  extends ResourceServerConfigurerAdapter 

    @Override
    public void configure(HttpSecurity http) throws Exception 
        // @formatter:off
        http
                .authorizeRequests()
                //.antMatchers("/**").authenticated()
                .antMatchers(
                        "/*/v2/api-docs",
                        "/v2/api-docs",
                        "/swagger-ui.html",
                        "/swagger-ui.html/**",
                        "/webjars/springfox-swagger-ui/**",
                        "/swagger-resources/**", 
                        "/actuator/*"
                ).permitAll()
                .anyRequest().authenticated();

        //http.cors(
        // @formatter:on
    



但我收到以下错误响应

401

    "error": "invalid_token",
    "error_description": "Cannot convert access token to JSON"

日志显示的信息很少

aplicacion-funcionarios-service_1  | 2019-12-14 22:31:57.545 DEBUG [aplicacion-funcionarios-service,2e8a7fbc810ad58c,2e8a7fbc810ad58c,false] 1 --- [nio-8080-exec-1] p.a.OAuth2AuthenticationProcessingFilter : Authentication request failed: error="invalid_token", error_description="Cannot convert access token to JSON"
aplicacion-funcionarios-service_1  | 2019-12-14 22:31:57.594 DEBUG [aplicacion-funcionarios-service,2e8a7fbc810ad58c,2e8a7fbc810ad58c,false] 1 --- [nio-8080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter  : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@64c00153
aplicacion-funcionarios-service_1  | 2019-12-14 22:31:57.598 DEBUG [aplicacion-funcionarios-service,2e8a7fbc810ad58c,2e8a7fbc810ad58c,false] 1 --- [nio-8080-exec-1] s.s.o.p.e.DefaultOAuth2ExceptionRenderer : Written [error="invalid_token", error_description="Cannot convert access token to JSON"] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@5739b1cb]
aplicacion-funcionarios-service_1  | 2019-12-14 22:31:57.599 DEBUG [aplicacion-funcionarios-service,2e8a7fbc810ad58c,2e8a7fbc810ad58c,false] 1 --- [nio-8080-exec-1] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed

这是发送的标头

Authorization: Bearer eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCIsIm9yZy5hcGVyZW8uY2FzLnNlcnZpY2VzLlJlZ2lzdGVyZWRTZXJ2aWNlIjoiMSJ9.eyJzdWIiOiJkZW1vZG9jZW50ZSIsInJvbGVzIjpbXSwiaXNzIjoiaHR0cHM6XC9cL3ByZXByb2QuYW5lcC5lZHUudXlcL2NhcyIsIm5vbmNlIjpbIiJdLCJjbGllbnRfaWQiOlsiQVBQRnVuY2lvbmFyaW9zIl0sImF1ZCI6Imh0dHA6XC9cL2xvY2FsaG9zdDo4MDgxXC9cL2xvZ2luIiwiZ3JhbnRfdHlwZSI6WyJBVVRIT1JJWkFUSU9OX0NPREUiXSwicGVybWlzc2lvbnMiOltdLCJzY29wZSI6WyJlbWFpbCIsIm9wZW5pZCIsInByb2ZpbGUiXSwiY2xhaW1zIjpbXSwic2NvcGVzIjpbIm9wZW5pZCBwcm9maWxlIGVtYWlsIl0sInN0YXRlIjpbIiJdLCJleHAiOjE1NzYzOTE0MTgsImlhdCI6MTU3NjM2MjYxOCwianRpIjoiQVQtNS05a3ZvNGhsUFY0TUkzcjdReEticGRaSDYtMnRlNTQtdiJ9.NvFRFpa8XqYwGCYNHV6brBoi2wMsHH9YthbUK4wjifg7Kfeu__R9wialAyCUJViifi1cTCkTNfysbo-tH5WaJN3vrENDVSpSPlBbWJS5fVmNR45-HCDtLJkNsoexeTwNin1R5tz-GHNTnh4rNFjGJwj_gI5_MCFRYODBiuU_19HsVX_eEYJn7mPchk_Q8wujk9e_akPRLHzruCa3yilR6LGOzWWecQwVt3q0ZMgaOt-aG42OVuGySD-vgzpfJfPc4SFzYXyQYtvnuOyb3q1pxECzBVbW296uzzWaEOfV5OlGcMk_i4vN61HBQDMYbYcheehO3T7jZgCulYlGzit6Mw

【问题讨论】:

您当前使用 JwtAccessTokenConverter 的实现是什么?您的 IAM 是什么? 按照@dotore 的建议,我像***.com/a/52952376/1989579 那样实现了JwtAccessTokenConverter,但我打印了一些未出现在控制台中的日志 【参考方案1】:

我无法使用 spring-oauth 依赖项解决这个问题。

我用过

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>   
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.8.3</version>
</dependency>

我创建了用于验证令牌的自定义过滤器

public class JwtFilter extends GenericFilterBean 


    private ObjectMapper mapper = new ObjectMapper();
    private JWTVerifier verifier;

    public JwtFilter(String key) throws Exception 

        String publicKeyPEM = key
                .replace("-----BEGIN PUBLIC KEY-----", "")
                .replace("-----END PUBLIC KEY-----", "")
                .replaceAll("\\s", "");

        byte[] encoded = Base64.getDecoder().decode(publicKeyPEM);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        RSAPublicKey publicKey = (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));

        Algorithm algorithm = Algorithm.RSA512(publicKey, null);
        this.verifier = JWT.require(algorithm)
                .build();

    

    @Override
    public void doFilter(ServletRequest request,
            ServletResponse response,
            FilterChain filterChain)
            throws IOException, ServletException 

        Authentication authentication = getAuthentication((HttpServletRequest) request);

        SecurityContextHolder.getContext().setAuthentication(authentication);

        filterChain.doFilter(request, response);
    

    // Método para validar el token enviado por el cliente
    private Authentication getAuthentication(HttpServletRequest request) 
        try 

            // Obtenemos el token que viene en el encabezado de la peticion
            String tokenStr = request.getHeader("Authorization");
            tokenStr = tokenStr.replace("Bearer", "").trim();

            //se verifica la firma del token
            DecodedJWT jwt = verifier.verify(tokenStr);

            String token = newString((Base64.getDecoder().decode(jwt.getPayload())), "UTF-8");



            //verifica si el token expiro
            Date today = new Date();
            if (jwt.getExpiresAt() != null && (today.compareTo(jwt.getExpiresAt()) > 0)) 
                return null;
            

            //AHORA VA A CARGAR LOS ROLES EN VASE AL USUARIO               

            CustomPrincipal userDetail = new CustomPrincipal();
            userDetail.setRoles(new LinkedList());

            ...

            List<GrantedAuthority> roles = AuthorityUtils.createAuthorityList(
                    userDetail.getRoles()
                            .stream().toArray(size -> new String[size])
            );

            return new UsernamePasswordAuthenticationToken(userDetail, null, roles);

         catch (Throwable t) 
            //t.printStackTrace();
        
        return null;
    

    public static String newString(final byte[] bytes, final String charsetName) 
        if (bytes == null) 
            return null;
        
        try 
            return new String(bytes, charsetName);
         catch (final UnsupportedEncodingException e) 
            throw new RuntimeException(e);
        
    


然后像这样配置过滤器

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
@ConfigurationProperties(prefix = "security.custom")
public class OAuth2SecurityConfig  extends WebSecurityConfigurerAdapter 

    private String publicKey = "...";

    @Override
    public void configure(HttpSecurity http) throws Exception 
        // @formatter:off
        http
                //.csrf().disable()
                .cors().and()
                // make sure we use stateless session; session won't be used to store user's state.
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // handle an authorized attempts 
                .exceptionHandling().authenticationEntryPoint((req, rsp, e) -> rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED));
        http
                .authorizeRequests()
                //.antMatchers("/**").authenticated()
                .antMatchers(
                        "/*/v2/api-docs",
                        "/v2/api-docs",
                        "/swagger-ui.html",
                        "/swagger-ui.html/**",
                        "/webjars/springfox-swagger-ui/**",
                        "/swagger-resources/**", 
                        "/actuator/*"
                ).permitAll()
                .anyRequest().authenticated()
                .and()
                // Las demás peticiones pasarán por este filtro para validar el token
                .addFilterBefore(new JwtFilter(publicKey), UsernamePasswordAuthenticationFilter.class);

        //http.cors(
        // @formatter:on
    

【讨论】:

【参考方案2】:

默认情况下,Oauth2 Spring 集成使用自己的“令牌类型”,如果您想使用 JWT 令牌,您必须指定如何处理它们以开发JwtAccessTokenConverter 的合适行为。

希望以下链接能帮助您解决问题:

Option1

Option2

在下面的link 中,您将能够看到一个完整集成的教程:JWT + Oauth2

另一方面,在下一个中,您会发现一个功能齐全的微服务,用作与 Spring 集成的 Oauth 2.0 安全服务器,其中包括其他几个自定义项:

Oauth 2 security server

【讨论】:

非常感谢您的建议!我已经实现了选项 1。我在 CustomUserAuthenticationConverter 类中放置了一些日志打印,但它们没有出现在控制台上,我仍然收到相同的错误消息。

以上是关于Spring - oauth 2 - 无法将访问令牌转换为 JSON的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot 资源服务器无法使用 oAuth 2 访问令牌授权角色

Spring oauth2 刷新令牌 - 无法将访问令牌转换为 JSON

Spring oauth2刷新令牌 - 无法将访问令牌转换为JSON

Oauth2:资源服务器应该如何知道访问令牌是不是有效?

带有 Spring Boot REST 应用程序的 OAuth2 - 无法使用令牌访问资源

无法使用 access_token 访问资源:spring boot Oauth2