Use a tenant-specific endpoint or configure the application to be multi-tenant - spring-boot

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.

Related

How to get out of static context in spring boot

Both methods, "createToken" and "getAuthentication" return errors when being used in class "JWTAuthenticationFilter" and "JWTAuthorizationFilter". The errors are Non-static method 'getAuthentication(java.lang.String)' cannot be referenced from a static context
The problem is that I don't have a single static field in all my code, so I don't understand the error. I also can't just declare the methods as static because I need to use the variable "jwt_secret" and in my previous post someone explained why I shouldn't make it static:
"Spring will not inject (autowire) into static fields.; that wouldn't make any sense even if it could. Spring beans are instances of classes, but static fields are not associated to any one instance. There are some ugly workarounds but better would be to eliminate the use of static fields."
TokenUtils
package com.XX.ZZ.security;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.stereotype.Component;
import java.util.*;
#Component
public class TokenUtils {
#Autowired
#Value("${custom.data.jwt.secret}")
private final String jwt_secret;
public TokenUtils(String jwtSecret) {
jwt_secret = jwtSecret;
}
public String createToken(String name, String email){
long expirationTime = 86400 * 1000;
Date expirationDate = new Date(System.currentTimeMillis() + expirationTime);
Map<String, Object> extra = new HashMap<>();
extra.put("name", name);
return Jwts.builder()
.setSubject(email)
.setExpiration(expirationDate)
.addClaims(extra)
.signWith(Keys.hmacShaKeyFor(jwt_secret.getBytes()))
.compact();
}
public UsernamePasswordAuthenticationToken getAuthentication(String token){
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(jwt_secret.getBytes())
.build()
.parseClaimsJws(token)
.getBody();
String email = claims.getSubject();
return new UsernamePasswordAuthenticationToken(email, null, Collections.emptyList());
} catch (JwtException e){
return null;
}
}
}
JWTAuthorizationFilter, error in UsernamePasswordAuthenticationToken usernamePAT = TokenUtils.getAuthentication(token)
package com.XX.ZZ.security;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
#Component
public class JWTAuthorizationFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String bearerToken = request.getHeader("Authorization");
if(bearerToken != null && bearerToken.startsWith("Bearer ")){
String token = bearerToken.replace("Bearer ", "");
UsernamePasswordAuthenticationToken usernamePAT = TokenUtils.getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(usernamePAT);
}
filterChain.doFilter(request, response);
}
}
JwtAuthenticationFilter, error in String token = TokenUtils.createToken
package com.XX.ZZ.security;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import java.io.IOException;
import java.util.Collections;
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
AuthCredentials authCredentials = new AuthCredentials();
try {
authCredentials = new ObjectMapper().readValue(request.getReader(), AuthCredentials.class);
} catch (IOException e){ }
UsernamePasswordAuthenticationToken usernamePAT = new UsernamePasswordAuthenticationToken(
authCredentials.getEmail(),
authCredentials.getPassword(),
Collections.emptyList()
);
return getAuthenticationManager().authenticate(usernamePAT);
}
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
UserDetailsImpl userDetails = (UserDetailsImpl) authResult.getPrincipal();
String token = TokenUtils.createToken(userDetails.getName(), userDetails.getUsername());
response.addHeader("Authorization", "Bearer " + token);
response.getWriter().flush();
super.successfulAuthentication(request, response, chain, authResult);
}
}
By calling it as TokenUtils.getAuthentication(token) you are literally calling it as as if it is a static. You need an instance in JWTAuthenticationFilter:
TokenUtils tokenUtilsInstance = new TokenUtils("my secret");
tokenUtilsInstance.getAuthentication(token)
You probably need to drop the constructor TokenUtils(String jwtSecret). That will allow the following in JWTAuthenticationFilter. Much better Spring practice.
#Autowired
TokenUtils tokenUtilsInstance;

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:

spring-boot error org.springframework.security.core.userdetails.User cannot be cast to in.cad.security.model.MyUserPrincipal class

