No primary or single public constructor found for interface javax.servlet.http - spring-boot

When I fire refreshtoken api on postman I got this type of error:
java.lang.IllegalStateException: No primary or single public
constructor found for interface javax.servlet.http.HttpServletRequest
and no default constructor found either
Here all the details of my filterclass and controller
jwtrefresh.kt:
package com.main.jwtrefresh.filter
import com.main.jwtrefresh.service.UuserService
import com.main.jwtrefresh.util.JwtRefreshTokenUtil
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter
import java.io.IOException
import javax.servlet.FilterChain
import javax.servlet.ServletException
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
import io.jsonwebtoken.ExpiredJwtException
import org.springframework.security.authentication.BadCredentialsException
import org.springframework.security.core.userdetails.User
import org.springframework.util.StringUtils
#Component
class JwtRefreshFilterUser : OncePerRequestFilter() {
#Autowired
private lateinit var jwtRefreshTokenUtil: JwtRefreshTokenUtil
#Autowired
private lateinit var uuserService: UuserService
#Throws(ServletException::class, IOException::class)
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain,
) {
try {
val jwtToken: String? = extractJwtFromRequest(request)
if (StringUtils.hasText(jwtToken) && jwtRefreshTokenUtil.validateToken(jwtToken)) {
val userDetails: UserDetails = User(jwtRefreshTokenUtil.getUsernameFromToken(jwtToken), "",
ArrayList())
val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.authorities
)
SecurityContextHolder.getContext().authentication = usernamePasswordAuthenticationToken
} else {
println("Cannot set the Security Context")
}
} catch (ex: ExpiredJwtException) {
val isRefreshToken = request.getHeader("isRefreshToken")
val requestURL = request.requestURL.toString()
if (isRefreshToken != null && isRefreshToken == "true" && requestURL.contains("refreshToken")) {
allowRefreshToken(ex, request)
} else
request.setAttribute("Exception", ex)
} catch (ex: BadCredentialsException) {
request.setAttribute("Exception", ex)
} catch (ex: Exception) {
println(ex)
}
filterChain.doFilter(request, response)
}
private fun allowRefreshToken(ex: ExpiredJwtException, request: HttpServletRequest) {
val usernamePasswordAuthenticationToken = UsernamePasswordAuthenticationToken(null, null, null)
SecurityContextHolder.getContext().authentication = usernamePasswordAuthenticationToken
request.setAttribute("claims", ex.claims)
}
private fun extractJwtFromRequest(request: HttpServletRequest): String? {
val bearerToken: String = request.getHeader("Authorization")
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
bearerToken.substring(7, bearerToken.length)
}
return null
}
}
jwtcontroller.kt:
#PostMapping("/refreshToken")
#Throws(Exception::class)
fun refreshToken(#ModelAttribute request: HttpServletRequest): ResponseEntity<*>? {
val claims = request.getAttribute("claims") as DefaultClaims
val expectedMap = getMapFromIoJsonwebtokenClaims(claims)
val token: String = jwtRefreshTokenUtil.doGenerateRefreshToken(expectedMap, expectedMap["sub"].toString())
return ResponseEntity(ResToken(token),HttpStatus.OK)
}
private fun getMapFromIoJsonwebtokenClaims(claims: DefaultClaims): Map<String, Any> {
val expectedMap: MutableMap<String, Any> = HashMap()
for ((key, value) in claims) {
expectedMap[key] = value
}
return expectedMap
}

Related

PATCH endpoint 404 on Upgrade to Springboot Version 3

