Need to get authenticated user name from the OAuth2User in Spring Cloud Gateway routes - spring

I am a newbie to Spring Cloud Gateway. I have created a simple SCG application and my use case is to forward the authenticated username to my downstream application via http request header.
This is my SpringBoot class
package com.xyz;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties.Jwt;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.security.oauth2.gateway.TokenRelayGatewayFilterFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
#Controller
#SpringBootApplication
public class OracleSSOMultiplexer {
#Autowired
private TokenRelayGatewayFilterFactory filterFactory;
#GetMapping("/")
public String index(Model model,
#RegisteredOAuth2AuthorizedClient OAuth2AuthorizedClient authorizedClient,
#AuthenticationPrincipal OAuth2User oauth2User) {
model.addAttribute("userName", oauth2User.getName());
model.addAttribute("clientName", authorizedClient.getClientRegistration().getClientName());
model.addAttribute("userAttributes", oauth2User.getAttributes());
return "index";
}
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
//.route("resource", r -> r.path("/resource")
.route(r -> r.path("/hyperion/**")
.filters(f -> f.filters(filterFactory.apply())
.addRequestHeader("HYPLOGIN", "scott") // I need to pass the authenticated username here instead of hardcoding it
.addResponseHeader("hyp-response", "hyp-response-header-val"))
.uri("http://localhost:8081/")
.id("hyperionModule"))
.build();
}
public static void main(String[] args) {
SpringApplication.run(OracleSSOMultiplexer.class, args);
}
}
and this my application.yml
server:
port: 8080
logging:
level:
root: INFO
org.springframework.web: INFO
org.springframework.web.HttpLogging: DEBUG
org.springframework.security: DEBUG
org.springframework.security.oauth2: DEBUG
org.springframework.cloud.gateway: DEBUG
spring:
autoconfigure:
# TODO: remove when fixed https://github.com/spring-projects/spring-security/issues/6314
exclude: org.springframework.boot.actuate.autoconfigure.security.reactive.ReactiveManagementWebSecurityAutoConfiguration
thymeleaf:
cache: false
security:
oauth2:
client:
registration:
gateway:
provider: pingfederate
client-id: oidchyperion
client-secret: Lms#12345
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/gateway
scope: openid
provider:
pingfederate:
authorization-uri: https://xyz:9035/as/authorization.oauth2
token-uri: https://xyz:9035/as/token.oauth2
user-info-uri: https://xyz:9035/idp/userinfo.openid
user-name-attribute: sub
jwk-set-uri: https://xyz:9035/pf/JWKS
# cloud:
# gateway:
# routes:
# - id: resource
# uri: http://resource:9000
# predicates:
# - Path=/resource
# filters:
# - TokenRelay=
# - RemoveRequestHeader=Cookie
I am able to perform OAuth Authentication and able to display the authenticated user information using the Model at the index.html. I need now to just pass the logged in user's username (oauth2user.getName()) in the request header but I am not able to figure out.
Any help in this regards is appreciated.
Thanks,
Thani

