SSO with saml2 : Http post-bind in local metadata is missing - spring-security-saml2

I followed the spring tutorial for setting up SSO with saml. My filterChain is looking like this:
#Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
val authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder::class.java)
val samlUserDetailsServiceImpl = SAMLUserDetailsServiceImpl()
authenticationManagerBuilder.userDetailsService<UserDetailsService>(samlUserDetailsServiceImpl)
val authenticationManager = authenticationManagerBuilder.build()
http
.authenticationProvider(samlAuthenticationProvider(samlUserDetailsServiceImpl))
http
.httpBasic()
.authenticationEntryPoint(samlEntryPoint());
http
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter::class.java)
.addFilterAfter(loginSuccessFilter(authenticationManager), BasicAuthenticationFilter::class.java)
http
.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers("/saml*", "/*.gif", "/*.jpg", "/*.jpeg", "/*.png", "/*.jsp", "/*.js", "/*.css", "/console*").permitAll().anyRequest().authenticated()
.and()
.authenticationManager(authenticationManager)
return http.build()
when logging in it jumps into the loginSuccessFilter (inherits from samlprocessingfilter) and there it gets stuck by checking the metadata post-bind, which is missing at this point and a SAMLException is thrown. The local metadata xml includes the post-bind and the base url is set in the metadataGenerator:
#Bean
open fun metadataGenerator(): MetadataGenerator? {
val metadataGenerator = MetadataGenerator()
metadataGenerator.entityId = "com:project:brxm"
metadataGenerator.entityBaseURL = "http://localhost:8080/cms"
metadataGenerator.extendedMetadata = extendedMetadata()
metadataGenerator.isIncludeDiscoveryExtension = false
metadataGenerator.setKeyManager(keyManager())
return metadataGenerator
}
I don't know why the post-bind is getting missing ?
Thanks for any hint in advance

Related

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

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

Json data type response of IP rate limit API

I am using middleware provided by AspNetCoreRateLimit to rate limit incoming requests to an ASP.NET Core 2.x REST API web application.
Currently this library returns html responses for rejected requests. How can I make it return json responses instead?
You could custom your response in the IpRateLimitMiddleware.
IpRateLimitMiddleware
public class MyIpRateLimitMiddleware : IpRateLimitMiddleware
{
public MyIpRateLimitMiddleware(RequestDelegate next
, IOptions<IpRateLimitOptions> options
, IRateLimitCounterStore counterStore
, IIpPolicyStore policyStore
, IRateLimitConfiguration config
, ILogger<IpRateLimitMiddleware> logger)
: base(next, options, counterStore, policyStore, config, logger)
{
}
public override Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitRule rule, string retryAfter)
{
//return base.ReturnQuotaExceededResponse(httpContext, rule, retryAfter);
var message = new { rule.Limit, rule.Period, retryAfter };
httpContext.Response.Headers["Retry-After"] = retryAfter;
httpContext.Response.StatusCode = 200;
httpContext.Response.ContentType = "application/json";
return httpContext.Response.WriteAsync(JsonConvert.SerializeObject(message));
}
}
Configure the middlware in Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//app.UseIpRateLimiting();
app.UseMiddleware<MyIpRateLimitMiddleware>();
//your rest middlware
}
Read the docs
If the request gets blocked then the client receives a text response like this:
Status Code: 429
Retry-After: 58
Content: API calls quota exceeded! maximum admitted 2 per 1m.
You can customize the response by changing these options HttpStatusCode and QuotaExceededMessage, if you want to implement your own response you can override the IpRateLimitMiddleware.ReturnQuotaExceededResponse. The Retry-After header value is expressed in seconds. (emphasis mine)

Modify request/observable between retry