I'm in the process of migrating my Kotlin API to Springboot version 3.
I'm having a problem where certain endpoints aren't getting hit, for example::
a GET to localhost:8081/search/hello
works just fine, here is the GET endpoint::
#PreAuthorize("hasRole('ROLE_MYROLE')")
#GetMapping("/search/{somephrase}")
suspend fun getSomething(#PathVariable("somephrase") phrase: String): someResponse {
return myService.getPhrase(phrase)
}
a PATCH to localhost:8081/update/resourceId
does not, and here is my patch endpoint::
#PreAuthorize("hasRole('ROLE_MYROLE')")
#PatchMapping("/update/{resourceId}")
suspend fun updateSomething(
#PathVariable("resourceId") resourceId: Long,
#RequestBody updateJson: JsonNode
): UpdateResponse {
return recommendationService.update(updateJson, resourceId)
}
Here is the reponse I get::
{
"timestamp": "2023-02-12 09:59:50",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/search"
}
and this in the console::
: [preHandle] PATCH /error - operationId: 00000000000000000000000000000000 |
Here is my Spring Security Config::
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.DefaultSecurityFilterChain
#Configuration
#EnableWebSecurity
class ResourceServerConfiguration {
#Bean
fun configure(http: HttpSecurity): DefaultSecurityFilterChain? {
http.authorizeHttpRequests()
.requestMatchers("/csrf").permitAll()
.requestMatchers("/**").authenticated()
.and()
.csrf()
.disable()
.oauth2ResourceServer().jwt().jwtAuthenticationConverter { AuthAwareTokenConverter().convert(it) }
return http.build()
}
}
and here is my token converter (it just adds a couple of extra claims to the token)
import org.springframework.core.convert.converter.Converter
import org.springframework.security.authentication.AbstractAuthenticationToken
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.oauth2.jwt.Jwt
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter
class AuthAwareTokenConverter : Converter<Jwt, AbstractAuthenticationToken> {
private val jwtGrantedAuthoritiesConverter: Converter<Jwt, Collection<GrantedAuthority>> =
JwtGrantedAuthoritiesConverter()
override fun convert(jwt: Jwt): AbstractAuthenticationToken {
val claims = jwt.claims
val principal = findPrincipal(claims)
val authorities = extractAuthorities(jwt)
return AuthAwareAuthenticationToken(jwt, principal, authorities)
}
private fun findPrincipal(claims: Map<String, Any?>): String {
return if (claims.containsKey(CLAIM_USERNAME)) {
claims[CLAIM_USERNAME] as String
} else if (claims.containsKey(CLAIM_USER_ID)) {
claims[CLAIM_USER_ID] as String
} else {
claims[CLAIM_CLIENT_ID] as String
}
}
private fun extractAuthorities(jwt: Jwt): Collection<GrantedAuthority> {
val authorities = mutableListOf<GrantedAuthority>().apply { addAll(jwtGrantedAuthoritiesConverter.convert(jwt)!!) }
if (jwt.claims.containsKey(CLAIM_AUTHORITY)) {
#Suppress("UNCHECKED_CAST")
val claimAuthorities = (jwt.claims[CLAIM_AUTHORITY] as Collection<String>).toList()
authorities.addAll(claimAuthorities.map(::SimpleGrantedAuthority))
}
return authorities.toSet()
}
companion object {
const val CLAIM_USERNAME = "user_name"
const val CLAIM_USER_ID = "user_id"
const val CLAIM_CLIENT_ID = "client_id"
const val CLAIM_AUTHORITY = "authorities"
}
}
class AuthAwareAuthenticationToken(
jwt: Jwt,
private val aPrincipal: String,
authorities: Collection<GrantedAuthority>
) : JwtAuthenticationToken(jwt, authorities) {
override fun getPrincipal(): String {
return aPrincipal
}
}
What I've tried::
Simplifying the PATCH endpoint to simply return a "hello" String to rule out an issue with the service.
The result is the same, so I think the point of failure must be my Spring Security configuration.
Any help/pointers would be much appreciated!
Interesting! I appear to have found the solution.
I added an extra forward slash to the end of the path in my mapping.
So I changed this::
#PatchMapping("/update/{resourceId}")
to this::
#PatchMapping("/update/{resourceId}/")
and it works!
Oddly SpringBoot version 2.X worked just fine with or without the forward slash. But SpringBoot version 3.X requires it.

