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
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.
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.
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:
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();
}
}
}
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();
}