Spring SAML 2.0 with Dynamic relying registration, doesn't work on Reverse Proxy Nginx - spring-mvc

I've been looking for a solution in many questions and forums but nothing worked for me.
I've a use case, in which we register Idp configuration in another service, and then we retrieve that particular configuration providing an orgId query param. Somehow I could built something that works on localhost with Okta as Idp, based on these sites:
Here I could delegate Idp selection before real saml auth gets triggered:
Spring Security SAML2 dynamic selection of IDPs or dynamic URLs for them
It works on localhost even SLO, but when we deploy the service to a EKR cluster, for all backend services, at first we can load the login redirection like:
http://dev.org.com/services/sso/auth-sso?orgId=<id>
It goes to Okta, and we can login, when it tries to redirect back to our service, the "/" path doesn't get triggered and somehow the url it's redirected to
http://dev.org.com/auth-sso?error
We have tried, context-path setting in application.yml with no success, and lastly I read this reference
https://docs.spring.io/spring-security-saml/docs/1.0.4.BUILD-SNAPSHOT/reference/html/configuration-advanced.html#configuration-load-balancing
Also referenced to this questions:
Spring SAML 2.0 behind Nginx
but this spring saml version, doesn't recognize SAMLContextProviderLB or SAMLContextProviderImpl
Here some code:
build.gradle.kt:
constraints {
implementation("org.opensaml:opensaml-core:4.1.1")
implementation("org.opensaml:opensaml-saml-api:4.1.1")
implementation("org.opensaml:opensaml-saml-impl:4.1.1")
}
// spring
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-security")
// saml2
implementation("org.springframework.security.extensions:spring-security-saml2-core:2.0.0.M31")
implementation("org.springframework.security:spring-security-saml2-service-provider:5.7.4")
In my SamlConfig class, I define these beans:
securityFilterChain:
#Bean
fun securityWebFilterChain(http: HttpSecurity): SecurityFilterChain {
http
.authorizeRequests {
it
.mvcMatchers("/auth-sso").permitAll()
.mvcMatchers("/actuator/*").permitAll()
.anyRequest().authenticated()
}
.saml2Login {
it.loginPage("/auth-sso")
}
.saml2Logout{}
return http.build()
}
and a custom implementation of RelyingPartyRegistrationRepository, as LazyRelyingPartyRegistrationRepository
:
#Bean
protected fun relyingPartyRegistrations(): RelyingPartyRegistrationRepository? {
return LazyRelyingPartyRegistrationRepository()
}
And this is my /auth-sso
#GetMapping(value = ["/auth-sso"])
#ResponseBody
fun login(
#RequestParam(name = "orgId", required = true) orgId: String,
request: HttpServletRequest,
response: HttpServletResponse
) {
try {
val id = runBlocking {
findIdpConfigurationUseCase.execute(object : UserOrganizationLogin {
override val organizationId = organizationId
override val token: String? = null
})
}
val spInitiateUrl = "saml2/authenticate/$id"
log.debug("Redirecting to {}", spInitiateUrl)
response.sendRedirect(spInitiateUrl)
}
catch (e: Exception) {
log.error("Error preparing assertion info: {}", e)
response.sendRedirect(MessageFormat.format(environment.getRequiredProperty(HOME_URL), ""))
}
}
My "auth entry point" / which recieves the Principal crendentials and login the user calling a use case with internally create user and generates token:
#RequestMapping("/")
fun index(model: Model, #AuthenticationPrincipal principal: Saml2AuthenticatedPrincipal, response: HttpServletResponse) {
log.debug("User is authenticated!!!")
val loginInfo = runBlocking {
loginUserUseCase.execute(principal)
}
response.sendRedirect(
environment.getRequiredProperty(HOME_URL) +
MessageFormat.format(environment.getRequiredProperty(SSO_HOME_PARAMS), loginInfo.token, loginInfo.organizationId)
)
}

Related

How to pass header in Azure endpoint..?

