Why #RequestHeader does not work with #MessageMapping? - spring

My goal is to get the RequestHeader e.g Authorization from a message published by Stomp.
The flow
1. Subscribe
Front End <-> /topic/javainuse
2. Publish Message
Front End <-> /app/chat.newUser
The Spring Boot WebSocket's source code
#MessageMapping("/chat.newUser")
#SendTo("/topic/javainuse")
public WebSocketChatMessage newUser(#RequestHeader(value = "Authorization") String authorization, WebSocketChatMessage message, SimpMessageHeaderAccessor headerAccessor) {
System.out.println("Authorization: " + authorization);
headerAccessor.getSessionAttributes().put("username", message.getSender()); // dependency of WebSocketChatEventListener to handle SessionDisconnectEvent.
return message;
}
The Front End's source code, written in JavaScript
const publish = () => {
stompClient.publish({
destination: '/app/chat.newUser',
body: JSON.stringify({
sender: 'User A',
type: 'newUser',
}),
headers: {
Authorization: 'Bearer a',
},
skipContentLengthHeader: true,
});
};
My expected result is it will print out the Authorization's key value.
My actual result is
Authorization: {"sender":"User A","type":"newUser"}
==================================================
Related question but not the main topic
==================================================
I am worried that my practice for the RestController is wrong since my practice can't be applied to WebSocket:
What are the advantages of using Spring Security over manual #RequestHeader annotation?
Currently, I use #RequestHeader inside a RestController e.g
#PostMapping
public ResponseEntity<Object> something(#RequestHeader(value = "Authorization) String authorization) {
boolean isValid = customValidatorService.isValid(authorization.split(" ")[1]);
// do something
if (isValid === false) {}
else (isValid === true) {}
...
}
Why should you use Spring Security to handle RestController and WebSocket instead of using the #RequestHeader(value = "Authorization") annotation and a Custom Validator Service?
If you protect the websocket endpoint, is validating e.g by do a sql eury SELECT session from a table for every published message sent to a #MessageMapping a correct practice?
Disclaimer: I have not use Spring Security at all.

Related

spring security & axios - email: "NONE_PROVIDED"

Logging in via postman works fine ONLY when using application/x-www-form-urlencoded
Trying to do the same request with raw-json doesn't work.
Anyway I'm trying to implement the login to my react app. When i have the request set as:
const response = await Axios.post(`${window.ipAddress.ip}/login`,
JSON.stringify({ email: email.toLowerCase(), password: password }),
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
);
From this in springboot the UserService class gets called which shows:
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = findUserByEmail(email);
if ( user != null){
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
user.getRoles().forEach(role -> { authorities.add(new SimpleGrantedAuthority(role.getName()));});
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(), authorities);
}
else{ throw new EntityNotFoundException("Entity not found"); }
}
It then shows email as "NONE_PROVIDED" and ultimately failing.
The second issue is I don't actually have a /login route in controller code when searching through all files so I'm unsure at which point it actually calls this method through springboot.
The only changes I made from the default spring-security implementation is the use of my own class where I use "email" in place of "userName".
Any suggestions are welcome.
Edit:
the working login function via postman
Springboot was expecting encoded form which is different from my other requests , this answer shows how to properly outline x-www-for-urlencoded
axios post request to send form data
having the request as:
var bodyFormData = new FormData();
bodyFormData.append('email', email.toLowerCase());
bodyFormData.append('password', password);
const response = await Axios({
method: "post",
url: `${window.ipAddress.ip}/login`,
data: bodyFormData,
headers: { "Content-Type": "multipart/form-data" },
})

rsocket-js routing fireAndForget to Spring Boot #MessageMapping

