I'm using Quarkus to authenticate users with JWT RBAC. However, I would like to be able to authenticate users if JWT token is not present in HTTP header, with HTTP Basic username / password pair and a JDBC Realm. Is it possible with Quarkus ?
Basic Authentication
To enable basic authentication set quarkus.http.auth.basic=true. You must also have at least one extension installed that provides a username/password based IdentityProvider, such as Elytron JDBC.
More can be seen on: https://quarkus.io/guides/security#basic-authentication
Also check this: http://www.adam-bien.com/roller/abien/entry/client_side_http_basic_access
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.core.MultivaluedMap;
import javax.xml.bind.DatatypeConverter;
public class Authenticator implements ClientRequestFilter {
private final String user;
private final String password;
public Authenticator(String user, String password) {
this.user = user;
this.password = password;
}
public void filter(ClientRequestContext requestContext) throws IOException {
MultivaluedMap<String, Object> headers = requestContext.getHeaders();
final String basicAuthentication = getBasicAuthentication();
headers.add("Authorization", basicAuthentication);
}
private String getBasicAuthentication() {
String token = this.user + ":" + this.password;
try {
return "BASIC " + DatatypeConverter.printBase64Binary(token.getBytes("UTF-8"));
} catch (UnsupportedEncodingException ex) {
throw new IllegalStateException("Cannot encode with UTF-8", ex);
}
}
}
Related
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:
I am migrating my spring boot application from ADAL to MSAL.
I am getting the error shown below:
java.util.concurrent.ExecutionException: com.microsoft.aad.msal4j.MsalServiceException:
AADSTS50194: Application 'fd0ac989-0246-4999-b562-6d42d3636c22'(primadollardev_solanapi) is not configured as a multi-tenant application.
Usage of the /common endpoint is not supported for such applications created after '10/15/2018'.
Use a tenant-specific endpoint or configure the application to be multi-tenant.
Trace ID: 49905cdc-df8e-4bbb-9f48-daa2fe893000
Correlation ID: d6957f88-b418-4351-b141-4a5fbb6c6a99
Timestamp: 2020-08-30 06:05:40Z
ADAL CODE
/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for
* license information.
*/
package com.microsoft.azure.spring.autoconfigure.aad;
import com.microsoft.aad.adal4j.ClientCredential;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.proc.BadJOSEException;
import com.nimbusds.jose.util.ResourceRetriever;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.naming.ServiceUnavailableException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.MalformedURLException;
import java.text.ParseException;
import java.util.concurrent.ExecutionException;
public class AADAuthenticationFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(AADAuthenticationFilter.class);
private static final String CURRENT_USER_PRINCIPAL = "CURRENT_USER_PRINCIPAL";
private static final String CURRENT_USER_PRINCIPAL_GRAPHAPI_TOKEN = "CURRENT_USER_PRINCIPAL_GRAPHAPI_TOKEN";
private static final String CURRENT_USER_PRINCIPAL_JWT_TOKEN = "CURRENT_USER_PRINCIPAL_JWT_TOKEN";
private static final String TOKEN_HEADER = "Authorization";
private static final String TOKEN_TYPE = "Bearer ";
private AADAuthenticationProperties aadAuthProps;
private ServiceEndpointsProperties serviceEndpointsProps;
private UserPrincipalManager principalManager;
public AADAuthenticationFilter(AADAuthenticationProperties aadAuthProps,
ServiceEndpointsProperties serviceEndpointsProps,
ResourceRetriever resourceRetriever) {
this.aadAuthProps = aadAuthProps;
this.serviceEndpointsProps = serviceEndpointsProps;
this.principalManager = new UserPrincipalManager(serviceEndpointsProps, aadAuthProps, resourceRetriever, false);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader(TOKEN_HEADER);
if (authHeader != null && authHeader.startsWith(TOKEN_TYPE)) {
try {
final String idToken = authHeader.replace(TOKEN_TYPE, "");
UserPrincipal principal = (UserPrincipal) request
.getSession().getAttribute(CURRENT_USER_PRINCIPAL);
String graphApiToken = (String) request
.getSession().getAttribute(CURRENT_USER_PRINCIPAL_GRAPHAPI_TOKEN);
final String currentToken = (String) request
.getSession().getAttribute(CURRENT_USER_PRINCIPAL_JWT_TOKEN);
final ClientCredential credential =
new ClientCredential(aadAuthProps.getClientId(), aadAuthProps.getClientSecret());
final AzureADGraphClient client =
new AzureADGraphClient(credential, aadAuthProps, serviceEndpointsProps);
if (principal == null ||
graphApiToken == null ||
graphApiToken.isEmpty() ||
!idToken.equals(currentToken)
) {
principal = principalManager.buildUserPrincipal(idToken);
final String tenantId = principal.getClaim().toString();
graphApiToken = client.acquireTokenForGraphApi(idToken, tenantId).getAccessToken();
principal.setUserGroups(client.getGroups(graphApiToken));
request.getSession().setAttribute(CURRENT_USER_PRINCIPAL, principal);
request.getSession().setAttribute(CURRENT_USER_PRINCIPAL_GRAPHAPI_TOKEN, graphApiToken);
request.getSession().setAttribute(CURRENT_USER_PRINCIPAL_JWT_TOKEN, idToken);
}
final Authentication authentication = new PreAuthenticatedAuthenticationToken(
principal, null, client.convertGroupsToGrantedAuthorities(principal.getUserGroups()));
authentication.setAuthenticated(true);
log.info("Request token verification success. {}", authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (MalformedURLException | ParseException | BadJOSEException | JOSEException ex) {
log.error("Failed to initialize UserPrincipal.", ex);
throw new ServletException(ex);
} catch (ServiceUnavailableException | InterruptedException | ExecutionException ex) {
log.error("Failed to acquire graph api token.", ex);
throw new ServletException(ex);
}
}
filterChain.doFilter(request, response);
}
}
MSAL CODE
public class MSALAuthenticationFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(MSALAuthenticationFilter.class);
private static final String TOKEN_HEADER = "Authorization";
private static final String TOKEN_TYPE = "Bearer ";
// Properties from the application.properties file like clientId, tenant and stuff.
private static final String CURRENT_USER_PRINCIPAL = "CURRENT_USER_PRINCIPAL";
private static final String CURRENT_USER_ACCESS_TOKEN = "CURRENT_USER_ACCESS_TOKEN";
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final String authHeader = request.getHeader(TOKEN_HEADER);
UserPrincipal principal = (UserPrincipal) request.getSession().getAttribute(CURRENT_USER_PRINCIPAL);
if (authHeader != null && authHeader.startsWith(TOKEN_TYPE)) {
try {
final String idToken = authHeader.replace(TOKEN_TYPE, "");
ConfidentialClientApplication clientApplication = ConfidentialClientApplication.builder(
clientId,
ClientCredentialFactory.create(clientSecret))
.authority(authority)
.build();
Set<String> scopes = new HashSet<>(Arrays.asList(scope.split(" ")));
UserAssertion assertion = new UserAssertion(idToken);
OnBehalfOfParameters params = OnBehalfOfParameters.builder(scopes, assertion).build();
CompletableFuture<AuthenticationResult> future = clientApplication.acquireToken(params);
AuthenticationResult accessToken = future.get();
if (principal == null) {
principal = principalManager.buildUserPrincipal(idToken, accessToken);
request.getSession().setAttribute(CURRENT_USER_PRINCIPAL, principal);
request.getSession().setAttribute(CURRENT_USER_ACCESS_TOKEN, accessToken);
}
final Authentication authentication = new PreAuthenticatedAuthenticationToken(
principal,
null,
convertGroupsToGrantedAuthorities(principal.getUserGroups()));
authentication.setAuthenticated(true);
log.info("Request token verification success. {}", authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
catch (MalformedURLException | InterruptedException | ExecutionException ex) {
log.error("Failed to authenticate", ex);
throw new ServletException(ex);
}
}
filterChain.doFilter(request, response);
}
}
Please can some give me a solution......
It seems to be a version issue please refer to this GitHub issue. Please update you are version to 2.2.4.
I have been trying to use the authentication method from Hashicorp Vault (from here) in my application to get configurations.
But not able to get any information regarding this authentication type in Spring's docs, examples, etc. Can you please help me out as I need this type of authentication to help me with vault in multiuser environment.
Here is my solution:
Configuration class:
package com.company.myapp.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.vault.VaultException;
import org.springframework.vault.authentication.ClientAuthentication;
import org.springframework.vault.support.VaultToken;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
#Configuration
public class VaultConfig {
public static final String LOGIN_PATH = "/v1/auth/userpass/login/";
#Bean
public ClientAuthentication clientAuthentication(#Value("${VAULT_USERNAME}") String username,
#Value("${VAULT_PASSWORD}") String password,
#Value("${spring.cloud.vault.uri}") String host) {
return new UserPassAuthentication(host, LOGIN_PATH, username, password);
}
public static class UserPassAuthentication implements ClientAuthentication {
private RestOperations restOperations = new RestTemplate();
private String url;
private String password;
public UserPassAuthentication(String host, String path, String user, String password) {
this.url = new StringBuilder(host).append(path).append(user).toString();
this.password = password;
}
#Override
public VaultToken login() throws VaultException {
return VaultToken.of(
((Map<String, String>) restOperations.postForEntity(url, new Password(password), Map.class)
.getBody().get("auth")).get("client_token"));
}
}
static class Password {
private String password;
public Password(String password) {
this.password = password;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
resources/bootstrap.properties:
spring.profiles.active=dev
spring.application.name=myapp
spring.cloud.vault.kv.enabled=true
spring.cloud.vault.kv.backend=test-backend
spring.cloud.vault.uri=https://localhost:8200
VAULT_USERNAME=usr
VAULT_PASSWORD=pwd
resources/META-INF/spring.factories
org.springframework.cloud.bootstrap.BootstrapConfiguration=com.company.myapp.config.VaultConfig
Using this solution I had 2 problems.
overriding spring preconfigured beans.
solution: add
spring.main.allow-bean-definition-overriding =true
in your bootstrap.properties file. this allows bean definition overriding.
beanInstantiationException: Because authentication steps were not defined properly.
solution: I used the token authentication method as follows:
public ClientAuthentication clientAuthentication() {
return new TokenAuthentication(new UserPassAuthentication("host", path, "user", "password").login());
}
and the rest of the code exactly as shown before.
I have created a very basic Spring Cloud Zuul application to act as a token relay to a resource server. This is extremely simple to do for authorization_code grant. The below does exactly what I need:
package zuul;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
#SpringBootApplication
#EnableOAuth2Sso
#EnableZuulProxy
public class RelayProxy {
public static void main(String[] args) {
SpringApplication.run(RelayProxy.class, args);
}
}
And in the application.yml (my authorization server runs on port 9999):
server:
port: 8000
security:
oauth2:
client:
accessTokenUri: http://localhost:9999/oauth/token
userAuthorizationUri: http://localhost:9999/oauth/authorize
clientAuthenticationScheme: form
clientId: client
clientSecret: secret
resource:
jwt:
key-uri: http://localhost:9999/oauth/token_key
zuul:
routes:
resource:
path: /resource/**
url: http://localhost:8002/
To allow a legacy system that only supports Basic Authentication to get access to the resource server, I would like a request with a basic authentication header to be intercepted and a password grant performed to obtain and relay the access token. I have tried to configure this using Spring Security / OAuth2 configuration, but was unable to get it to work. The zuul filter below is what I am currently using and it works fine, but I would like to know if it is possible to achieve the same result through the correct Spring configuration:
public class PasswordGrantZuulFilter extends ZuulFilter {
#Value("${security.oauth2.client.accessTokenUri}")
private String accessTokenUri;
#Value("${security.oauth2.client.clientId}")
private String clientId;
#Value("${security.oauth2.client.clientSecret}")
private String clientSecret;
#Autowired
private OAuth2ClientContext clientContext;
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 10;
}
#Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
String header = ctx.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
return header != null && header.toLowerCase().startsWith("basic");
}
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
if (clientContext.getAccessToken() == null) {
String header = ctx.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
String base64Credentials = header.substring("Basic".length()).trim();
String credentials = new String(Base64.getDecoder().decode(base64Credentials), Charset.forName("UTF-8"));
final String[] values = credentials.split(":", 2);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.put(HttpHeaders.AUTHORIZATION, Collections.singletonList("Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes())));
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "password");
map.add("username", values[0]);
map.add("password", values[1]);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
ResponseEntity<OAuth2AccessToken> response = new RestTemplate().postForEntity(accessTokenUri, request, OAuth2AccessToken.class);
clientContext.setAccessToken(response.getBody());
}
ctx.addZuulRequestHeader(HttpHeaders.AUTHORIZATION, OAuth2AccessToken.BEARER_TYPE + " " + clientContext.getAccessToken().getValue());
return null;
}
}
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();
}