I am using Azure API , URL getting below error please help on this issue. please share codesnip, how to change in web.config and endpoints.
The HTTP request is unauthorized with client authentication scheme
'Anonymous'. The authentication header received from the server was
'AzureApiManagementKey
realm="https:/azure.azure-api.net/MethodName",name="Ocp-Apim-Subscription-Key",type="header"'.
I know this is a very old question still, my answer would help someone faces the same issue.
The solution is to create a custom endpoint behavior where you add a custom message handler to the binding parameters.
In the custom message handler, please add your request headers. After this, use any of the binding technique (like basichttpsbinding or NetHttpsBinding) with security mode as "Transport" and MessageEncoding as "Text" for creating soap client object. Add custom endpoint behavior to the soap client.
public class CustomEndpointBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
bindingParameters.Add(new Func<HttpClientHandler, HttpMessageHandler>(x =>
{
return new CustomMessageHandler(x);
}));
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) { }
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }
public void Validate(ServiceEndpoint endpoint) { }
}
public class CustomMessageHandler : DelegatingHandler
{
public CustomMessageHandler(HttpClientHandler handler)
{
InnerHandler = handler;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
request.Headers.Add("xxxx", "abcde");
return base.SendAsync(request, cancellationToken);
}
}
The console app to consume the service.
static async Task Main(string[] args)
{
var client = GetSOAPClient();
try
{
var result = await client.MyOperation().ConfigureAwait(false);
if(result.Body != null && result.Body.status == "Success")
{
Console.WriteLine(result.Body.myValue);
}
}
catch (Exception ex)
{
Console.WriteLine(ex?.Message);
}
Console.ReadKey();
}
static MyServiceClient GetSOAPClient()
{
NetHttpsBinding binding = new NetHttpsBinding();
binding.Security.Mode = BasicHttpsSecurityMode.Transport;
binding.MessageEncoding = NetHttpMessageEncoding.Text;
EndpointAddress ea = new EndpointAddress(new Uri("https://myazureurl"));
var client = new MyServiceClient(binding, ea);
client.Endpoint.EndpointBehaviors.Add(new CustomEndpointBehavior());
return client;
}
}
This is complaining that your Subscription key is wrong. If you check the response body, it will give you a readable message of what the real problem is. Double check you are entering the correct subscription key for your Azure API access.
You get your subscription key from the Developer Portal under your profile menu. You can see an example of the subscription key being used in this article under the section "Call an operation from the developer portal": https://learn.microsoft.com/en-us/azure/api-management/api-management-get-started
Also, the 'The HTTP request is unauthorized with client authentication scheme 'Anonymous'.' part of the message is a red herring and a separate problem with how responses work.

Best practice for deploying spring boot application on Amazon

