Reading RequestMapping wildcard in webflux application - spring-boot

I am trying to obtain the wildcard value from a controller's #requestMapping within a Spring boot webflux application:
#Controller
#RequestMapping("/template/**")
public class TemplateController {
#GetMapping
public Mono<String> getTemplate ( ... ) {
String path = ... // obtain the value of ** in the #RequestMapping
return Mono.just(path);
}
}
Everything I have found regarding reading such a value is for an MVC application; those solutions don't work for a webflux application.
How does one access this value within a webflux context? I would prefer an annotation-based approach for reusability, but any solution would do.

You could get the path from ServerHttpRequest that could be obtained from ServerWebExchange passed to the controller method. From ServerHttpRequest you can get access to a structured representation of the full request path
#GetMapping("/endpoint")
public Mono<Void> endpoint(ServerWebExchange serverWebExchange) {
ServerHttpRequest request = serverWebExchange.getRequest();
RequestPath path = request.getPath();
String value = path.value();
return Mono.empty();
}
With this you can play around the methods of RequestPath, maybe it helps

Related

Reactive Spring Security PostAuthorize annotation doesn't work

Using Webflux and Reactive Spring Security, how do you do post processing via annotations to control access to methods?
Trying a very basic sample, I'm not able to get the value from the PostAuthorize annotation. For example
#GetMapping
#PostAuthorize("#email == authentication.principal.email")
public Flux<Project> sampleTest(final String email) {
log.info("email: {}", email);
return Flux.empty();
}
The email will always be null. I have the basic wiring working to the fact if I set something like #PreAuthorize("hasRole('ADMIN')") I'll get back a 403.
I can extract the Authentication out with a helper like:
public Mono<Authentication> getAuthentication() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.flatMap(Mono::just);
}
I may not be understanding your question correctly, but the PostAuthorize uses the return object - the body of the method doesn't have access to anything in the SPEL expression.
Something like this might work -
#GetMapping
#PostAuthorize("returnObject == someCondition")
public Flux<Project> sampleTest(final String email) {
// get some data and return it
}
But maybe you want to filter the items in the Flux?
You might look at the #PostFilter annotation -
// assuming there's an email property on your Project object.
#GetMapping
#PostFilter("filterObject.getEmail() == authentication.principal.email")
public Flux<Project> sampleTest() {
// get some data and return it
}

Is Springdoc swagger not possible when passing HttpServletRequest as method param?

I have below springboot rest controller and using springdoc-openapi. My springboot rest service has to cater to some legacy application( which is the only client which calls this service) and I need to have HttpServletRequest as param.
I am generating swagger openapi doc and when I got to swagger-ui.html, I see that the rerquest body comes for Httprequest param method with uri path = '/personhttprequest' but not when param is HttpServletRequest. I see here
https://springdoc.org/faq.html#what-are-the-ignored-types-in-the-documentation
But I am not clear why and how can I get HttpServletRequest param working in swagger ui. I want to pass it as text/xml just like i can make it work for HttpRequest below.I have attached scrrenshot for "/personhttprequest" and you see the box for request body as xml comes up. How can make it work for "/personhttpservletrequest"?
#RestController
public class PersonController {
#RequestMapping(path = "/personhttprequest", method = RequestMethod.POST,consumes=MediaType.TEXT_XML_VALUE,produces=MediaType.APPLICATION_JSON_VALUE)
public Person personHttpRequest(HttpRequest req) {
Person person = new Person();
return person;
}
#RequestMapping(path = "/personhttpservletrequest", method = RequestMethod.POST,consumes=MediaType.TEXT_XML_VALUE,produces=MediaType.APPLICATION_JSON_VALUE)
public Person personHttpServletRequest(HttpServletRequest req) {
Person person = new Person();
return person;
}
}
Here is git hub :
https://github.com/vmisra2018/sb-example-swaggerdoc
Principal, Locale, HttpServletRequest and HttpServletResponse and other injectable parameters supported by Spring MVC are excluded.
Full documentation here:
https://docs.spring.io/spring/docs/5.1.x/spring-framework-reference/web.html#mvc-ann-arguments.
If you don't want to ignore it:
SpringDocUtils.getConfig().removeRequestWrapperToIgnore(HttpServletRequest.class)