I have rxjava(observable) + retrofit2 together to make http requests to my application. I create OkHttpClient once for app and don't want to recreate it.
I have retry logic implemented on observable level - using filter, retryWhen together.
What I want - if request finished with error from server side, i want to retry it and send additional header with it.
So, I dont understand neither how can I modify observable inside retryWhen nor how to get the knowledge about observable from interceptor level.
Any ideas and/or knowledge about possible approaches?
You need to create your own Interceptor implementation where you can setup the header logic. Something like
public class FallbackInterceptor implements Interceptor {
static String header1Key = "key1";
static String extraHeaderKey = "key2";
String header1, extraHeader;
boolean useextraheader = false;
public FallbackInterceptor(string header1, string extraheader) {
this.header1 = header1;
this.extraheader = extraheader;
}
public void setUseExtraHeader(boolean useextraheader) {
this.userextraheader = useextraheader;
}
#Override
public Response intercept(Interceptor.Chain chain) throws IOException {
Request original = chain.request();
// Add request headers
Request.Builder requestBuilder = original.newBuilder().header(header1Key, header1);
if (useExtraHeader) {
requestBuilder = requestBuilder.header(extraHeaderKey, extraHeader)
}
Request newRequest = requestBuilder.method(original.method(), original.body())
.build();
// Return the response
return chain.proceed(request);
}
}
Add this to an okhttpclient and have your retrofit instance use this this. You can then manipulate the extraheader flag in your retry logic.

Apache Camel - from jms to http

I have a spring-boot project using Apache Camel.
I want to read a message from an activemq queue containing a file and send it to a web server.
I am trying to find the proper way to do this.
I believe I can make something like:
from("activemq:queue").bean(MyBean.class, "process")
And manually build a http request but I can't help thinking there is probably a better way to do it. Like:
from("activemq:queue").bean(MyBean.class, "process")
.setHeader(Exchange.HTTP_METHOD,constant("POST"))
.to("http://localhost:8080/test");
But I don't know how to manipulate the "exchange" to have a valid http Message.
MyBean receives an Exchange object containing a JmsMessage. I see that there is also a HTTPMessage but I don't think I should build that manually. (It requires HTTPRequest and Response objects I am not sure how to get.)
Can someone shed some light on this problem?
Update
I am going for the bean solution.
from("activemq:queue").bean(MyBean.class, "sendMultipart");
public void sendMultipart(Exchange exchange) {
ByteArrayInputStream in = new ByteArrayInputStream((byte[]) exchange.getIn().getBody());
InputStreamBody contentBody = new InputStreamBody(in, ContentType.create("application/octet-stream"), "filename");
HttpEntity entity = MultipartEntityBuilder
.create()
.addPart("file", contentBody)
.build();
HttpPost httpPost = new HttpPost("http://localhost:8080/upload/");
httpPost.setEntity(entity);
CloseableHttpClient httpClient = HttpClients.createDefault();
try {
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
System.out.println(httpResponse);
} catch (IOException e) {
e.printStackTrace();
}
}
Updated post
I found this http://hilton.org.uk/blog/camel-multipart-form-data. It allows you to leverage the camel http component.
"jms:queue/SomeQ" ==> {
process(toMultipart)
setHeader(Exchange.CONTENT_TYPE, "multipart/form-data")
process((e: Exchange) => e.getIn.setHeader(Exchange.HTTP_URI,"http://localhost:8111/foo"))
to ("http:DUMMY")
}
def toMultipart(exchange: Exchange): Unit = {
val data = exchange.in[java.io.File]
val entity = MultipartEntityBuilder.create()
entity.addBinaryBody("file", data)
entity.addTextBody("name", "sample-data")
// Set multipart entity as the outgoing message’s body…
exchange.in = entity.build
}
Side note: this would really be a nice use-case to try-out reactive streams.
Original post
I am still having some problems understanding your actual problem. Perhaps some code might help:
I am now assuming you are receiving bytes in some character encoding and want to sent it onward to a dynamically established http-endpoint.
Is the following something you are looking for (code is in camel's scala-dsl)
"jms:queue/SomeQ" ==> {
convertBodyTo(classOf[String],"UTF-32" )
process((e: Exchange) => e.in = e.in[String].toUpperCase + "!")
process((e: Exchange) => e.getIn.setHeader(Exchange.HTTP_URI,"http://localhost:8111/foo"))
to ("http:DUMMY")
}
It will be send as an HTTP POST as the body is not null.
I receive it all well on another endpoint i created to ensure the code above is correct:
"jetty:http://localhost:8111/foo" ==> {
log("received on http 8111 endpoint ${body}")
}

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

Resources