I've devloped a chat bot application using the Facebook Messenger platform.
I used Spring Boot with embedded Tomcat for the web platform.
The application should run on Amazon aws, open to the WWW, and to be used as a webhook for recieving callbacks from Messenger over https.
I need an advice how to secure the application, so it won't be hacked or flooded with requests that are not coming from Facebook.
I thought to make the application require secured (ssl) connection, but using the "security.require_ssl=true" in application.properties didn't do the work. Perhaps I don't know what is the meaning of this and how to configure it propertly.
Is there a best practice how to block requests which are not https requests? Or a way to block requests which are coming outside Messenger in the application level?
Thank you very much!
EDIT
In the meantime, I blocked requests from other IPs in application layer using the handler interceptor:
#Configuration
public class MyWebApplicationInitializer implements WebApplicationInitializer, WebMvcConfigurer{
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
if (! (request.getRemoteAddr().equals("173.252.88.66") || request.getRemoteAddr().equals("127.0.0.1")|| request.getRemoteAddr().equals("0:0:0:0:0:0:0:1"))){
logger.warn("Request is not coming from authorized remote address: " + request.getRemoteAddr()+". Rejecting");
response.getWriter().write("Unauthorized Address");
response.setStatus(401);
return false;
} else {
return true;
}
}
}
You should check the X-Hub-signature HTTP header available in the requests sent by Facebook to your webhook URL.
In your case, you may define a filter or interceptor for the verification of the signature. You can also do it in your controller as in the this example I found in RealTimeUpdateController.java from the spring social project.
private boolean verifySignature(String payload, String signature) throws Exception {
if (!signature.startsWith("sha1=")) {
return false;
}
String expected = signature.substring(5);
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
SecretKeySpec signingKey = new SecretKeySpec(applicationSecret.getBytes(), HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(payload.getBytes());
String actual = new String(Hex.encode(rawHmac));
return expected.equals(actual);
}
a lot to say so I am sure I will miss some points.
setting SSL is a first good thing but make sure you get a certificate. lets encrypt is a good thing if you dont want to pay for SSL certificate.
Just seeing aws provides an alternative to letsencrypt
Security Group You can see Security Group as something similar to a firewall so you can control which port is opened, external and internal flows.
Look at IAM which control who and how can get access to your AWS account
obvious : change your password. do not let default password for installation you could make on the instance
read some of https://aws.amazon.com/security/security-resources/ to get more information about what you can do
it won't be hacked or flooded with requests
sorry to say but most probably it will be - It does not need to be an advanced hacker to run scanner and scan IPs and check open ports / brute force login etc ...
Thanks to Guy Bouallet help I added the signature check.
I added it in my controller and not in the interceptor, to avoid the problem of How to read data twice in spring which seems a little complicated.
So here is it:
#RequestMapping(path = "/")
public void doHandleCallback(#RequestBody String body, #RequestHeader(value = "X-Hub-Signature") String signature) throws IOException {
if (!verifyRequestSignature(body.getBytes(), signature)){
logger.error ("Signature mismatch.");
throw new MismatchSignatureException(signature);
}
MessengerCallback callback = mapper.readValue(body, MessengerCallback.class);
logger.info("Incoming Callback: " + body );
for (EventData entry : callback.getEntry()) {
for (ReceivedMessagingObject message : entry.getMessaging()) {
if (message.isMessage() || message.isPostback()) {
doHandleMessage(message);
}
else if (message.isDelivery()){
doHandleDelivery(message);
}
}
}
}
private boolean verifyRequestSignature(byte[] payload, String signature) {
if (!signature.startsWith("sha1="))
return false;
String expected = signature.substring(5);
System.out.println("Expected signature: " + expected); //for debugging purposes
String hashResult = HmacUtils.hmacSha1Hex(APP_SECRET.getBytes(), payload);
System.out.println("Calculated signature: " + hashResult);
if (hashResult.equals(expected)) {
return true;
} else {
return false;
}
}
And this is the Exception handling class:
#ResponseStatus(value=HttpStatus.BAD_REQUEST, reason="Request Signature mismatch")
public class MismatchSignatureException extends RuntimeException {
private String signature;
public MismatchSignatureException(String signature) {
this.signature = signature;
}
#Override
public String getMessage() {
return "Signature mismatch: " + signature;
}

Returning Descriptive 401 Messages From Owin WebApi2

I have an asp.net 4.5 web api running using owin. Whenever an unauthorized request is made it returns a 401 with the following response as expected:
{"Message":"Authorization has been denied for this request."}
I would like to add additional detail to this response (expired token, invalid role, etc...) and implemented a custom [AuthorizeAttribute] based on this SO post.
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
var response = actionContext.Request.CreateResponse<MyError>
(new MyError() { Description = "This is why you're unauthorized" });
response.StatusCode = HttpStatusCode.Unauthorized;
actionContext.Response = response;
}
}
and then used it on my controllers like this:
[MyAuthorizeAttribute(Roles = "Foo")]
public class MyController : ApiController
{
...
}
which returns a 401 with the following response as expected:
{"Description": "This is why you're unauthorized"}
However, I do not see how to determine the reason the request is unauthorized from the HttpActionContext passed to MyAuthorizeAttribute.HandleUnauthorizedRequest. For instance, when I'm debugging locally and make a request with an expired token it throws a SecurityTokenExpiredException explaining IDX10223: Lifetime validation failed. The token is expired. ValidTo: '...' Current time: '...'. or with a invalid audience it throws a SecurityTokenInvalidAudienceException explaining Message=IDX10214: Audience validation failed. Audiences: '...'. Did not match: validationParameters.ValidAudience: 'null' or validationParameters.ValidAudiences: '...'. I've set several breakpoints in my Startup.cs yet have been unable to even catch one of these exceptions before they're thrown.
How can I determine the specific reason a request is unauthorized using owin middleware?
Haven't figured out how to identify expirations nor invalid audiences and such, but I ended up using this to at least return 403s based on roles.
You can customize the message ("You must have role X to access this action ...") using the example in my question above.
public class MyAuthorizeAttribute : AuthorizeAttribute
{
protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
{
base.HandleUnauthorizedRequest(actionContext);
if (actionContext.RequestContext.Principal != null &&
actionContext.RequestContext.Principal.Identity.IsAuthenticated &&
Roles != null)
{
if (!Roles.Split(',').Any(x =>
actionContext.RequestContext.Principal.IsInRole(x.Trim())))
{
actionContext.Response.StatusCode = HttpStatusCode.Forbidden;
}
}
}
}

How to authenticate an access token using OWIN OAuthBearerAuthentication?

