I am trying to push/get values to salesforce using camel with java. When I try to send data I get the following error :
HTTP protocol violation: Authentication challenge without WWW-Authenticate header
The strange part is that it work like a charm when I subscribe to data with routes /data/AccountChangeEvent
public class MyRouteBuilder extends RouteBuilder {
#Override
public void configure() {
from("salesforce:/data/AccountChangeEvent?replayId=-2")
.bean(clientService, "accountChange");
from("direct:updateSalesforce")
.to("salesforce:createSObject?sObjectName=Account");
}
}
public class SalesforcePublisherService {
#Autowired
CamelContext camelContext;
public void publishToSalesforce(String endpointUri, Object body) {
Map<String, Object> values = new HashMap<>();
// [...] Putting some values
camelContext.createProducerTemplate().requestBody("direct:updateSalesforce", values);
}
}
I found out what the problem was.
First I found out that the error was really different from what was written. In order to investigate on that part I activated Jetty's logs with :
-Dorg.eclipse.jetty.util.log.class=org.eclipse.jetty.util.log.StdErrLog
-Dorg.eclipse.jetty.LEVEL=DEBUG
Then I reproduce the call on my postman and I got the message from this 401 error. After some investigation the problem was related with how I generated my refresh token:
I shouldn't have used the scope "refresh_token" when generating the salesforces token (actually no scope is needed).
Related
I am trying to accomplish each time when I start my app, to get from the RabbitMQ API, all the queues and exchanges that exist. But unfortunately I get the following error:
org.springframework.web.client.HttpClientErrorException$Unauthorized: 401 Unauthorized: [no body]
My code:
application properties file :
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.addresses=localhost:8080/api/
rabbitmq-api-url = http://localhost:8080/api/
Component that executes each time on startup:
#Component
public class StartupCLass implements ApplicationListener<ApplicationReadyEvent> {
#Value("${rabbitmq-api-url}")
private String endpoint;
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<String> response = restTemplate.getForEntity(endpoint+"queues",String.class);
}
}
What I am trying to accomplish is the following:
I have some queues and exchanges defined on my RabbitMQ server. I have also exported the definitions json file and modified it with renaming some of the queues/exchanges. I want to check when starting the app, which queue or exchange has been renamed regarding the json file and the current state of the rabbit mq server (that's why I want to get from the API the queues)
401 means unauthorized. So you have to pass username and password in the request.
restTemplate.getInterceptors().add(
new BasicAuthorizationInterceptor("username", "password"));
Read more here:
https://www.baeldung.com/how-to-use-resttemplate-with-basic-authentication-in-spring
Calling the Firebase API is successful from Postman but Apache Camel-Spring Boot app returns an error.
API: https://fcm.googleapis.com/fcm/notification?notification_key_name=expectedvalue
Http Method: GET
Headers:
Authorization: expectedvalue
Content-Type: application/json
project_id: expectedvalue
Response Code: 400
Response Text: Bad Request
Response: {"error":"NOT_A_JSON_REQUEST"}
Calling from the Java app with below syntax:
.toD(https://fcm.googleapis.com/fcm/notification?notification_key_name=expectedvalue)
All required headers are set. Body is empty. HttpMethod also set as GET.
Update: --Added code--
public class MyRouteBuilder extends RouteBuilder{
#Override
public void configure() throws Exception {
from("direct:notifyroute")
.bean(FirebaseNM.class, "getNotificationKey")
.setHeader("CamelHttpMethod", constant("GET"))
.toD("https://fcm.googleapis.com/fcm/notification?notification_key_name=expectedvalue").log("${body}");
//.to("https://fcm.googleapis.com/fcm/notification?notification_key_name=expectedvalue").log("${body}"); //Same error is thrown when I use 'to'
}
}
public class FirebaseNM{
public void getNotificationKey(Exchange exchange){
exchange.getIn().removeHeaders("*"); //Code throws same error with or without this
exchange.removeProperties("*"); //Code throws same error with or without this
exchange.getIn().setBody(""); //Code throws same error with or without this
exchange.getIn().setHeader("Authorization", SERVER_KEY_VALUE);
exchange.getIn().setHeader("Content-Type", "application/json");
exchange.getIn().setHeader("project_id", PROJECT_ID_VALUE);
}
}
Alternate code that doesn't work:
rest().get("/nKey").route().removeHeaders("*")
.setHeader("Authorization", constant("server-key-value"))
.setHeader("Content-Type", constant("application/json"))
.setHeader("project_id", constant("project-id-value"))
.to("https://fcm.googleapis.com/fcm/notification?notification_key_name=key-name-value")
.endRest();
Update: --Question--
Is there a way to view error/exception details on Firebase console or to get detailed response from the API?
Update: --Question 2--
Firebase cloud messaging documentation does not mention the error(in the Enums columns) I'm receiving.
https://firebase.google.com/docs/reference/fcm/rest/v1/ErrorCode
Any other documentation I should look at?
Have you considered https://firebase.google.com/docs/functions/writing-and-viewing-logs? If you have access to the server this might be an option.
I came across an Issue where public urls won't work in Spring security, when you already have an SessionID which is not valid anymore.
Example:
I have the user-register page and gave it a permitAll access like so:
http.authorizeRequests().antMatchers("/register**").permitAll();
http.authorizeRequests().anyRequest().authenticated();
http.formLogin().loginPage("/login").permitAll();
For my Session settings i have:
http.sessionManagement().invalidSessionUrl("/login?logoutcause=sessiontimeout");
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
http.sessionManagement().sessionAuthenticationErrorUrl("/login");
http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true);
http.sessionManagement().sessionFixation().newSession();
If i have a sessionID from a previous session, which is maybe an old and invalid one, and i hit the Route "/register", spring complains about the invalid session ID and redirects me to "/login".
Just to mention it: Everything else, like login, ressource management, protected urls and logout is working as expected with the configuration.
Reproducing this: Use Redis-Session management in Spring. Got to login page, flush redis db with console. Access the register page directly in browser -> redirected to login because of invalid session id.
o.s.s.w.s.SessionManagementFilter : Requested session ID 8ad2e166-bc21-4646-8390-ad8d1043baec is invalid.
w.s.SimpleRedirectInvalidSessionStrategy : Starting new session (if required) and redirecting to '/login?logoutcause=sessiontimeout'
o.s.s.w.DefaultRedirectStrategy : Redirecting to '/login?logoutcause=sessiontimeout'
Why does Spring even check the session id for a route that have "public" access?
The next Step:
if i fully disable any security checks on the route itself, sadly the required ressources like js and css assets trigger the same behavior and either i get redirected to login, or the assets simply do not get delivered (both no option :D )
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/register/**");
super.configure(web);
}
My Solution and Workaround
I disabled the following config which solved all my problems
// DISABLED
// http.sessionManagement().invalidSessionUrl("/login?logoutcause=sessiontimeout");
My Question
This can not be the best way to do it, right?
What is the better and more secure way to do it.
Please help me to understand why this is done this way by spring, or what i configured the wrong way.
I have solved the same issue by adding the following config:
.antMatchers("/login-invalid").permitAll()
I had same issue, and solved like below.
0. pre-requisite
jdk 17
spring-boot 2.7
1. create custom InvalidSessionStrategy
#Component
public class MyInvalidSessionStrategy implements InvalidSessionStrategy {
private final InvalidSessionStrategy simpleRedirectStrategy;
private final InvalidSessionStrategy requestedUrlRedirectStrategy;
private final HandlerMappingIntrospector handlerMappingIntrospector;
public MyInvalidSessionStrategy(HandlerMappingIntrospector handlerMappingIntrospector) {
this.simpleRedirectInvalidSessionStrategy = new SimpleRedirectInvalidSessionStrategy("/login?logoutcause=sessiontimeout");
this.requestedUrlRedirectStrategy = new RequestedUrlRedirectInvalidSessionStrategy();
this.handlerMappingIntrospector = handlerMappingIntrospector;
}
#Override
public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response)
throws IOException {
var matcher = new MvcRequestMatcher(handlerMappingIntrospector, "/register/**");
if (matcher.matches(request))) {
requestedUrlRedirectStrategy.onInvalidSessionDetected(request, response);
} else {
simpleRedirectStrategy.onInvalidSessionDetected(request, response);
}
}
}
2. configure spring-securiry and register custom InvalidSessionStrategy
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
SecurityFilterChain filterChain(
HttpSecurity http,
MyInvalidSessionStrategy invalidSessionStrategy)
throws Exception {
return http
.login(...)
.logout(...)
.sessionManagement(
configurer -> configurer
.invalidSessionStrategy(invalidSessionStrategy))
.build();
}
}
Introduction
I have a custom ErrorController implementation to handle all exceptions and create a custom error message:
#RestController
public class CustomErrorController implements ErrorController {
#RequestMapping("/error")
public ResponseEntity handleError(HttpServletRequest request) {
HttpStatus status = HttpStatus.valueOf((Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
String body = ... // Code to calculate the body based on the request
return ResponseEntity.status(status.value()).body(body);
}
#Override
public String getErrorPath() {
return "/error";
}
}
Problem Description
However, this also enables access to the path /error which I would like to disable.
When trying to access https://localhost:8443/error, a NullPointerException is thrown by the HttpStatus.valueOf() method, because the status code could not be extracted. As a result, an Internal Server Error (500) is created, which is run through my custom controller, creating a custom 500 error response.
Temporary Fix
As a workaround, I can check if the status code attribute exists, and handle that case separately. But it is a work-around and not an actual fix.
The Question
What I would like is to disable the /error mapping from external access. If attempted, the result should be Not Found (404) which is then run through my custom controller.
Is the #RequestMapping("/error") necessary or could this be implemented differently?
Edits
Spring Boot version is 2.1.2.RELEASE
The server.error.whitelabel.enabled property is set to false. The issue does not seem to be related with it.
I have a Spring webservice #Controller class with a #MessageMapping annotated method as follows:
#MessageMapping("/trade")
public void executeTrade(MarketOrderRequest trade, Principal principal) {
trade.setUserID(principal.getName());
logger.debug("Trade: " + trade);
this.tradeService.executeTrade(trade);
}
I am sending a JSON string message built using the same MarketOrderRequest POJO as is accepted by the server method. With some Key:Value pairs which are set null (but are still present).
The WebSocketConfig class has configured the following endpoints:
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/app");
}
When i try to send a message to this messagemapping using this code:
MarketOrderRequest request = new MarketOrderRequest();
//{set request variables..}
StompHeaders someHeaders = new StompHeaders();
someHeaders.putAll(sessionHeaders);
someHeaders.setDestination("/app/trade");
session.send(someHeaders, request);
With headers:
{Cookie=[JSESSIONID=8421F536B639126F84F12E655375D790; Path=/spring-websocket-portfolio/; HttpOnly], version=[1.2], heart-beat=[0,0], user-name=[fabrice], destination=[/app/trade]}
The server then prints that a method cannot be found for the request:
Searching methods to handle SEND /app/trade session=397da625042343b4bac1c913b6d8ec22 application/json;charset=UTF-8
payload={"uuid":null,"symbol":"EUR/USD","price":1.10182,"side":"1","qty":50000,"quoteID"...(truncated)
WebSocketAnnotationMethodMessageHandler[DEBUG] - No matching methods.
The server code is lifted from this project and altered slightly to suit my needs: link
I have added some role-based web socket security in an AbstractSecurityWebSocketMessageBrokerConfigurer implementation class as follows:
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
.nullDestMatcher().authenticated()
.simpSubscribeDestMatchers("/user/queue/errors").permitAll()
.simpDestMatchers("/app/**").hasAnyRole("roleA", "roleB", "roleC")
//{some more subscribe dest matchers by role which are working}
}
would this possibly effect the WebSocketAnnotationMethodMessageHandler's attempts to map the request? It is pretty much the only change I have made to the config. My subscribe mappings are working perfectly.
To me it seems that there is a problem finding the method due to either the JSON or Principal parameters. I am sending the correct object type so is this possibly a problem with the User principal? Thanks
There was an error in my WebSocketConfig class.
The #componentscan annotation had the wrong package name. I updated the name to the correct value ( the name of my base package eg "com.my.project" ). Now during deployment in the logs, I can see the controller resources being mapped to the methods in my class.
Eg log output for one method:
Mapped "{[/order],messageType=[MESSAGE]}" onto public void com.my.project.web.PortfolioController.executeOrder(tradeObjects.OrderRequest,java.security.Principal)