As I understand RSocket-JS supports routing messages using encodeCompositeMetadata and encodeRoute, however, I cannot get the server to accept a fireAndForget message. The server constantly logs the following message:
o.s.m.r.a.support.RSocketMessageHandler : No handler for fireAndForget to ''
This is the server mapping I am trying to trigger:
#Controller
public class MockController {
private static final Logger LOGGER = LoggerFactory.getLogger(MockController.class);
#MessageMapping("fire-and-forget")
public Mono<Void> fireAndForget(MockData mockData) {
LOGGER.info("fireAndForget: {}", mockData);
return Mono.empty();
}
}
This is the TypeScript code that's trying to make the connection:
client.connect().subscribe({
onComplete: socket => {
console.log("Connected to socket!")
socket.fireAndForget({
data: { someData: "Hello world!" },
metadata: encodeCompositeMetadata([[MESSAGE_RSOCKET_ROUTING, encodeRoute("fire-and-forget")]])
});
},
onError: error => console.error(error),
onSubscribe: cancel => {/* call cancel() to abort */ }
});
I've also tried adding the route in other ways (metadata: String.fromCharCode('route'.length)+'route') I found on the internet, but none seem to work.
What do I need to do to format the route in a way that the Spring Boot server recognizes it and can route the message correctly?
Binary only communication when using CompositeMetadata
Please make sure that you have configured your ClientTransport with binary codecs as follows:
new RSocketWebSocketClient(
{
url: 'ws://<host>:<port>'
},
BufferEncoders,
),
Having Binary encoders you will be able to properly send your routes using composite metadata.
Also, please make sure that you have configured metadataMimeType as:
...
const metadataMimeType = MESSAGE_RSOCKET_COMPOSITE_METADATA.string; // message/x.rsocket.composite-metadata.v0
new RSocketClient<Buffer, Buffer>({
setup: {
...
metadataMimeType,
},
transport: new RSocketWebSocketClient(
{
url: 'ws://<host>:<port>',
},
BufferEncoders,
),
});
Note, once you enabled BufferEncoders your JSONSeriallizer will not work and you would need to encode your JSON to binary yours selves ( I suggest doing that since in the future versions we will remove support of Serializers concept completely). Therefore, your request has to be adjusted as it is in the following example:
client.connect().subscribe({
onComplete: socket => {
console.log("Connected to socket!")
socket.fireAndForget({
data: Buffer.from(JSON.stringify({ someData: "Hello world!" })),
metadata: encodeCompositeMetadata([[MESSAGE_RSOCKET_ROUTING, encodeRoute("fire-and-forget")]])
});
},
onError: error => console.error(error),
onSubscribe: cancel => {/* call cancel() to abort */ }
});
Use #Payload annotation for your payload at spring backend
Also, to handle any data from the client and to let Spring know that the specified parameter argument is your incoming request data, you have to annotate it with the #Payload annotation:
#Controller
public class MockController {
private static final Logger LOGGER = LoggerFactory.getLogger(MockController.class);
#MessageMapping("fire-and-forget")
public Mono<Void> fireAndForget(#Payload MockData mockData) {
LOGGER.info("fireAndForget: {}", mockData);
return Mono.empty();
}
}

How to send multipart request in angular 6?

I need to send a multipart request.
When I am submitting the form I am getting below error from backend,
Resolved exception caused by Handler execution: org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported
I am able to hit from Advanced rest client, but facing issue with angular.
On backend side below is The REST endpoint.
#PostMapping("/createCIF")
public Response < Map < String, Object >> createCIF(
#RequestPart("actDocs") List < MultipartFile > actDocs,
#Valid #RequestPart("createCIFReq") CreateCIFReq createCIFReq,
HttpServletRequest request) throws URISyntaxException {
}
Below is the angular side code in component.ts file.
let formData = new FormData();
formData.append('actDocs', this.userInfoService.mulitPartFileArray);
formData.append('createCIFReq', JSON.stringify(this.userInfo));
this.userInfoService.createCif(formData)
.pipe(first())
.subscribe(
data => {
}
}
Angular side Service level code
createCif(formData): any {
return this.http.post<any>(this.url + 'createCIF',
formData)
.pipe(map(cif => {
return cif;
}));
}
I got stuck on this issue an entire day.
Angular seems to fail to set a correct content-type to the JSON part.
I managed to solve this by creating a Blob :
let formData = new FormData();
formData.append('actDocs', this.userInfoService.mulitPartFileArray);
formData.append(
'createCIFReq',
new Blob([JSON.stringify(this.userInfo)], {type: 'application/json'})
);
Hope it helps.