Modify response body in Spring Cloud gateway Post Filter

I want to fetch the response received from microservice in gateway and add that response to a new ConsumerResponse Object and set that as the new response to my UI in Post Filter of Gateway.
Say my response from microservice is a ResponseEntity of Object, Json Object like below,
{
"userDetails": {
"userId": 24,
"userName": "ABC",
"description": "ABC from ZZZ",
"registrationStatus": "REGISTERED",
"registrationTime": [
2022,
5,
23,
21,
17,
41,
465000000
],
"lastUpdatedTime": [
2022,
5,
23,
21,
17,
41,
465000000
]
}
}
Below is my ConsumerResponse Class, in which i want to set the above Json data in T data,
#JsonInclude(JsonInclude.Include.NON_NULL)
#Data
public class ConsumerResponse implements Serializable {
private static final long serialVersionUID = 1L;
private ConsumerResponse() {}
public static <T> ResponseEntity<ResponseEnvelope<Object>> ok(
T data, String apiVersion, Status status, HttpStatus httpStatus) {
return ResponseEntity.status(httpStatus)
.body(ResponseEnvelope.builder().apiVersion(apiVersion).data(data).status(status).build());
}
}
Below is my Gateway Filter class,
Took reference from this link How to get Original response body in Spring cloud gateway (Webflux) Post filter
In the above link, its creating a new String response body, but i want the response received from the microservice.
package org.xyz.gateway.filter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.stream.Collectors;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBuffer;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
#Component
public class AuthFilter implements GlobalFilter {
private final Logger logger = LoggerFactory.getLogger(AuthFilter.class);
#Autowired
private IdService idService;
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// code to fetch id from request
MyResult response = idService.getEntitlements(id);
ArrayList<String> rolesList = response.getRoles()
.values()
.stream()
.distinct()
.collect(Collectors.toCollection(ArrayList::new));
ServerHttpRequest httpRequest = exchange.getRequest()
.mutate()
.header("id", id)
.header("roles", rolesList.toString())
.build();
ServerWebExchange mutatedExchange = exchange
.mutate()
.request(httpRequest)
.build();
return chain.filter(mutatedExchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
HttpStatus responseStatus = serverHttpResponse.getStatusCode();
DataBuffer dataBuffer = null;
String path = exchange.getRequest().getPath().toString();
ServerHttpRequest serverHttpRequest = exchange.getRequest();
DataBufferFactory dataBufferFactory = serverHttpResponse.bufferFactory();
ServerHttpResponseDecorator decoratedResponse = getDecoratedResponse(path, serverHttpResponse, serverHttpRequest, dataBufferFactory);
try {
dataBuffer = dataBufferFactory.wrap(
new ObjectMapper().writeValueAsBytes(
// need to set the response data here in ConsumerResponse Object below
ConsumerResponse.ok(decoratedResponse.toString(), GatewayConstants.VERSION, Status.OK, responseStatus)));
} catch (JsonProcessingException e) {
dataBuffer = serverHttpResponse.bufferFactory().wrap("".getBytes());
}
logger.info("decoratedResponse = {}", decoratedResponse);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
serverHttpResponse.getHeaders().setContentLength(decoratedResponse.toString().length());
serverHttpResponse.writeWith(Mono.just(dataBuffer)).subscribe();
exchange.mutate().response(serverHttpResponse).build();
}));
}
private ServerHttpResponseDecorator getDecoratedResponse(String path, ServerHttpResponse response, ServerHttpRequest request, DataBufferFactory dataBufferFactory) {
return new ServerHttpResponseDecorator(response) {
#Override
public Mono<Void> writeWith(final Publisher<? extends DataBuffer> body) {
logger.info("writeWith entering(...)---> with body = {} ", body);
if (body instanceof Flux) {
logger.info("Body is Flux");
Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
DefaultDataBuffer joinedBuffers = new DefaultDataBufferFactory().join(dataBuffers);
byte[] content = new byte[joinedBuffers.readableByteCount()];
joinedBuffers.read(content);
String responseBody = new String(content, StandardCharsets.UTF_8);//MODIFY RESPONSE and Return the Modified response
logger.debug("requestId: {}, method: {}, url: {}, \nresponse body :{}", request.getId(), request.getMethodValue(), request.getURI(), responseBody);
return dataBufferFactory.wrap(responseBody.getBytes());
})).onErrorResume(err -> {
logger.error("error while decorating Response: {}",err.getMessage());
return Mono.empty();
});
}
return super.writeWith(body);
}
};
}
}
Any help would be greatly appreciated.
Thanks in advance.

