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

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

Related

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

Sticky session Ribbon rule in Zuul always has null request

I am attempting to implement a sticky session load balancer rule in a Zuul proxy service. I am using the code from this example: https://github.com/alejandro-du/vaadin-microservices-demo/blob/master/proxy-server/src/main/java/com/example/StickySessionRule.java
I seem to have everything configured correctly, and the rule is triggering in my debugger, but the call to RequestContext.getCurrentContext().getResponse() always returns null, so the cookie is never found, so the rule never takes effect.
The rest of the Zuul config is working 100%. My traffic is proxied and routed and I can use the app fine, only the sticky session rule is not working.
Is there another step I am missing to get the request wired in to this rule correctly?
My route config:
zuul.routes.appname.path=/appname/**
zuul.routes.appname.sensitiveHeaders=
zuul.routes.appname.stripPrefix=false
zuul.routes.appname.retryable=true
zuul.add-host-header=true
zuul.routes.appname.service-id=APP_NAME
hystrix.command.APP_NAME.execution.isolation.strategy=THREAD
hystrix.command.APP_NAME.execution.isolation.thread.timeoutInMilliseconds=125000
APP_NAME.ribbon.ServerListRefreshInterval=10000
APP_NAME.ribbon.retryableStatusCodes=500
APP_NAME.ribbon.MaxAutoRetries=5
APP_NAME.ribbon.MaxAutoRetriesNextServer=1
APP_NAME.ribbon.OkToRetryOnAllOperations=true
APP_NAME.ribbon.ReadTimeout=5000
APP_NAME.ribbon.ConnectTimeout=5000
APP_NAME.ribbon.EnablePrimeConnections=true
APP_NAME.ribbon.NFLoadBalancerRuleClassName=my.package.name.StickySessionRule
The app:
#EnableZuulProxy
#SpringBootApplication
public class ApplicationGateway {
public static void main(String[] args) {
SpringApplication.run(ApplicationGateway.class, args);
}
#Bean
public LocationRewriteFilter locationRewriteFilter() {
return new LocationRewriteFilter();
}
}
EDIT: As requested, the code:
import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
/**
* #author Alejandro Duarte.
*/
public class StickySessionRule extends ZoneAvoidanceRule {
public static final String COOKIE_NAME_SUFFIX = "-" + StickySessionRule.class.getSimpleName();
#Override
public Server choose(Object key) {
Optional<Cookie> cookie = getCookie(key);
if (cookie.isPresent()) {
Cookie hash = cookie.get();
List<Server> servers = getLoadBalancer().getReachableServers();
Optional<Server> server = servers.stream()
.filter(s -> s.isAlive() && s.isReadyToServe())
.filter(s -> hash.getValue().equals("" + s.hashCode()))
.findFirst();
if (server.isPresent()) {
return server.get();
}
}
return useNewServer(key);
}
private Server useNewServer(Object key) {
Server server = super.choose(key);
HttpServletResponse response = RequestContext.getCurrentContext().getResponse();
if (response != null) {
String cookieName = getCookieName(server);
Cookie newCookie = new Cookie(cookieName, "" + server.hashCode());
newCookie.setPath("/");
response.addCookie(newCookie);
}
return server;
}
private Optional<Cookie> getCookie(Object key) {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
if (request != null) {
Server server = super.choose(key);
String cookieName = getCookieName(server);
Cookie[] cookies = request.getCookies();
if (cookies != null) {
return Arrays.stream(cookies)
.filter(c -> c.getName().equals(cookieName))
.findFirst();
}
}
return Optional.empty();
}
private String getCookieName(Server server) {
return server.getMetaInfo().getAppName() + COOKIE_NAME_SUFFIX;
}
}
I think you are missing a PreFilter, like this:
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
public class PreFilter extends com.netflix.zuul.ZuulFilter {
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
RequestContext.getCurrentContext().set(FilterConstants.LOAD_BALANCER_KEY, ctx.getRequest());
return null;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public int filterOrder() {
return FilterConstants.SEND_RESPONSE_FILTER_ORDER;
}
#Override
public String filterType() {
return "pre";
}
}
Mark as Bean
#Bean
public PreFilter preFilter() {
return new PreFilter();
}
And use it in your rule
#Override
public Server choose(Object key) {
javax.servlet.http.HttpServletRequest request = (javax.servlet.http.HttpServletRequest) key;
RequestContext not working cause "hystrix.command.APP_NAME.execution.isolation.strategy=THREAD"

Is there a way to automatically propagate an incoming HTTP header in a JAX-RS request to an outgoing JAX-RS request?

I'm looking for the proper way—in a Jersey application—to read a header from an incoming request and automatically install it in any outgoing requests that might be made by a JAX-RS client that my application is using.
Ideally I'd like to do this without polluting any of my classes' inner logic at all, so via various filters and interceptors.
For simple use cases, I can do this: I have a ClientRequestFilter implementation that I register on my ClientBuilder, and that filter implementation has:
#Context
private HttpHeaders headers;
...which is a context-sensitive proxy (by definition), so in its filter method it can refer to headers that were present on the inbound request that's driving all this, and install them on the outgoing request. For straightforward cases, this appears to work OK.
However, this fails in the case of asynchronicity: if I use the JAX-RS asynchronous client APIs to spawn a bunch of GETs, the filter is still invoked, but can no longer invoke methods on that headers instance variable; Jersey complains that as far as it knows we're no longer in request scope. This makes sense if request scope is defined to be per-thread: the spawned GETs are running in some Jersey-managed thread pool somewhere, not on the same thread as the one with which the headers proxy is associated, so that proxy throws IllegalStateExceptions all over the place when my filter tries to talk to it.
I feel like there's some combination of ContainerRequestFilter and ClientRequestFilter that should be able to get the job done even in asynchronous cases, but I'm not seeing it.
What I would do is make a WebTarget injectable that is preconfigured with a ClientRequestFilter to add the headers. It's better to configure the WebTarget this way, as opposed to the Client, since the Client is an expensive object to create.
We can make the WebTarget injectable using a custom annotation and an InjectionResolver. In the InjectionResolver, we can get the ContainerRequest and get the headers from that, which we will pass to the ClientRequestFilter.
Here it is in action
Create the custom annotation
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface WithHeadersTarget {
String baseUri();
String[] headerNames() default {};
}
Make the InjectionResolver with the custom ClientRequestFilter
private static class WithHeadersTargetInjectionResolver
implements InjectionResolver<WithHeadersTarget> {
private final Provider<ContainerRequest> requestProvider;
private final Client client;
#Inject
public WithHeadersTargetInjectionResolver(Provider<ContainerRequest> requestProvider) {
this.requestProvider = requestProvider;
this.client = ClientBuilder.newClient();
}
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (injectee.getRequiredType() == WebTarget.class
&& injectee.getParent().isAnnotationPresent(WithHeadersTarget.class)) {
WithHeadersTarget anno = injectee.getParent().getAnnotation(WithHeadersTarget.class);
String uri = anno.baseUri();
String[] headersNames = anno.headerNames();
MultivaluedMap<String, String> requestHeaders = requestProvider.get().getRequestHeaders();
return client.target(uri)
.register(new HeadersFilter(requestHeaders, headersNames));
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() {
return false;
}
#Override
public boolean isMethodParameterIndicator() {
return false;
}
private class HeadersFilter implements ClientRequestFilter {
private final MultivaluedMap<String, String> headers;
private final String[] headerNames;
private HeadersFilter(MultivaluedMap<String, String> headers, String[] headerNames) {
this.headers = headers;
this.headerNames = headerNames;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
// if headers names is empty, add all headers
if (this.headerNames.length == 0) {
for (Map.Entry<String, List<String>> entry: this.headers.entrySet()) {
requestContext.getHeaders().put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
// else just add the headers from the annotation
} else {
for (String header: this.headerNames) {
requestContext.getHeaders().put(header, new ArrayList<>(this.headers.get(header)));
}
}
}
}
}
One thing about this implementation is that it checks for an empty headerNames in the #WithHeadersTarget annotation. If it is empty, then we just forward all headers. If the user specifies some header names, then it will only forward those
Register the InjectionResolver
new ResourceConfig()
.register(new AbstractBinder() {
#Override
protected void configure() {
bind(WithHeadersTargetInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<WithHeadersTarget>>() {
}).in(Singleton.class);
}
})
Use it
#Path("test")
public static class TestResource {
#WithHeadersTarget(
baseUri = BASE_URI
headerNames = {TEST_HEADER_NAME})
private WebTarget target;
#GET
public String get() {
return target.path("client").request().get(String.class);
}
}
In this example if, the headerNames is left out, then it will default to an empty array, which will cause all the request headers to be forwarded.
Complete test using Jersey Test Framework
import org.glassfish.hk2.api.Injectee;
import org.glassfish.hk2.api.InjectionResolver;
import org.glassfish.hk2.api.ServiceHandle;
import org.glassfish.hk2.api.TypeLiteral;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.filter.LoggingFilter;
import org.glassfish.jersey.server.ContainerRequest;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.inject.Singleton;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import java.io.IOException;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import static org.assertj.core.api.Assertions.assertThat;
public class ForwardHeadersTest extends JerseyTest {
private static final String BASE_URI = "http://localhost:8000";
private static final String TEST_HEADER_NAME = "X-Test-Header";
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
public #interface WithHeadersTarget {
String baseUri();
String[] headerNames() default {};
}
#Path("test")
public static class TestResource {
#WithHeadersTarget(
baseUri = BASE_URI
)
private WebTarget target;
#GET
public String get() {
return target.path("client").request().get(String.class);
}
}
#Path("client")
public static class ClientResource {
#GET
public String getReversedHeader(#HeaderParam(TEST_HEADER_NAME) String header) {
System.out.println(header);
return new StringBuilder(header).reverse().toString();
}
}
private static class WithHeadersTargetInjectionResolver
implements InjectionResolver<WithHeadersTarget> {
private final Provider<ContainerRequest> requestProvider;
private final Client client;
#Inject
public WithHeadersTargetInjectionResolver(Provider<ContainerRequest> requestProvider) {
this.requestProvider = requestProvider;
this.client = ClientBuilder.newClient();
}
#Override
public Object resolve(Injectee injectee, ServiceHandle<?> handle) {
if (injectee.getRequiredType() == WebTarget.class
&& injectee.getParent().isAnnotationPresent(WithHeadersTarget.class)) {
WithHeadersTarget anno = injectee.getParent().getAnnotation(WithHeadersTarget.class);
String uri = anno.baseUri();
String[] headersNames = anno.headerNames();
MultivaluedMap<String, String> requestHeaders = requestProvider.get().getRequestHeaders();
return client.target(uri)
.register(new HeadersFilter(requestHeaders, headersNames));
}
return null;
}
#Override
public boolean isConstructorParameterIndicator() {
return false;
}
#Override
public boolean isMethodParameterIndicator() {
return false;
}
private class HeadersFilter implements ClientRequestFilter {
private final MultivaluedMap<String, String> headers;
private final String[] headerNames;
private HeadersFilter(MultivaluedMap<String, String> headers, String[] headerNames) {
this.headers = headers;
this.headerNames = headerNames;
}
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
// if headers names is empty, add all headers
if (this.headerNames.length == 0) {
for (Map.Entry<String, List<String>> entry: this.headers.entrySet()) {
requestContext.getHeaders().put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
// else just add the headers from the annotation
} else {
for (String header: this.headerNames) {
requestContext.getHeaders().put(header, new ArrayList<>(this.headers.get(header)));
}
}
}
}
}
#Override
public ResourceConfig configure() {
return new ResourceConfig()
.register(TestResource.class)
.register(ClientResource.class)
.register(new AbstractBinder() {
#Override
protected void configure() {
bind(WithHeadersTargetInjectionResolver.class)
.to(new TypeLiteral<InjectionResolver<WithHeadersTarget>>() {
}).in(Singleton.class);
}
})
.register(new LoggingFilter(Logger.getAnonymousLogger(), true))
.register(new ExceptionMapper<Throwable>() {
#Override
public Response toResponse(Throwable t) {
t.printStackTrace();
return Response.serverError().entity(t.getMessage()).build();
}
});
}
#Override
public URI getBaseUri() {
return URI.create(BASE_URI);
}
#Test
public void testIt() {
final String response = target("test")
.request()
.header(TEST_HEADER_NAME, "HelloWorld")
.get(String.class);
assertThat(response).isEqualTo("dlroWolleH");
}
}