Modify request header going into a controller and response header leaving a Spring Boot RestController

This feels like it should be a simple thing, but I'm still pretty new to SpringBoot, and the whole Servlet ecosystem, so it's not readily apparent. I would love an interface similar to HandlerInterceptor that allows me to modify the request and response object once I'm done in a controller. Even better would be to decorate mapping annotation, so I can specify which controllers need the operation.
The problem I'm solving right now, though I anticipate expanding this in the future, is that I have an encrypted header coming into my application that I would like to decrypt for use in the controller and then encrypt again on the way out.
EDIT: For clarity.
I have a rest controller, something like:
#RestController
public class PojoService {
#GetMapping(value = "/path/to/resource")
public ResponseEntity<SomeClass> getLocationData(
#RequestHeader(value = "EncryptedHeader", required = false) String ecryptedHeaderValue) {
DecryptionObject decryptedHeader = new DecryptionObject(pageHeaderValue);
SomePojo result = getResult();
return decryptedHeader.decorateResponseWithEncryptedHeader(result);
}
}
I would love to not have the DecryptionObject on every mapping, but rather, before I even get to the mapping, I decrypt the header via some filter or hook and then re-encrypt the header on the way out. Then my code would look something like:
#RestController
public class PojoService {
#GetMapping(value = "/path/to/resource", decryptHeader="EncryptedHeader")
public ResponseEntity<SomeClass> getLocationData(
#RequestHeader(value = "EncryptedHeader", required = false) String decryptedHeaderValue) {
SomePojo result = getResult();
return result;
}
}
I found that the HandlerInterceptor doesn't work because I cannot modify the request or response in the interceptor. Hope that clarifies the issue.
You can still use HandlerInterceptor. Create your class implementing HandlerInterceptor, and then register it using another class which extends WebMvcConfigurer.
#EnableWebMvc
#Configuration
#ComponentScan
public class MyWebConfig implements WebMvcConfigurer {
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new [...]); //Apply to all controllers
registry.addInterceptor(new [...]).addPathPatterns("path1","path2"); //Apply to specific paths to restrict to some controllers.
}
}
You also could do it using a Filter - create your Filter class and register it by declaring a #Bean of type FilterRegistrationBean - this also allows you to restrict to some paths.
UPDATE: You could do this with request attributes which can be set by interceptors (request.setAttribute("decryptedHeaderValue",<decrypted>). Or if you're specific about using headers, a filter would be more suitable for your purpose. Create a new wrapped request type that wraps the incoming request and does whatever you want, and pass this wrapper to the next filter in chain.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
[...]
HttpServletRequestWrapper decryptedRequest = new HttpServletRequestWrapper((HttpServletRequest) request) {
public String getHeader(String name) {
if (name.equals("DecryptedHeader")) {
String encrypted = super.getHeader("EncryptedHeader");
String decrypted = decrypt(encrypted);
return decrypted;
}
return super.getHeader(name); //Default behavior
}
}
chain.doFilter(decryptedRequest, response); //Pass on the custom request down
}
Then any class down the line (other filters, controllers etc) can just call request.getHeader("DecryptedHeader") to retrieve the decrypted header. This is just one of many similar approaches. You can restrict the paths for which this filter executes when registering it.
For response, there is a similar class HttpServletResponseWrapper which you can use for customization.
We can do this via addingAttribute in the interceptor
httpServletRequest.setAttribute(,);

Spring how to resolve entity uri in #RepositoryRestController