When rest controller return null body, ajax go to the error method

env: spring boot 2.0,jackson serialization
my rest controller method will not return any body,just like ResponseEntity.ok().build(),but the ajax don't go to success method,it will go to error emthod,and the error shows "Unexpected end of JSON input"
i tried this:
1. add the properties into application.properties : spring.jackson.default-property-inclusion: NON_NULL, it still go to error method;
2. update return result like this ResponseEntity.ok("{}"),it will ok;
#PostMapping(value = "/import")
public ResponseEntity<Void> importRecipients(#ModelAttribute UserRecipientDto userRecipientDto,
#CurrentUser #ApiIgnore User user,
#RequestParam ImportType importType,
#RequestParam String name,
#RequestParam List<Long> ids) {
Collection<MailRecipient> count = this.service.getRecipients(userRecipientDto, importType, ids);
UserMailRecipientGroup userMailRecipientGroup = new UserMailRecipientGroup(name, USER_RECIPIENT, count, user);
userMailRecipientGroupService.saveMailRecipientGroup(userMailRecipientGroup);
return ResponseEntity.ok().build();
}
application.yml:
spring:
jackson:
serialization:
write-dates-as-timestamps: true
write_dates_with_zone_id: true
time-zone: GMT
default-property-inclusion: NON_NULL
ajax code block:
url: url,
type: type,
contentType: "application/json;charset=utf-8",
headers: {},
data: param,
async: true,
dataType: "json",
success: function success(data) {
cb(data);
},
error: function error(request, status_code, text_error) {}
i expect this:
when my rest method return ok().build(), the ajax will go to success method.
i guess ajax convert result to json occur some error, may be some spring boot properties will do it, but i haven't found yet.
thanks!!
Not clear what class your controller is extending and the import of your ok() method.
Maybe try with ResponseEntity
ResponseEntity.ok().build()
From return ok().build() you are just returning the Http Status 200 OK Status. Isn't it?
What exactly you have to return to client?
If it is some response you have go for
return ResponseEntity.ok().build() or
return Response.ok().entity(someEntity).build();
and im seeing the return type is ResponseEntity from the code.
Please eloborate the problem.
Edit:
Code would be look like
#PostMapping(value = "/import")
public ResponseEntity<String> importRecipients(#ModelAttribute UserRecipientDto userRecipientDto,
#CurrentUser #ApiIgnore User user, #RequestParam ImportType
importType, #RequestParam String name,
#RequestParam List ids) {
Collection count = this.service.getRecipients(userRecipientDto, importType, ids);
UserMailRecipientGroup userMailRecipientGroup = new UserMailRecipientGroup(name, USER_RECIPIENT, count, user);
userMailRecipientGroupService.saveMailRecipientGroup(userMailRecipientGroup);
return new ResponseEntity(HttpStatus.OK);
}
how a silly question is it, I should not doubt my controller. In fact, the problem happens on front-end, my workmate support an error json resolver!!

Required request part 'file' is not present - Angular2 Post request