I am completely new to spring boot and trying to write Unit test cases but stuck completely and not able to understand how Authentication works.
Controller class
#PostMapping (path = "/createConcept")
#ApiOperation(value = "Composite object with conceptCO with added roles",
response = ConceptCO.class)
public ConceptCO createConcept(
#RequestBody final ConceptFormVO conceptFormVO,
#ApiIgnore Authentication authentication
) {
ConceptDTO conceptDTO = new ConceptDTO();
BeanUtils.copyProperties(conceptFormVO, conceptDTO,
AppUtils.getNullPropertyNames(conceptFormVO));
LOGGER.info("Input Config : ::{}", conceptFormVO);
List<UserPrincipalAttributes> cadenzPrincipalAttributesList = PrincipalUtil.getRoles(authentication);
String token = PrincipalUtil.getToken(authentication);
return conceptDelegate.createConcept(conceptDTO,cadenzPrincipalAttributesList,token);
}
PrincipalUtil.java
public final class PrincipalUtil {
public static final String BEARER_TOKEN = "Bearer ";
public static List<UserPrincipalAttributes> getRoles(final Authentication authentication) {
UserPrincipal user =
(UserPrincipal) authentication.getPrincipal();
return new ArrayList<>(user.getUserPrincipalAttributes());
}
public static String getToken(final Authentication authentication) {
UserPrincipal user =
(UserPrincipal) authentication.getPrincipal();
String finalToken = BEARER_TOKEN + user.getToken();
return finalToken;
}
}
UserPrincipal.java
public class UserPrincipal implements AuthenticatedPrincipal {
private String name;
private Set<UserPrincipalAttributes> userPrincipalAttributes;
private String token;
// getter & setters
}
UserPrincipalAttributes .java
public class UserPrincipalAttributes {
Set<String> columns;
Set<String> concepts;
String role;
// getter & setters
}
Below is my test function
private Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
private static final String BASE_URL = "/xyz";
#Before
public void setup() throws Exception {
mvc = MockMvcBuilders
.webAppContextSetup(this.wac)
.apply(SecurityMockMvcConfigurers.springSecurity())
.build();
}
#Test
#WithMockUser(username = "test_user1")
public void createConceptTest() throws Exception {
ConceptFormVO conceptFormVO = new ConceptFormVO();
conceptFormVO.setConceptExpression("payment_cat = ABC");
conceptFormVO.setConceptName("all_subsc_test");
RequestBuilder createConceptRequest = post(BASE_URL + "/createConcept",authentication)
.header("Authorization",
String.format("Bearer %s", accessToken))
.content(objectMapper.writeValueAsString(conceptFormVO))
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON);
this.mvc
.perform(createConceptRequest)
.andExpect(status().isOk())
}
Running above test case gives me error
java.lang.ClassCastException: org.springframework.security.core.userdetails.User cannot be cast to in.at.security.model.UserPrincipal
at in.at.security.util.PrincipalUtil.getRoles(PrincipalUtil.java)
Apologies for silly mistakes.
instead of passing Authentication u can directly inject AuthenticatedPrincipal refer below code let me know if it works,
import java.util.Collection;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
public class SampleController {
#PostMapping (path = "/createConcept")
public SampleController createConcept(
#RequestBody final ConceptFormVO conceptFormVO,
#AuthenticationPrincipal OAuth2User principal
) {
Map<String, Object> principalDetails = principal.getAttributes();
Collection<? extends GrantedAuthority> authorities = principal.getAuthorities();
.....
}
}

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

AJAX response conflict

I have a problem with my ajax redirection on response.
The redirection works perfectly, but when, later, I have to return a Boolean with response, it returns the redirection.
Here is the code. The concerned lines have comments :
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Worker extends HttpServlet {
private static final long serialVersionUID = 1L;
private static String firstName = "";
private static String lastName = "";
private static boolean doAnimWheel = false;
private static String portion;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// begin recovering form
Worker.firstName = request.getParameter("firstName");
Worker.lastName = request.getParameter("lastName");
response.sendRedirect("launch.html"); // TODO find why it blocks response
// end recovering form
String param = request.getParameter("srcId");
if(param != null) {
if(param.equals("launch")) {
Worker.doAnimWheel = new Boolean(request.getParameter("doAnimWheel")).booleanValue();
return;
}
else if(param.equals("wheel")) {
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.print(Worker.doAnimWheel); // Here I have to return my Boolean, but it return launch.html
out.flush();
out.close();
return;
}
else if(param.equals("result")) {
Worker.portion = request.getParameter("portion");
Worker.doAnimWheel = new Boolean(request.getParameter("doAnimWheel")).booleanValue();
return;
}
}
}
}
I think the problem is that you always send the redirect at the beginning of your method
response.sendRedirect("launch.html"); // TODO find why it blocks response
// end recovering form
Java documentation for the sendRedirect method HttpServletResponse states: "After using this method, the response should be considered to be committed and should not be written to."
What you try to return later is evidently ignored.
You may want to move the sendRedirect calling to the branches of code that actually need to perform the redirect, like this:
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// begin recovering form
Worker.firstName = request.getParameter("firstName");
Worker.lastName = request.getParameter("lastName");
String param = request.getParameter("srcId");
if(param.equals("launch")) {
Worker.doAnimWheel = new Boolean(request.getParameter("doAnimWheel")).booleanValue();
response.sendRedirect("launch.html");
return;
}
else if(param.equals("wheel")) {
response.setContentType("text/plain");
PrintWriter out = response.getWriter();
out.print(Worker.doAnimWheel); // Here I have to return my Boolean, but it return launch.html
out.flush();
out.close();
return;
}
else if(param.equals("result")) {
Worker.portion = request.getParameter("portion");
Worker.doAnimWheel = new Boolean(request.getParameter("doAnimWheel")).booleanValue();
response.sendRedirect("launch.html");
return;
}
}
}
}

Resources