I'm using Spring boot 1.5.3, Spring Data REST, Spring HATEOAS. Spring Data REST is amazing and does a perfect job, but sometimes it's needed custom business logic and therfore I need to create a custom controller.
I'm going to use a #RepositoryRestController to benefit of Spring Data REST functionality (http://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.overriding-sdr-response-handlers).
Because Spring Data REST use HATEOAS by default, I'm using that. I need a controller like this:
#RepositoryRestController
#RequestMapping(path = "/api/v1/workSessions")
public class WorkSessionController {
#Autowired
private EntityLinks entityLinks;
#Autowired
private WorkSessionRepository workSessionRepository;
#Autowired
private UserRepository userRepository;
#PreAuthorize("isAuthenticated()")
#RequestMapping(method = RequestMethod.POST, path = "/start")
public ResponseEntity<?> start(#RequestBody(required = true) CheckPoint checkPoint) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (checkPoint == null) {
throw new RuntimeException("Checkpoint cannot be empty");
}
if (workSessionRepository.findByAgentUsernameAndEndDateIsNull(auth.getName()).size() > 0) {
// TODO return exception
throw new RuntimeException("Exist a open work session for the user {0}");
}
// ...otherwise it's opened a new work session
WorkSession workSession = new WorkSession();
workSession.setAgent(userRepository.findByUsername(auth.getName()));
workSession.setCheckPoint(checkPoint);
workSession = workSessionRepository.save(workSession);
Resource<WorkSession> resource = new Resource<>(workSession);
resource.add(entityLinks.linkFor(WorkSession.class).slash(workSession.getId()).withSelfRel());
return ResponseEntity.ok(resource);
}
}
Because the parameter CheckPoint must be an existant resource, I want the client send the link of the resourse (like you can do in Spring Data REST POST methods).
Unfortunately when I try to do that, server side I receive an empty CheckPoint object.
I already read Resolving entity URI in custom controller (Spring HATEOAS) and converting URI to entity with custom controller in spring data rest? and expecially Accepting a Spring Data REST URI in custom controller.
I'm wondering if there is a best practice to do this avoiding to expose id to the client, followind so the HATEOAS principles.
Try to change you controller like this:
#RepositoryRestController
#RequestMapping(path = "/checkPoints")
public class CheckPointController {
//...
#Transactional
#PostMapping("/{id}/start")
public ResponseEntity<?> startWorkSession(#PathVariable("id") CheckPoint checkPoint) {
//...
}
}
That will mean: "For CheckPoint with given ID start new WorkSession".
Your POST request will be like: localhost:8080/api/v1/checkPoints/1/start.

Map #CookieValue, #RequestHeader etc. to POJO in Spring Controller

I have a bunch of params in my controller and want to map all of them to a separate POJO to keep readability. There is also a #CookieValue, #RequestHeader I need to evaluate and aim for a solution to also map them to that POJO. But how?
I saw a possible solution on a blog but it doesn't work, the variable stays null.
Controller:
#RequestMapping(path = MAPPING_LANGUAGE + "/category", produces = MediaType.TEXT_HTML_VALUE)
#ResponseBody
public String category(CategoryViewResolveModel model) {
doSomething();
}
And my POJO is this:
public class CategoryViewResolveModel {
private String pageLayoutCookieValue;
public CategoryViewResolveModel() {
}
public CategoryViewResolveModel(
#CookieValue(value = "SOME_COOKIE", required = false) String pageLayoutCookieValue) {
this.pageLayoutCookieValue = pageLayoutCookieValue;
}
... some other RequestParams, PathVariables etc.
}
According to the documentation it's not possible for #CookieValue and #RequestHeader.
This annotation is supported for annotated handler methods in Servlet
and Portlet environments.
Take a look at:
https://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-creating-a-custom-handlermethodargumentresolver/
instead of using getParameter to access request parameters you can use getHeader to retrieve the header value and so define your CategoryViewResolveModel just as you were requesting

Resources