How do I get the Session Object in Spring?

I am relatively new to Spring and Spring security.
I was attempting to write a program where I needed to authenticate a user at the server end using Spring security,
I came up with the following:
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider{
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken)
throws AuthenticationException
{
System.out.println("Method invoked : additionalAuthenticationChecks isAuthenticated ? :"+usernamePasswordAuthenticationToken.isAuthenticated());
}
#Override
protected UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException
{
System.out.println("Method invoked : retrieveUser");
//so far so good, i can authenticate user here, and throw exception if not authenticated!!
//THIS IS WHERE I WANT TO ACCESS SESSION OBJECT
}
}
My usecase is that when a user is authenticated, I need to place an attribute like:
session.setAttribute("userObject", myUserObject);
myUserObject is an object of some class that I can access throughout my server code across multiple user requests.
Your friend here is org.springframework.web.context.request.RequestContextHolder
// example usage
public static HttpSession session() {
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attr.getRequest().getSession(true); // true == allow create
}
This will be populated by the standard spring mvc dispatch servlet, but if you are using a different web framework you have add org.springframework.web.filter.RequestContextFilter as a filter in your web.xml to manage the holder.
EDIT: just as a side issue what are you actually trying to do, I'm not sure you should need access to the HttpSession in the retieveUser method of a UserDetailsService. Spring security will put the UserDetails object in the session for you any how. It can be retrieved by accessing the SecurityContextHolder:
public static UserDetails currentUserDetails(){
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
Object principal = authentication.getPrincipal();
return principal instanceof UserDetails ? (UserDetails) principal : null;
}
return null;
}
Since you're using Spring, stick with Spring, don't hack it yourself like the other post posits.
The Spring manual says:
You shouldn't interact directly with the HttpSession for security
purposes. There is simply no justification for doing so - always use
the SecurityContextHolder instead.
The suggested best practice for accessing the session is:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
The key here is that Spring and Spring Security do all sorts of great stuff for you like Session Fixation Prevention. These things assume that you're using the Spring framework as it was designed to be used. So, in your servlet, make it context aware and access the session like the above example.
If you just need to stash some data in the session scope, try creating some session scoped bean like this example and let autowire do its magic. :)
i made my own utils. it is handy. :)
package samples.utils;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.MessageSource;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.ui.context.Theme;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.ThemeResolver;
import org.springframework.web.servlet.support.RequestContextUtils;
/**
* SpringMVC通用工具
*
* #author 应卓(yingzhor#gmail.com)
*
*/
public final class WebContextHolder {
private static final Logger LOGGER = LoggerFactory.getLogger(WebContextHolder.class);
private static WebContextHolder INSTANCE = new WebContextHolder();
public WebContextHolder get() {
return INSTANCE;
}
private WebContextHolder() {
super();
}
// --------------------------------------------------------------------------------------------------------------
public HttpServletRequest getRequest() {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
return attributes.getRequest();
}
public HttpSession getSession() {
return getSession(true);
}
public HttpSession getSession(boolean create) {
return getRequest().getSession(create);
}
public String getSessionId() {
return getSession().getId();
}
public ServletContext getServletContext() {
return getSession().getServletContext(); // servlet2.3
}
public Locale getLocale() {
return RequestContextUtils.getLocale(getRequest());
}
public Theme getTheme() {
return RequestContextUtils.getTheme(getRequest());
}
public ApplicationContext getApplicationContext() {
return WebApplicationContextUtils.getWebApplicationContext(getServletContext());
}
public ApplicationEventPublisher getApplicationEventPublisher() {
return (ApplicationEventPublisher) getApplicationContext();
}
public LocaleResolver getLocaleResolver() {
return RequestContextUtils.getLocaleResolver(getRequest());
}
public ThemeResolver getThemeResolver() {
return RequestContextUtils.getThemeResolver(getRequest());
}
public ResourceLoader getResourceLoader() {
return (ResourceLoader) getApplicationContext();
}
public ResourcePatternResolver getResourcePatternResolver() {
return (ResourcePatternResolver) getApplicationContext();
}
public MessageSource getMessageSource() {
return (MessageSource) getApplicationContext();
}
public ConversionService getConversionService() {
return getBeanFromApplicationContext(ConversionService.class);
}
public DataSource getDataSource() {
return getBeanFromApplicationContext(DataSource.class);
}
public Collection<String> getActiveProfiles() {
return Arrays.asList(getApplicationContext().getEnvironment().getActiveProfiles());
}
public ClassLoader getBeanClassLoader() {
return ClassUtils.getDefaultClassLoader();
}
private <T> T getBeanFromApplicationContext(Class<T> requiredType) {
try {
return getApplicationContext().getBean(requiredType);
} catch (NoUniqueBeanDefinitionException e) {
LOGGER.error(e.getMessage(), e);
throw e;
} catch (NoSuchBeanDefinitionException e) {
LOGGER.warn(e.getMessage());
return null;
}
}
}
Indeed you can access the information from the session even when the session is being destroyed on an HttpSessionLisener by doing:
public void sessionDestroyed(HttpSessionEvent hse) {
SecurityContextImpl sci = (SecurityContextImpl) hse.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
// be sure to check is not null since for users who just get into the home page but never get authenticated it will be
if (sci != null) {
UserDetails cud = (UserDetails) sci.getAuthentication().getPrincipal();
// do whatever you need here with the UserDetails
}
}
or you could also access the information anywhere you have the HttpSession object available like:
SecurityContextImpl sci = (SecurityContextImpl) session().getAttribute("SPRING_SECURITY_CONTEXT");
the last assuming you have something like:
HttpSession sesssion = ...; // can come from request.getSession(false);
I try with next code and work excellent
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Created by jaime on 14/01/15.
*/
#Controller
public class obteinUserSession {
#RequestMapping(value = "/loginds", method = RequestMethod.GET)
public String UserSession(ModelMap modelMap) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String name = auth.getName();
modelMap.addAttribute("username", name);
return "hellos " + name;
}
In my scenario, I've injected the HttpSession into the CustomAuthenticationProvider class
like this
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider{
#Autowired
private HttpSession httpSession;
#Override
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken)
throws AuthenticationException
{
System.out.println("Method invoked : additionalAuthenticationChecks isAuthenticated ? :"+usernamePasswordAuthenticationToken.isAuthenticated());
}
#Override
protected UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException
{
System.out.println("Method invoked : retrieveUser");
//so far so good, i can authenticate user here, and throw exception
if not authenticated!!
//THIS IS WHERE I WANT TO ACCESS SESSION OBJECT
httpSession.setAttribute("userObject", myUserObject);
}
}
If all that you need is details of User, for Spring Version 4.x you can use #AuthenticationPrincipal and #EnableWebSecurity tag provided by Spring as shown below.
Security Configuration Class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
Controller method:
#RequestMapping("/messages/inbox")
public ModelAndView findMessagesForUser(#AuthenticationPrincipal User user) {
...
}
ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
attr.getSessionId();

Resources