How to authentication for ASP.NET Core Web API 6 by JWT token generated by Spring Security (Spring Boot 2.7.0)?

I have file JwtTokenUtil.java
package com.example.multitenant.util;
import com.example.multitenant.constant.JWTConstants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;
import java.util.function.Function;
#Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -2550185165626007488L;
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public String getAudienceFromToken(String token) {
return getClaimFromToken(token, Claims::getAudience);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(JWTConstants.SIGNING_KEY)
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(String userName, String tenantOrClientId) {
return doGenerateToken(userName, tenantOrClientId);
}
private String doGenerateToken(String subject, String tenantOrClientId) {
Claims claims = Jwts.claims().setSubject(subject).setAudience(tenantOrClientId);
claims.put("scopes", Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
return Jwts.builder()
.setClaims(claims)
.setIssuer("system")
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWTConstants.ACCESS_TOKEN_VALIDITY_SECONDS * 1000))
.signWith(SignatureAlgorithm.HS256, JWTConstants.SIGNING_KEY)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
I have file
package com.example.multitenant.security;
import com.example.multitenant.constant.JWTConstants;
import com.example.multitenant.mastertenant.config.DBContextHolder;
import com.example.multitenant.mastertenant.entity.MasterTenant;
import com.example.multitenant.mastertenant.service.MasterTenantService;
import com.example.multitenant.util.JwtTokenUtil;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
#Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
#Autowired
MasterTenantService masterTenantService;
#Autowired
private JwtUserDetailsService jwtUserDetailsService;
#Autowired
private JwtTokenUtil jwtTokenUtil;
#Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String header = httpServletRequest.getHeader(JWTConstants.HEADER_STRING);
String username = null;
String audience = null; //tenantOrClientId
String authToken = null;
if (header != null && header.startsWith(JWTConstants.TOKEN_PREFIX)) {
authToken = header.replace(JWTConstants.TOKEN_PREFIX, "");
try {
username = jwtTokenUtil.getUsernameFromToken(authToken);
audience = jwtTokenUtil.getAudienceFromToken(authToken);
MasterTenant masterTenant = masterTenantService.findByClientId(Integer.valueOf(audience));
if (null == masterTenant) {
logger.error("An error during getting tenant name");
throw new BadCredentialsException("Invalid tenant and user.");
}
DBContextHolder.setCurrentDb(masterTenant.getDbName());
} catch (IllegalArgumentException ex) {
logger.error("An error during getting username from token", ex);
} catch (ExpiredJwtException ex) {
logger.warn("The token is expired and not valid anymore", ex);
} catch (SignatureException ex) {
logger.error("Authentication Failed. Username or Password not valid.", ex);
}
} else {
logger.warn("Couldn't find bearer string, will ignore the header");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
logger.info("authenticated user " + username + ", setting security context");
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
}
JWT token generate by Spring Security (for many RESTful end-points what written by Spring RESTful). Sample working token:
https://jwt.io/#debugger-io?token=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkb25odXZ5IiwiYXVkIjoiMSIsInNjb3BlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9BRE1JTiJ9XSwiaXNzIjoic3lzdGVtIiwiaWF0IjoxNjU1MzA0NDczLCJleHAiOjE2NTUzMjI0NzN9.9X3k1VJJOp937X6LJiWuizrZyBP8nROAYlcwiKriXEE
I need call web service to generate PDF invoice from ASP.NET Core Web API 6. Of course, I need validate perimssion/security by JWT token. How to authentication for ASP.NET Core Web API 6 by JWT token generated by Spring Security (Spring Boot 2.7.0)?
I want call ASP.NET Core Web API 6 APIs (with commercial .NET component back-end) directly without call via Spring web-service (with free and open source Java component back-end).
I think this is a tough problem when interface/integration between Java Spring Boot 2.7.0 and ASP.NET Web API 6 and hope you give me a working solution.
Some thing like this validate .Net JWT token to call Java APIs but in reverse direction.
For example I have a .net 6 api project, I installed Microsoft.AspNetCore.Authentication.JwtBearer package for JwtAuthentication.
In program.cs, I add the configuration like this, adding this configuration will make your api validate the token and check token if has correct issuer/autdience, if expired, more details can be seen in new TokenValidationParameters method:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateIssuerSigningKey = true,
ValidIssuer = TokenHelper.Issuer,
ValidAudience = TokenHelper.Audience,
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(TokenHelper.Secret))
};
});
builder.Services.AddAuthorization();
...
...
...
app.UseAuthentication();
app.UseAuthorization();
And in my api controller, I added [Authorize] annotation so that if the input request doesn't contain request header like Authoration: Bearer xxxxxx, api will return 401 unauthorize:
[ApiController]
[Route("Hello")]
public class HelloController : ControllerBase
{
[Authorize]
[HttpGet]
public async Task<string> GetAsync()
{
var accessToken = await TokenHelper.GenerateAccessToken("userOne");
return accessToken;
}
}
This is the code how I generate access token which is similar with yours by Java, follow this blog, token are the same, na matter generated by java or .net.
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
namespace WebApiNet6
{
public class TokenHelper
{
public const string Issuer = "http://mytest.com";
public const string Audience = "http://mytest.com";
public const string Secret = "p0GXO6VuVZLRPef0tyO9jCqK4uZufDa6LP4n8Gj+8hQPB30f94pFiECAnPeMi5N6VT3/uscoGH7+zJrv4AuuPg==";
public static async Task<string> GenerateAccessToken(string userId)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Convert.FromBase64String(Secret);
var claimsIdentity = new ClaimsIdentity(new[] {
new Claim(ClaimTypes.NameIdentifier, userId)
});
var signingCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = claimsIdentity,
Issuer = Issuer,
Audience = Audience,
Expires = DateTime.Now.AddMinutes(15),
SigningCredentials = signingCredentials,
};
var securityToken = tokenHandler.CreateToken(tokenDescriptor);
return await System.Threading.Tasks.Task.Run(() => tokenHandler.WriteToken(securityToken));
}
}
}
without token:

