I am trying to use Spring Cloud Zuul as an api/authentication gateway. I have successfully implemented bearer token authorization for my service behind zuul and I successfully have Zuul forwarding to my form login and routing back to my application, but I cannot get Zuul to pass the bearer token to the service.
My Zuul configuration is as follows:
#EnableEurekaClient
#EnableZuulProxy
#SpringBootApplication
#RestController
public class Application { ... }
My service configuration is as follows:
#Profile("oauth")
#Configuration
#EnableResourceServer
#EnableWebSecurity
public static class InternalApiGatewayConfig extends ResourceServerConfigurerAdapter {
When my Angular app tries to access my service through zuul, I get
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
I have managed to work around this issue by putting the following code in a ZuulFilter, but it doesn't seem right:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();
String tokenValue = details.getTokenValue();
ctx.addZuulRequestHeader("Authorization", "bearer " + tokenValue);
My understanding is that Zuul should automatically send the bearer token value. What am I missing?
So I've figured out the answer to my own question, and it was painfully simple. My project imported spring-security-oauth2. I simply needed to add a dependency on spring-cloud-security as well. With that, I did not have to implement a ZuulFilter at all.
Btw this is the solution that works without spring-cloud-security
#Component
public class TokenRelayFilter extends ZuulFilter {
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
#SuppressWarnings("unchecked")
Set<String> headers = (Set<String>) ctx.get("ignoredHeaders");
// JWT tokens should be relayed to the resource servers
headers.remove("authorization");
return null;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 10000;
}
}
Related
Blazor server app (.net 6.0) which uses Windows Authentication and works properly with Authorizing Roles is not recognizing Authorize Roles in an API I have in the same project. If I have allow anonymous in iis set and use [AllowAnonymous] in API it works fine. (Making an api call from code in same project). If iis does not allow anonymous then I try to use [Authorize(Roles.... but I always get 401 Unauthorized.
I do pass in headers username and password that do a validation that works fine if api set for allowanonymous but if not set then the api Get method never even gets triggered and returns 401.
Shouldn't the API recognize the Authorization? Do I need to configure something different in the project?
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class SampleController : ControllerBase
{
private readonly IConfiguration _config;
private readonly IDataServices _iService;
public SampleController(IDataServices dataAccess, IConfiguration config)
{
this._iService = dataAccess;
_config = config;
}
// GET: api/<SampleController>
[HttpGet]
[Authorize(Roles = "Administrators")]
public async Task<List<SampleModel>> GetAllItems([FromHeader] string username, [FromHeader] string password)
{
List<SampleModel> list = new List<SampleModel>();
if (username == _config.GetValue<string>("SampleSettings:username") && password == _config.GetValue<string>("SampleSettings:password"))
{
list = await _iService.GetList();
}
return list;
}
}
thanks,
We have been looking a lot on StackOverflow for this but we cannot seem to have it working.
Our scenario is as following. We have server A and server B.
Server A is a web API that communicates with Server B which is also a web API.
Server B should ONLY serve http requests comming from server A and deny all other requests.
We are trying to force this by using cors
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy(name: "AllowSpecificOrigin", builder =>
{
builder.WithOrigins(new string[0]); // Empty list as a test.
});
});
...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider serviceProvider)
{
app.UseRouting();
app.UseCors("AllowSpecificOrigin");
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
...
}
But now in one of our controllers, there are two methods. One that requires CORS and one that doesn't.
[ApiController]
[Route("api/Server")]
public sealed class ServerController : ControllerBase
{
[HttpGet]
[Route("ServerCheck")]
[DisableCors]
public IActionResult ServerCheck()
{
return Ok(true);
}
[HttpGet]
[Route("Version")]
[EnableCors("AllowSpecificOrigin")]
public IActionResult Version()
{
return Ok(GetType().Assembly.GetName().Version.ToString());
}
}
If I know issue a request through Postman to both methods, both methods supply an answer. But since we did not allow any origin, how come our request is served in the controller?
Is CORS the wrong way to tackle this issue?
What whould be the best way?
Or is our configuration simply wrong?
Nowhere in our appsettings.json or appsettings.Development.json is there a line 'AllowedHosts'.
Have you considered an IP Address allow list? Or an API Key or some other form of authentication?
It seems as if you are trying to leverage CORS for access control which is not it's purpose. The Mozilla Docs are pretty good, but the short version is that CORS is a way to safely get around the browser same origin restrictions and you should not expect it to work in the same way using Postman or any other non-browser client.
It is also worth considering that every header can be controlled by an attacker and so shouldn't be trusted and any credentials should be transmitted only via https
You could create a custom authorization filter which validates that the host is allowed via an authorization filter
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
public class ValidateDomainAttribute : Attribute, IAuthorizationFilter
{
private IEnumerable<string> AllowedDomains { get; }
public ValidateDomainAttribute(params string[] domains){
AllowedDomains = domains;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var host = context.HttpContext.Request.Host;
if (!AllowedDomains.Contains(host, StringComparer.OrdinalIgnoreCase))
{
context.Result = new BadRequestObjectResult("Domain is not allowed !");
}
}
}
And to apply this globally for all controllers and actions by registering this attribute in the StartUp class:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(options =>
{
options.Filters.Add(new ValidateDomainAttribute("your-allowed-domain-1", "your-allowed-domain-2");
)};
// rest of code
}
}
More about filter attributes can be found here: https://learn.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-5.0
I have created an Identity Server using .NET Core and IdentityServer4, I have set of APIs and all calls to these APIs must be authenticated but these APIs might be used by third-party applications so clients can be dynamic
Till now example I am finding is set Clients on startup statically like
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());
}
public class Config
{
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("resourceApi1", "API Application1")
new ApiResource("resourceApi2", "API Application2")
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "clientApp1",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = GrantTypes.ClientCredentials,
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
AllowedScopes = { "resourceApi1" }
}
};
}
}
Is there a way in IdentityServer implementation to register Client Apps and set dynamically
For Example, If I have APIs
1. resourceApi1
2. resourceApi2
Each third-party APIs should be able to register and we should be able to generate ClientID and Client secret for each with what resources they can access and Identity Server authenticates that ClientID and Client Secret?
There is an option to use a client store. By default identity server uses in memory store for finding clients:
services.AddIdentityServer()
.AddInMemoryClients(Clients)
You can change this and register your own client store
services.AddIdentityServer()
.AddClientStore<MyClientStore>()
and implement the service
class MyClientStore : IClientStore
{
public Task<Client> FindClientByIdAsync(string clientId)
{
}
}
This would solve dynamic lookup of clients. For registration of clients and their management, you would need to implement your own infrastructure.
First of all configure your IdentityServer using EntityFramework, Then you need to some Apis to add Client and ApiResources.
IdentityServer has its own implementation of Stores using EntityFramework.
You can add new Api for this purpose.
[Route("api/[controller]")]
[ApiController]
[AllowAnonymous]
public class DynamicController : ControllerBase
{
private readonly ConfigurationDbContext context;
public DynamicController(ConfigurationDbContext context)
{
this.context = context;
}
[HttpPost]
public async Task<ActionResult> AddApiResource(ApiResource apiResource)
{
context.ApiResources.Add(apiResource);
await context.SaveChangesAsync();
return Ok();
}
[HttpPost]
public async Task<ActionResult> AddClient(Client client)
{
context.Clients.Add(client);
await context.SaveChangesAsync();
return Ok();
}
}
I am trying to create a spring resource server secured with oauth2.
I am using auth0 for my auth2 service, and I have an api and client configured with scopes.
I have a resource server that mostly works. It is secured, and I can use #EnableGlobalMethodSecurity and #PreAuthorize("#oauth2.hasScope('profile:read')") to limit access to tokens with that scope.
However, when I try to get the Principal or the OAuth2Authentication they are both null. I've configured the resource server to use the JWK key-set-uri.
I suspect that this has to do with the DefaultUserAuthenticationConverter trying to read the the 'user_name' claim form the JWT, but it needs to be reading it from the 'sub' claim, and I don't know how to change this behaviour.
First create a UserAuthenticationConverter:
public class OidcUserAuthenticationConverter implements UserAuthenticationConverter {
final String SUB = "sub";
#Override
public Map<String, ?> convertUserAuthentication(Authentication userAuthentication) {
throw new UnsupportedOperationException();
}
#Override
public Authentication extractAuthentication(Map<String, ?> map) {
if (map.containsKey(SUB)) {
Object principal = map.get(SUB);
Collection<? extends GrantedAuthority> authorities = null;
return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
}
return null;
}
}
Then configure spring to use it like so:
#Configuration
public class OidcJwkTokenStoreConfiguration {
private final ResourceServerProperties resource;
public OidcJwkTokenStoreConfiguration(ResourceServerProperties resource) {
this.resource = resource;
}
#Bean
public TokenStore jwkTokenStore() {
DefaultAccessTokenConverter tokenConverter = new DefaultAccessTokenConverter();
tokenConverter.setUserTokenConverter(new OidcUserAuthenticationConverter());
return new JwkTokenStore(this.resource.getJwk().getKeySetUri(), tokenConverter);
}
}
Following the sample from Spring Boot: example code from GitHub everything seems to work fine.
But when I integrate Spring Boot Security OAuth2 in the project, my OAuth2 endpoints stop working. There's a warning in the logs:
2017-05-04 08:56:24.109 WARN 2827 --- [nio-8080-exec-1] o.glassfish.jersey.servlet.WebComponent : A servlet request to the URI http://127.0.0.1:8080/oauth/token contains form parameters in the request body but the request body has been consumed by the servlet or a servlet filter accessing the request parameters. Only resource methods using #FormParam will work as expected. Resource methods consuming the request body by other means will not work as expected.
Which makes me think even though I'm not registering the endpoint, Jersey is capturing it and processing the body, making Spring MVC unable to accept the request...
My Jersey Config is:
#Component
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(InfoController.class);
}
}
And my info controller is very simple:
#Component
#Path("/me")
#Produces("application/json")
public class InfoController {
#GET
public String meAction() {
return "Hi";
}
}
And finally, the call I'm trying to make and it's causing the warning in the logs:
curl -X POST -u CLIENT_APPLICATION:123456789 http://127.0.0.1:8080/oauth/token -H "Accept: application/json" -d "password=aaa&username=aa&grant_type=password&client_id=CLIENT_APPLICATION"
Is there a known incompatibility between the two projects (spring-boot-starter-jersey and spring-security-oauth2 in that sense?
Removing the Jersey configuration makes it all work, but I need to use it on my controllers.
My configuration for OAuth2 is:
#Configuration
public class OAuth2ServerConfiguration {
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId("OAuth2 Server");
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/*").authenticated();
// #formatter:on
}
}
}
Then there's the security configuration itself:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final ApiUserDetailsService userDetailsService;
#Autowired
public WebSecurityConfiguration(ApiUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Thanks in advance!
It seems that Jersey is trying the handle the OAuth endpoint, which it shouldn't be. The reason is that the default mapping for Jersey is /*, which means that it will handle requests for all URLs. You can change that in a couple of ways:
Add an #ApplicationPath on top of your ResourceConfig subclass with a different mapping
#Component
#ApplicationPath("/api")
public class JerseyConfig extends ResourceConfig {}
You can add the mapping in your application.properties file
spring.jersey.application-path=/api
What this will do is prefix /api to all your Jersey endpoints, and also cause Jersey not to handle all request, only ones that begin with /api.