Error de Decodificación JSON en BaseClientDetails
Al trabajar con OAuth, es posible encounters el siguiente error:
2019-12-03 22:18:37.239 WARN 19120 --- [nio-8100-exec-4] o.s.s.o.p.c.JdbcClientDetailsService : Could not decode JSON for additional information: BaseClientDetails [clientId=c1, clientSecret=$2a$10$NlBC84MVb7F95EXYTXwLneXgCca6/GipyWR5NHm8K0203bSQMLpvm, scope=[ROLE_ADMIN, ROLE_USER, ROLE_API], resourceIds=[res1], authorizedGrantTypes=[client_credentials, password, authorization_code, implicit, refresh_token], registeredRedirectUris=[http://www.baidu.com], authorities=[], accessTokenValiditySeconds=7200, refreshTokenValiditySeconds=259200, additionalInformation={}]
java.io.EOFException: No content to map to Object due to end of input
at org.codehaus.jackson.map.ObjectMapper._initForReading(ObjectMapper.java:2775) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2718) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1863) ~[jackson-mapper-asl-1.9.13.jar:1.9.13]
at org.springframework.security.oauth2.provider.client.JdbcClientDetailsService$JacksonMapper.read(`JdbcClientDetailsService.java:309`) ~[spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.client.JdbcClientDetailsService$ClientDetailsRowMapper.mapRow(JdbcClientDetailsService.java:268) [spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.security.oauth2.provider.client.JdbcClientDetailsService$ClientDetailsRowMapper.mapRow(JdbcClientDetailsService.java:251) [spring-security-oauth2-2.3.4.RELEASE.jar:na]
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:94) [spring-jdbc-5.1.10.RELEASE.jar:5.1.10.RELEASE]
at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:61) [spring-jdbc-5.1.10.RELEASE.jar:5.1.10.RELEASE]
Análisis del Problema
Este error ocurre en la clase JdbcClientDetailsService cuando se intenta decodificar información adicional del cliente OAuth. Al revisar la base de datos, se observa que el campo additional_information está vacío.
La solución es asegurarse de que este campo contenga:
- Un valor
null, o - Un JSON con formato válido
No debe contener cadenas vacías o representaciones textuales de null, ya que causarán errores en el proceso de decodificación.
Solución para Problemas CORS con SpringBoot2.x + SpringSecurity + OAuth2
Al implementar aplicaciones con arquitectura frontend-backend separada, es común enfrentar problemas de Cross-Origin Resource Sharing (CORS) al obtener tokens de acceso.
Configuración CORS Básica (antes de OAuth)
@Configuration
public class ConfiguracionCORS implements WebMvcConfigurer {
@Override
public void agregarMapeosCORS(CorsRegistry registro) {
registro.agregarMapeo("/**")
.origenesPermitidos("*")
.metodosPermitidos("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
.permitirCredenciales(true)
.maximoEdad(3600)
.cabecerasPermitidas("*");
}
}
Configuración CORS para OAuth2
Después de integrar SpringSecurity con OAuth2, la configuración anterior puede no ser suficiente. Se requiere un filtro dedicado:
@Order(Ordered.MAYOR_PRECEDENCIA)
@Configuration
public class ConfiguracionCORSGlobal implements Filter {
@Override
public void init(FilterConfig configFiltro) {
}
@Override
public void doFilter(SolicitudServlet solicitud, RespuestaServlet respuesta, CadenaFiltros cadena)
throws ServletException, IOException {
HttpServletRequest peticion = (HttpServletRequest) solicitud;
HttpServletResponse respuestaHttp = (HttpServletResponse) respuesta;
respuestaHttp.setHeader("Access-Control-Allow-Origin","*");
respuestaHttp.setHeader("Access-Control-Allow-Credentials","true");
respuestaHttp.setHeader("Access-Control-Allow-Methods","POST,GET,OPTIONS,PUT,DELETE,PATCH,HEAD");
respuestaHttp.setHeader("Access-Control-Allow-Max-Age","3600");
respuestaHttp.setHeader("Access-Control-Allow-Headers","authorization,Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type");
if("OPTIONS".equalsIgnoreCase(peticion.getMethod())){
respuestaHttp.setStatus(HttpServletResponse.SC_OK);
} else {
cadena.doFilter(solicitud, respuesta);
}
}
@Override
public void destroy() {
}
}