What I want:
A token generator use OAuthAuthorizationServer and token consumer use OAuthBearerAuthentication (authenticate the access token).
Use OWIN pipeline to manage all stuff, token stuff and web api stuff.
What about the code:
public void Configuration(IAppBuilder app)
{
app.UseOAuthAuthorizationServer(new OAuthAuthorizationServerOptions
{
AuthorizeEndpointPath = "/Authorize",
AllowInsecureHttp = true,
Provider = new OAuthAuthorizationServerProvider
{
OnGrantCustomExtension = GrantCustomExtension,
OnValidateClientRedirectUri = ValidateClientRedirectUri,
OnValidateClientAuthentication = ValidateClientAuthentication,
}
});
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
{
Provider = new OAuthBearerAuthenticationProvider
{
//Handles applying the authentication challenge to the response message.
ApplyChallenge=MyApplyChallenge,
//Handles processing OAuth bearer token.
RequestToken=MyRequestToken,
//Handles validating the identity produced from an OAuth bearer token.
ValidateIdentity = MyValidateIdentity,
}
});
app.UseWebApi(new WebApplication3.Config.MyWebApiConfiguration());
}
What's the question:
The 3 properties of OAuthBearerAuthenticationProvider,
ApplyChallenge, RequestToken and ValidateIdentity. How to
implement the 3 methods?
In the token authetication process, What I thought is to decrypt the access token, validate the token from the client, and if the token is validated, put the identities of the token to the HttpContext.Current.User.
The OAuthBearerAuthenticationProvider's responsibility is to fulfill the
previous steps. Am I right?
As you know, UseOAuthAuthorizationServer has the job of authenticating the user. Then, UseOAuthBearerAuthentication has the job of ensuring that only authenticated users can access your application. Often, these two jobs are assigned to different web application. It looks like your application is doing both.
There are certainly some cases were you need to override the default OAuthBearerAuthenticationProvider. Maybe you do, or maybe you don't In my case, ApplicationCookie didn't quite fit the scenario. So, I'm storing a 3rd party JWT token in a cookie, rather than the header, and using it to indicate that the user is authenticated to a web application. I also needed to redirect to my own login page, rather than provide a 401.
Here's an implementation that does both:
public class CustomOAuthBearerProvider : IOAuthBearerAuthenticationProvider
{
public Task ApplyChallenge(OAuthChallengeContext context)
{
context.Response.Redirect("/Account/Login");
return Task.FromResult<object>(null);
}
public Task RequestToken(OAuthRequestTokenContext context)
{
string token = context.Request.Cookies[SessionKey];
if (!string.IsNullOrEmpty(token))
{
context.Token = token;
}
return Task.FromResult<object>(null);
}
public Task ValidateIdentity(OAuthValidateIdentityContext context)
{
return Task.FromResult<object>(null);
}
}
I didn't need to do anything special in ValidateIdentity, but I needed to satisfy the interface.
To wire this up, tell your app to use JwtBearerAuthentication with your provider:
// controllers with an [Authorize] attribute will be validated with JWT
app.UseJwtBearerAuthentication(
new JwtBearerAuthenticationOptions
{
AllowedAudiences = audiences.ToArray(),
IssuerSecurityTokenProviders = providers.ToArray(),
Provider = new CookieOAuthBearerProvider()
}
);

Asp.Net Identity - How to set Unauthorized programmatically?

I have a Web API 2 application which uses Asp.Net Identity for Authentication and Authorization. I also have a custom Message Handler to do an additional custom check to finalize the authentication (as well as parse some API data that is necessary to connect to the right schema on a multi-tenancy data store).
Here is my working code for the message handler:
public class AuthenticationHeadersHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Headers.Contains("Authorization"))
{
// Authenticate
var auth = request.GetOwinContext().Authentication.AuthenticateAsync(OAuthDefaults.AuthenticationType);
// Get user ID from token
identityId = auth.Result.Identity.GetUserId();
// Please note, the oAuth token would have successfully authenticated by now
// ... Do some custom authentication and data gathering
if (failedCheck)
{
// If user fails checks, I would like to force Asp.Net Identity to
// return 401 not authorized here, or flag the request as not authorized
}
}
}
}
Considering the code above, how can I manually flag the request as unauthorized even if it passed the initial authentication?
I think this should work:
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Headers.Contains("Authorization"))
{
// Authenticate
var auth = request.GetOwinContext().Authentication.AuthenticateAsync(OAuthDefaults.AuthenticationType);
// Get user ID from token
identityId = auth.Result.Identity.GetUserId();
// Please note, the oAuth token would have successfully authenticated by now
// ... Do some custom authentication and data gathering
if (failedCheck)
{
// Return 401 not authorized here.
return new HttpResponseMessage(HttpStatusCode.Unauthorized);
}
}
// If we got here send an HTTP status of 200
return new HttpResponsMessage(HttpStatusCode.OK);
}

Resources