How to use Apache CachingHttpAsyncClient with Spring AsyncRestTemplate?

Is it possible to use CachingHttpAsyncClient with AsyncRestTemplate? HttpComponentsAsyncClientHttpRequestFactory expects a CloseableHttpAsyncClient but CachingHttpAsyncClient does not extend it.
This is known as issue SPR-15664 for versions up to 4.3.9 and 5.0.RC2 - fixed in 4.3.10 and 5.0.RC3. The only way around is is creating a custom AsyncClientHttpRequestFactory implementation that is based on the existing HttpComponentsAsyncClientHttpRequestFactory:
// package required for HttpComponentsAsyncClientHttpRequest visibility
package org.springframework.http.client;
import java.io.IOException;
import java.net.URI;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.Configurable;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.CachingHttpAsyncClient;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.protocol.HttpContext;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
// TODO add support for other CachingHttpAsyncClient otpions, e.g. HttpCacheStorage
public class HttpComponentsCachingAsyncClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory implements AsyncClientHttpRequestFactory, InitializingBean {
private final CloseableHttpAsyncClient wrappedHttpAsyncClient;
private final CachingHttpAsyncClient cachingHttpAsyncClient;
public HttpComponentsCachingAsyncClientHttpRequestFactory() {
this(HttpAsyncClients.createDefault(), CacheConfig.DEFAULT);
}
public HttpComponentsCachingAsyncClientHttpRequestFactory(final CacheConfig config) {
this(HttpAsyncClients.createDefault(), config);
}
public HttpComponentsCachingAsyncClientHttpRequestFactory(final CloseableHttpAsyncClient client) {
this(client, CacheConfig.DEFAULT);
}
public HttpComponentsCachingAsyncClientHttpRequestFactory(final CloseableHttpAsyncClient client, final CacheConfig config) {
Assert.notNull(client, "HttpAsyncClient must not be null");
wrappedHttpAsyncClient = client;
cachingHttpAsyncClient = new CachingHttpAsyncClient(client, config);
}
#Override
public void afterPropertiesSet() {
startAsyncClient();
}
private void startAsyncClient() {
if (!wrappedHttpAsyncClient.isRunning()) {
wrappedHttpAsyncClient.start();
}
}
#Override
public ClientHttpRequest createRequest(final URI uri, final HttpMethod httpMethod) throws IOException {
throw new IllegalStateException("Synchronous execution not supported");
}
#Override
public AsyncClientHttpRequest createAsyncRequest(final URI uri, final HttpMethod httpMethod) throws IOException {
startAsyncClient();
final HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
postProcessHttpRequest(httpRequest);
HttpContext context = createHttpContext(httpMethod, uri);
if (context == null) {
context = HttpClientContext.create();
}
// Request configuration not set in the context
if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) {
// Use request configuration given by the user, when available
RequestConfig config = null;
if (httpRequest instanceof Configurable) {
config = ((Configurable) httpRequest).getConfig();
}
if (config == null) {
config = createRequestConfig(cachingHttpAsyncClient);
}
if (config != null) {
context.setAttribute(HttpClientContext.REQUEST_CONFIG, config);
}
}
return new HttpComponentsAsyncClientHttpRequest(cachingHttpAsyncClient, httpRequest, context);
}
#Override
public void destroy() throws Exception {
try {
super.destroy();
} finally {
wrappedHttpAsyncClient.close();
}
}
}