I am trying to get my file upload functionality done using Angular2 and SpringBoot. I can certify that my java code for the file uploading working fine since I have tested it successfully using Postman.
However, when it comes to sending the file from Angular2 front end, I am getting the HTTP 400 response saying Required request part 'file' is not present.
This is how I send the POST request from Angular2.
savePhoto(photoToSave: File) {
let formData: FormData = new FormData();
formData.append('file', photoToSave);
// this will be used to add headers to the requests conditionally later using Custom Request Options
this._globals.setRequestFrom("save-photo");
let savedPath = this._http
.post(this._endpointUrl + "save-photo", formData)
.map(
res => {
return res.json();
}
)
.catch(handleError);
return savedPath;
}
Note that I have written a CustomRequestOptions class which extends BaseRequestOptions in order to append Authorization header and Content Type header. Content Type header will be added conditionally.
Following is the code for that.
#Injectable()
export class CustomRequestOptions extends BaseRequestOptions {
constructor(private _globals: Globals) {
super();
this.headers.set('X-Requested-By', 'Angular 2');
this.headers.append('virglk', "vigamage");
}
merge(options?: RequestOptionsArgs): RequestOptions {
var newOptions = super.merge(options);
let hdr = this._globals.getAuthorization();
newOptions.headers.set("Authorization", hdr);
if(this._globals.getRequestFrom() != "save-photo"){
newOptions.headers.set('Content-Type', 'application/json');
}else{
//request coming from save photo
console.log("request coming from save photo");
}
return newOptions;
}
}
This conditional header appending is working fine. The purpose of doing that is if I add 'Content-Type', 'application/json' header to every request, file upload method in Spring controller will not accept it. (Returns http 415)
Everything seems to be fine. But I get Required request part 'file' is not present error response. Why is that? I am adding that param to the form Data.
let formData: FormData = new FormData();
formData.append('file', photoToSave);
This is the Spring Controller method for your reference.
#RequestMapping(method = RequestMethod.POST, value = "/tender/save-new/save-photo", consumes = {"multipart/form-data"})
public ResponseEntity<?> uploadPhoto(#RequestParam("file") MultipartFile file){
if (file.isEmpty()) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setMessage("DEBUG: Attached file is empty");
return new ResponseEntity<ErrorResponse>(errorResponse, HttpStatus.NOT_FOUND);
}
String returnPath = null;
try {
// upload stuff
} catch (IOException e) {
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setMessage(e.getMessage());
return new ResponseEntity<ErrorResponse> (errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<String>(returnPath, HttpStatus.OK);
}
EDIT - Adding the payload of the request captured by the browser
As you can see, the param "file" is available there.
Try to add
headers: {
'Content-Type': 'multipart/form-data'
},
to your
.post(this._endpointUrl + "save-photo", formData)
Change formData.append('file', photoToSave);
to              formData.append('file', this.photoToSave, this.photoToSave.name); and also add headers specifying the type of data you are passing to API, in your case it will be 'Content-Type': 'multipart/form-data'. Post the output here if it fails even after changing this.
Is there a chance that you're using zuul in a secondary app that is forwarding the request? I saw this with an update where the headers were stripped while forwarding a multi-part upload. I have a gatekeeper app which forwards requests using zuul to the actual service via a looking from eureka. I fixed it by modifying the url like this:
http://myserver.com/service/upload
to
http://myserver.com/zuul/service/upload
Suddenly the 'file' part of the upload header was no longer stripped away and discarded.
The cause, I suspect was a re-try mechanism which cached requests. On failure, it would re-submit the requests, but somehow for file uploads, it wasn't working properly.
To upload a file to the server, send your file inside a FormData and set content type as multipart/form-data.
export const uploadFile = (url, file, onUploadProgress) => {
let formData = new FormData();
formData.append("file", file);
return axios.post(url, formData, {
headers: {
'Content-Type': 'multipart/form-data;charset=UTF-8',
// other headers
},
onUploadProgress,
})
};
To handle file object, be careful with consumes attribute and #RequestPart annotation here.
#PostMapping(value = "/your-upload-path", consumes = "multipart/form-data")
public ResponseEntity<Object> uploadFile(#RequestPart("file") #Valid #NotNull #NotBlank MultipartFile file) {
// .. your service call or logic here
}

Resources