package com.likeminds.filter;
import java.security.Principal;
import java.util.Collections;
import java.util.List;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import
org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
#Component
public class AuthNUserHeaderFilterFactory extends
AbstractGatewayFilterFactory<AuthNUserHeaderFilterFactory.Config> {
private static final String VALUE = "value";
public AuthNUserHeaderFilterFactory() {
super(Config.class);
}
#Override
public Config newConfig() {
return new Config();
}
#Override
public List<String> shortcutFieldOrder() {
return Collections.singletonList(VALUE);
}
#Override
public GatewayFilter apply(Config config) {
//Custom Pre Filter. Suppose we can extract JWT and perform
Authentication
return (exchange, chain) -> exchange.getPrincipal()
.map(Principal::getName)
.defaultIfEmpty("Default User")
.map(userName -> {
//adds header to proxied request
System.out.println("Config value ="+config.value);
exchange.getRequest().mutate().header(config.value,
userName).build();
System.out.println("Config First pre header filter" +
exchange.getRequest().getHeaders());
return exchange;
})
.flatMap(chain::filter);
}
public static class Config {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}

You can write a simple gateway filter config and intercept the request before passing the request to downstream microservices if you are using it as a gateway.
If you are using OAuth2 Authentication then you should be able to get the users information by adding below lines of code:
if (principal instanceof OAuth2AuthenticationToken) {
//Get username from Principal
String userName = principal.getName();
//Get user infor from spring security context
SecurityContextImpl context = exchange.getSession().block().getAttribute("SPRING_SECURITY_CONTEXT");
DefaultOidcUser principal1 = (DefaultOidcUser) context.getAuthentication().getPrincipal();
String fullName = principal1.getUserInfo().getFullName();
String emailId = principal1.getUserInfo().getEmail();
}
Once you get the use information, you can add this information in the request header or in cookies and pass on to the downstream microservices.
// Adding cookies
session = new HttpCookie("SESSION", exchange.getSession().block().getId()).toString();
// adds header to proxied request
exchange.getRequest().mutate().header("X-Auth-Username", new String[] { userName })
.header("X-Auth-Userid", new String[] { userId })
.header("X-Auth-Email", new String[] { emailId }).header("cookie", session)
.build();
Below is complete code which you can use:
import java.net.HttpCookie;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.context.SecurityContextImpl;
import
org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import reactor.core.publisher.Mono;
#Configuration
public class GatewayConfig {
#SuppressWarnings("deprecation")
#Bean
public GlobalFilter customGlobalFilter() {
return (exchange, chain) -> exchange.getPrincipal().map(principal -> {
String emailId = "";
String session = "";
String userName = "";
if (principal instanceof OAuth2AuthenticationToken) {
//Get username from Principal
userName = principal.getName();
SecurityContextImpl context = exchange.getSession().block().getAttribute("SPRING_SECURITY_CONTEXT");
DefaultOidcUser principal1 = (DefaultOidcUser) context.getAuthentication().getPrincipal();
emailId = principal1.getUserInfo().getEmail();
// Adding cookies
session = new HttpCookie("SESSION", exchange.getSession().block().getId()).toString();
}
// adds header to proxied request
exchange.getRequest().mutate().header("X-Auth-Username", new String[] { userName })
.header("X-Auth-Email", new String[] { emailId }).header("cookie", session)
.build();
return exchange;
}).flatMap(chain::filter).then(Mono.fromRunnable(() -> {
}));
}
}
You can use these headers and cookies in all the downstream microservices.

Related

How to modify query param in webflux filter

So any ideal how to modify query param in webflux filter, this code didn't work;
public class MyFilter implements WebFilter {
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// didn't work, throw UnSupportOperationException
//request.getQueryParams().add("key1", "value1");
return chain.filter(exchange);
}
}
I am not sure about how you can modify the query param but the reason why you are not able to modify the params through the method getQueryParams() is because it returns a read only map. Check this link
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/server/reactive/ServerHttpRequest.html#getQueryParams--
As #Prakher Jindal explained request.getQueryParams() returns a read-only map,
but you can add query params by writing a filter like this:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.OrderedGatewayFilter;
import org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Optional;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
#Component
public class MyRewriteGatewayFilter extends AbstractGatewayFilterFactory<MyRewriteGatewayFilter.Config> {
public SecurityRewriteGatewayFilter() {
super(Config.class);
}
#Override
public GatewayFilter apply(Config config) {
return new OrderedGatewayFilter((exchange, chain) -> {
Optional.of(exchange)
.map(e -> (URI) e.getAttribute(GATEWAY_REQUEST_URL_ATTR))
.map(URI::toString)
.map(uri -> uri + "?key=" + "value")
.map(UriComponentsBuilder::fromUriString)
.map(UriComponentsBuilder::build)
.map(UriComponents::toUri)
.ifPresent(u -> exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, u));
return chain.filter(exchange.mutate().build());
}
, RouteToRequestUrlFilter.ROUTE_TO_URL_FILTER_ORDER + 1);
}
#Data
public static class Config {
}
}
Since this filter is not a global filter, make sure to add this to your application.yml file for the
routes on which you want to apply it. Something like this:
spring:
cloud:
gateway:
routes:
- id: my-routes
uri: http://localhost:8082
predicates:
- Path=/api/**
filters:
- name: MyRewriteGatewayFilter

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

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

How to get BearerTokenAuthentication from SecurityContext in a GlobalFilter in Spring Cloud Gateway

Spring Cloud Gateway as a OAuth2ResourceServer with following Authorisation Config:
#Bean
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange(exchanges ->
exchanges
.anyExchange().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerSpec::jwt)
return http.build();
}
I have a global filter in place which is responsible for performing some functions at each valid authenticated request, something like this:
#Service
public class CustomGlobal implements GlobalFilter {
#Autowired
BearerTokenAuthentication authentication;
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// access request headers, and perform some logic
// extract details from the JWT token, and perform some logic
log.info(authentication.getTokenAttributes.get("sub"));
// ^ in the above line there's a NullPointerException, since instance
// BearerTokenAuthentication is not set, or not visible at a GlobalFilter class
return chain.filter(exchange);
}
}
I am still in a learning phase. Any possible leads would be appreciated.
I did that this way(Note you should change WebFilter to GlobalFilter).
Add into your pom
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>5.4.6</version>
</dependency>
Then Filter should be like
package filter;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.*;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
#Log4j2
public class CustomGlobal implements WebFilter {
public static final String HEADER_PREFIX = "Bearer ";
private final ReactiveJwtDecoder jwtDecoder;
public ReactiveJwtDecoder createDecoder(String issuer, String jwkUrl) {
var jwtDecoder = NimbusReactiveJwtDecoder.withJwkSetUri(jwkUrl).build();
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(
new JwtIssuerValidator(issuer),
new JwtTimestampValidator()));
return jwtDecoder;
}
protected CustomGlobal(String issuer, String jwkUrl) {
this.jwtDecoder = createDecoder(issuer, jwkUrl);
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
return Mono
.defer(() -> {
var token = resolveToken(exchange.getRequest());
if (!StringUtils.hasText(token)) {
throw new BadJwtException("Authorisation token is invalid");
}
return jwtDecoder.decode(token);
})
.flatMap(tokenJwt -> {
log.info(tokenJwt.getClaimAsString("sub"));
return chain.filter(exchange);
})
.onErrorResume(err -> handleError(exchange));
}
private Mono<Void> handleError(ServerWebExchange exchange) {
exchange.getResponse().setRawStatusCode(HttpStatus.UNAUTHORIZED.value());
exchange.getResponse().getHeaders().add("Content-Type", "application/json");
return exchange.getResponse().setComplete();
}
private String resolveToken(ServerHttpRequest request) {
String bearerToken = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(HEADER_PREFIX)) {
return bearerToken.substring(7).trim();
}
return "";
}
}
Next step would be to create configuration
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
#Configuration
public class CustomGlobalConfig {
#Value("${jwt.iss}")
private String issuer;
#Value("${jwt.jwk-uri}")
private String jwkUrl;
#Bean
CustomGlobal createFilterBean() {
return new CustomGlobal(this.issuer, this.jwkUrl);
}
}

Invalid Access Token Spring Boot Resource Server

I have a Spring Boot Resource Server, like this:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
}
And a endpoint like this:
#RestController
public class TestResourceOne {
private static final Logger log = Logger.getLogger(TestResourceOne.class);
#RequestMapping(value = "/calcsqrt")
public Double calcSqtr(#RequestParam("value") Double value) {
return Math.sqrt(value);
}
#RequestMapping(value = "/sum")
public Double calcSqtr(#RequestParam("value1") Double value1, #RequestParam("value2") Double value2) {
return value1 + value2;
}
}
My Authorization Server is in Azure AD, so when i call this endpoint "/calcsqrt" i pass the Bearer Token generated by Azure. This is my request:
GET /serviceone/calcsqrt?value=3 HTTP/1.1
Host: localhost:8080
Authorization: Bearer MY_ACCESS_TOKEN_HERE
Cache-Control: no-cache
Postman-Token: ef5d493c-39f1-4bc4-9084-4ea510ac1255
But i always get the following error from spring:
{
"error": "invalid_token",
"error_description": "Invalid access token: MY_ACCESS_TOKEN_HERE"
}
It seems your resource config class was wrong. i have implemented resource config like this
package com.ig.user.config;
import org.springframework.boot.autoconfigure.security.oauth2.resource.UserInfoTokenServices;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
/**
* #author Jai
*
*/
#Configuration
#EnableResourceServer
#EnableWebSecurity
public class ResourceConfig extends ResourceServerConfigurerAdapter {
private final String userInfoUri = "url";
private final String clientId = "foo";
#Override
public void configure(final ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("user");
}
#Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/api/v1/user/activate/**").permitAll()//
.anyRequest().authenticated();
}
#Primary
#Bean
public UserInfoTokenServices tokenService() {
final UserInfoTokenServices tokenService = new UserInfoTokenServices(userInfoUri, clientId);
return tokenService;
}
}
EDIT-1
userInfoUri is the URL which gives the Authorization for this resource
resourceId For every resource server we have to create one resourceId and that the same id we have to keep in where you are storing the client details
Hope it will help you out