How to retrieve scopes from OAuth token within Spring boot SSO + zuul

I am trying to make a simple API gateway using Spring boot SSO + Zuul. I need to translate OAuth scopes into headers which will be further used by some other backend service to do RBAC based on headers.
I am using this CustomOAuth2TokenRelayFilter that will basically set headers before sending to the backend. My issue is how do I get scopes from the current token. The class OAuth2AuthenticationDetails does provide the token value but it doesnt provide the scopes.
I am not sure about how to obtain the scopes in there.
Below is the Custom Zuul Filter which is mostly taken from
https://github.com/spring-cloud/spring-cloud-security/blob/master/spring-cloud-security/src/main/java/org/springframework/cloud/security/oauth2/proxy/OAuth2TokenRelayFilter.java
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Component;
#Component
public class CustomOAuth2TokenRelayFilter extends ZuulFilter {
private static Logger LOGGER = LoggerFactory.getLogger(CustomOAuth2TokenRelayFilter.class);
private static final String ACCESS_TOKEN = "ACCESS_TOKEN";
private static final String TOKEN_TYPE = "TOKEN_TYPE";
private OAuth2RestOperations restTemplate;
public void setRestTemplate(OAuth2RestOperations restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public int filterOrder() {
return 1;
}
#Override
public String filterType() {
return "pre";
}
#Override
public boolean shouldFilter() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth instanceof OAuth2Authentication) {
Object details = auth.getDetails();
if (details instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oauth = (OAuth2AuthenticationDetails) details;
RequestContext ctx = RequestContext.getCurrentContext();
LOGGER.debug ("role " + auth.getAuthorities());
LOGGER.debug("scope", ctx.get("scope")); // How do I obtain the scope ??
ctx.set(ACCESS_TOKEN, oauth.getTokenValue());
ctx.set(TOKEN_TYPE, oauth.getTokenType()==null ? "Bearer" : oauth.getTokenType());
return true;
}
}
return false;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("x-pp-user", ctx.get(TOKEN_TYPE) + " " + getAccessToken(ctx));
return null;
}
private String getAccessToken(RequestContext ctx) {
String value = (String) ctx.get(ACCESS_TOKEN);
if (restTemplate != null) {
// In case it needs to be refreshed
OAuth2Authentication auth = (OAuth2Authentication) SecurityContextHolder
.getContext().getAuthentication();
if (restTemplate.getResource().getClientId()
.equals(auth.getOAuth2Request().getClientId())) {
try {
value = restTemplate.getAccessToken().getValue();
}
catch (Exception e) {
// Quite possibly a UserRedirectRequiredException, but the caller
// probably doesn't know how to handle it, otherwise they wouldn't be
// using this filter, so we rethrow as an authentication exception
throw new BadCredentialsException("Cannot obtain valid access token");
}
}
}
return value;
}
}
You could inject the OAuth2ClientContext into your filter, and use oAuth2ClientContext.getAccessToken().getScope() to retrieve the scopes.
OAuth2ClientContext is a session-scoped bean containing the current access token and preserved state.
So if we apply that to your example, it would look like this:
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Component;
#Component
public class CustomOAuth2TokenRelayFilter extends ZuulFilter {
private static Logger LOGGER = LoggerFactory.getLogger(CustomOAuth2TokenRelayFilter.class);
private static final String ACCESS_TOKEN = "ACCESS_TOKEN";
private static final String TOKEN_TYPE = "TOKEN_TYPE";
private OAuth2RestOperations restTemplate;
#Autowired
private OAuth2ClientContext oAuth2ClientContext;
public void setRestTemplate(OAuth2RestOperations restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public int filterOrder() {
return 1;
}
#Override
public String filterType() {
return "pre";
}
#Override
public boolean shouldFilter() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth instanceof OAuth2Authentication) {
Object details = auth.getDetails();
if (details instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oauth = (OAuth2AuthenticationDetails) details;
RequestContext ctx = RequestContext.getCurrentContext();
LOGGER.debug ("role " + auth.getAuthorities());
LOGGER.debug("scope" + oAuth2ClientContext.getAccessToken().getScope());
ctx.set(ACCESS_TOKEN, oauth.getTokenValue());
ctx.set(TOKEN_TYPE, oauth.getTokenType()==null ? "Bearer" : oauth.getTokenType());
return true;
}
}
return false;
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
ctx.addZuulRequestHeader("x-pp-user", ctx.get(TOKEN_TYPE) + " " + getAccessToken(ctx));
return null;
}
private String getAccessToken(RequestContext ctx) {
String value = (String) ctx.get(ACCESS_TOKEN);
if (restTemplate != null) {
// In case it needs to be refreshed
OAuth2Authentication auth = (OAuth2Authentication) SecurityContextHolder
.getContext().getAuthentication();
if (restTemplate.getResource().getClientId()
.equals(auth.getOAuth2Request().getClientId())) {
try {
value = restTemplate.getAccessToken().getValue();
}
catch (Exception e) {
// Quite possibly a UserRedirectRequiredException, but the caller
// probably doesn't know how to handle it, otherwise they wouldn't be
// using this filter, so we rethrow as an authentication exception
throw new BadCredentialsException("Cannot obtain valid access token");
}
}
}
return value;
}
}
You can retrieve scopes from OAuth2 token with SecurityContextHolder and OAuth2Authentication
private static Set<String> getOAuthTokenScopes() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2Authentication oAuth2Authentication;
if (authentication instanceof OAuth2Authentication) {
oAuth2Authentication = (OAuth2Authentication) authentication;
} else {
throw new IllegalStateException("Authentication not supported!");
}
return oAuth2Authentication.getOAuth2Request().getScope();
}

Resources