Spring Cloud Security with WSO2 Identity Server

I am trying to implement a spring-boot-web application, secured using spring-cloud-security with an external on-premise authentication server (WSO2 Identity Server). We're using OAuth2 OpenID Connect (JWT tokens).
My application will redirect to the WSO2 server just fine for the /oauth2/authorize request, but is failing when it tries to convert the authorization_code into an access_token. spring-boot reports a 401 authentication error.
I believe this is because the oauth2/token endpoint requires basic authentication.
Can someone guide me how to make the /oauth2/token request use basic authentication headers with spring-cloud-security?
Here's my application.yml
spring:
oauth2:
sso:
home:
secure: false
path: /,/**/*.html
client:
accessTokenUri: https://URI:9443/oauth2/token
userAuthorizationUri: https://URI:9443/oauth2/authorize
clientId: id
clientSecret: secret
scope: openid
clientAuthenticationScheme: header
resource:
userInfoUri: https://URI:9443/oauth2/userinfo?schema=openid
Here is my simple application.java
package demo;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.security.oauth2.sso.EnableOAuth2Sso;
import org.springframework.cloud.security.oauth2.sso.OAuth2SsoConfigurerAdapter;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.WebUtils;
#SpringBootApplication
#EnableZuulProxy
#EnableOAuth2Sso
public class SpringOauth2WithWso2Application {
public static void main(String[] args) {
SpringApplication.run(SpringOauth2WithWso2Application.class, args);
}
#Configuration
protected static class SecurityConfiguration extends OAuth2SsoConfigurerAdapter {
#Override
public void match(RequestMatchers matchers) {
matchers.anyRequest();
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/index.html", "/home.html", "/")
.permitAll().anyRequest().authenticated().and().csrf()
.csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null
&& !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
}
Although the need to add the WSO2 IS certificate to the Java Certificate Store has been mentioned as a solution, in case it helps anyone the steps required to do so, as well as set up mitmproxy so you can inspect the traffic has been documented here by means of a template project: https://github.com/nicodewet/template-spring-boot-oauth2-wso2-is

Resources