Spring in Kotlin: Spring Security gives me forbidden message despite i'm logged in - spring

I have my spring server working already.
I added the spring-security dependency to the gradle.
logged in with the provided password, I can access all the pages with their links directly, and move from one page to another, except one page that I get a forbidden message (403) when I submit the details in it.
The page provide an image with checkboxes for the logged in user to design his/her taco and submit it to the orders page.
this part stopped working for no reason and started to give me the 403 message. what is the issue with it?
the dependency i used:
implementation("org.springframework.boot:spring-boot-starter-security")
the controller works fine until it reaches the #PostMapping code:
#Controller
#RequestMapping("/design")
#SessionAttributes("tacoOrder")
class DesignTacoController(
#Autowired private val ingredientsRepository: IngredientsRepository
) {
#ModelAttribute
fun addIngredientsToModel(model: Model) {
val ingredients = ingredientsRepository.findAll()
val types: Array<Type> = Type.values()
for (type in types) {
model.addAttribute(
type.toString().lowercase(), //Name to be used in View
filterByType(ingredients.toList(), type)
) //Value
}
}
#ModelAttribute(name = "tacoOrder")
fun order(): TacoOrder {
return TacoOrder()
}
#ModelAttribute(name = "taco")
fun taco(): Taco {
return Taco()
}
#GetMapping
fun showDesignForm(): String = "design"
private fun filterByType(ingredients: List<Ingredients>, type: Type): Iterable<Ingredients> {
return ingredients
.stream()
.filter { it.type == type }
.collect(Collectors.toList())
}
#PostMapping
fun processTaco(
taco: Taco,
bindingResult: BindingResult,
#ModelAttribute tacoOrder: TacoOrder,
): String {
checkTaco(taco, bindingResult)
if (bindingResult.hasErrors()) return "design"
tacoOrder.addTaco(taco)
println("Processing Taco:$taco")
return "redirect:/orders/current"
}
private fun checkTaco(taco: Taco, bindingResult: BindingResult) {
if (taco.name.length < 5) bindingResult.addError(FieldError("name", "name", "Name Should be longer than 5 Characters."))
if (taco.ingredient.isEmpty()) bindingResult.addError(FieldError("taco", "ingredient", "You should have at least one ingredient."))
}
}

Spring Security enables CSRF protection by default, that caused the 403 response on the POST request.
you can either disable the CSRF from the configuration or add the csrf to the POST Request.
like for my situation i only needed to add one field to the form
th:action="#{/design}"
this is it...

Related

Secure a Spring Webflux controller considering dynamic parameters with Spring Security annotations

I have an application with some controllers that require access control based on the requested resources ids, checking against the Spring Security user Authentication roles. At the moment I have created a function that checks this condition returning a Mono<True> if it is ok (so that I can flatmap it) or an empty Mono (and also setting a 403 status code) otherwise:
#RestController
#RequestMapping("/api/v1/clients/{clientId}/departments/{departmentId}/users")
class UserRestController(private val userService: UserService) {
#GetMapping
fun getAll(principal: Principal, response: ServerHttpResponse,
#PathVariable clientId: String, #PathVariable departmentId: String): Flux<Users> {
return checkDepartmentViewPermissions(principal, response, clientId, departmentId)
.flatMap {
userService.getAll(clientId, departmentId)
}
}
...
}
fun checkDepartmentViewPermissions(principal: Principal, response: ServerHttpResponse,
clientId: String, departmentId: String): Mono<Boolean> {
val authentication = principal as MyAuthentication
authentication.authorities.contains(SimpleGrantedAuthority("${clientId}:${departmentId}")).toMono()
.filter {
it == true
}.switchIfEmpty {
response.statusCode = HttpStatus.FORBIDDEN
Mono.empty()
}
}
As seen above, requests are in the format /api/v1/clients/{clientId}/departments/{departmentId}/users where clientId and departmentId are dynamic path variables.
The checkDepartmentViewPermission method accesses the Authentication roles where users will have a list such as (client1:department1, client1:department2, client2:department1). Thus, a URL /api/v1/clients/client1/departments/department1/users would work fine for those permissions.
Although what I have works, I would like to use a more declarative way to deal with this if it is possible, ideally something based on Spring Security annotations and having into account I need to access PathVariable parameters, something like (I'm making it up):
#RestController
#RequestMapping("/api/v1/clients/{clientId}/departments/{departmentId}/users")
class UserRestController(private val userService: UserService) {
#PreAuthorize("#{principal.roles.contains(clientId:departmentId)}")
#GetMapping
fun getAll(principal: Principal, response: ServerHttpResponse,
#PathVariable clientId: String, #PathVariable departmentId: String): Flux<Users> {
return userService.getAll(clientId, departmentId)
}
...
}
Does Spring Security support a way to do this?
If not, could you suggest any ideas to achieve it?
First off, thanks Thomas for pointing me in the right direction, I hadn't realized we can invoke a Spring bean from the security web expressions. This way, there's no need anymore to inject the principal since the Authentication object will be passed to the bean.
Controller:
#PreAuthorize("#permissionChecker.hasDepartmentViewPermissions(authentication, #clientId, #departmentId)")
#GetMapping
fun getAll(#PathVariable clientId: String, #PathVariable departmentId: String): Flux<Users> {
return userService.getAll(clientId, departmentId)
}
PermissionChecker bean:
class PermissionChecker {
fun hasDepartmentViewPermissions(authentication: Authentication, clientId: String, projectId: String): Boolean {
...
}

Spring MVC, validate if request has duplicate params

We have multipart post request mapped to below method.
fun post(#RequestParam(value = "photo", required = true) photo: Array<MultipartFile>,
#Valid person: Person)
class Person {
int id,
String name
}
In the below example has more than one name param in the request
localhost:8080/api
post body:
id:101
name: Jhon
name: Jhonny
Is there a way to reject the request if it contains repeated params?
You could add a HttpServletRequest to the handler method argument list, validate it, and return a ReponseEntity.badRequest().build() if there are multiple occurrences of the same parameters.
#GetMapping
public ResponseEntity handlerMethod(HttpServletRequest request) {
if (request.getParameterValues("name").length > 1) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok().build();
}
If you want to do it across the whole application you can define a filter:
#Component
class DuplicateRequestParameterFilter extends OncePerRequestFilter {
Enumeration<String> parameters = request.getHeaderNames();
while (parameters.hasMoreElements()) {
if (request.getParameterValues(parameters.nextElement()).length > 1) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
}
filterChain.doFilter(request, response);
}
no,there would be a array named 'name' or a string which contain two name,it is looked like this
{id:101,name:'Jhon,Jhonny'} or {id:101,name:['Jhon','Jhonny']}
which is determined by how to receive it
if name is a string ,the result would be 'Jhon,Jhonny'.
if name is a string array ,the result would by ['Jhon','Jhonny'].

What is the best way to validate request in a Spring Webflux functional application

In a traditional web application it is easy to validate the request body in the controller method, eg.
ResponseEntity create(#Valid #ResponseBody Post post) {
}
If it is a MVC application, we can gather the errors by injecting a BindingResult, and decide if there is some validation errors from the input form.
In the pages, there are some helpers existed for Freemarker and Thymeleaf to display the messages.
But when I come to Webflux and try to use RouterFunction to define the routing in the applications. For example,
Mono<ServerResponse> create(ServerRequest req) {
return req.bodyToMono(Post.class)
.flatMap { this.posts.save(it) }
.flatMap { ServerResponse.created(URI.create("/posts/".concat(it.getId()))).build() }
}
#Bean
RouterFunction<ServerResponse> routes(PostHandler postController) {
return route(GET("/posts"), postController.&all)
.andRoute(POST("/posts"), postController.&create)
.andRoute(GET("/posts/{id}"), postController.&get)
.andRoute(PUT("/posts/{id}"), postController.&update)
.andRoute(DELETE("/posts/{id}"), postController.&delete)
}
A possible approach is converting the request data(Mono or Flux) to blocking and injecting a Validator and validate them manually.
But I think the codes will look a little ugly.
How to process the validation of request body or form data gracefully?
Is there a better to validate the request body or form data and do not lost the functional and reactive features for both WEB(rendering a view) and REST applications?
I've developed "Yet Another Validator" for this porpose.
https://github.com/making/yavi
It would be great if YAVI could meet your expectation.
Validation code will look like following:
static RouterFunction<ServerResponse> routes() {
return route(POST("/"), req -> req.bodyToMono(User.class) //
.flatMap(body -> validator.validateToEither(body) //
.leftMap(violations -> {
Map<String, Object> error = new LinkedHashMap<>();
error.put("message", "Invalid request body");
error.put("details", violations.details());
return error;
})
.fold(error -> badRequest().syncBody(error), //
user -> ok().syncBody(user))));
}
One of the ways I've managed to do it in my application is the following (code is in Kotlin but the idea is the same). I've declared RequestHandler class which performs validation:
#Component
class RequestHandler(private val validator: Validator) {
fun <BODY> withValidBody(
block: (Mono<BODY>) -> Mono<ServerResponse>,
request: ServerRequest, bodyClass: Class<BODY>): Mono<ServerResponse> {
return request
.bodyToMono(bodyClass)
.flatMap { body ->
val violations = validator.validate(body)
if (violations.isEmpty())
block.invoke(Mono.just(body))
else
throw ConstraintViolationException(violations)
}
}
}
Request objects can contain java validation annotations in this way:
data class TokenRequest constructor(#get:NotBlank val accessToken: String) {
constructor() : this("")
}
And handler classes use RequestHandler to perform validation:
fun process(request: ServerRequest): Mono<ServerResponse> {
return requestHandler.withValidBody({
tokenRequest -> tokenRequest
.flatMap { token -> tokenService.process(token.accessToken) }
.map { result -> TokenResponse(result) }
.flatMap { ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(Mono.just(it), TokenResponse::class.java)
}
}, request, TokenRequest::class.java)
}
Got the idea from this blog post.

Spring Boot application form validation is intercepted by /error page

Currently I am working on a project which used Spring Boot 1.2.7, and freemarker as page template engine.
I am trying to use Bean Validation as before, but it does not work as expected.
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public String signup(#Valid #ModelAttribute("signup") SignupForm signup, BindingResult result) {
log.debug("signup form #" + signup);
if (result.hasErrors()) {
return "/signup";
}
//AccountDetails details = accountService.register(form);
return "redirect:/login";
}
When the bean validation is failed, it redirected to Spring Boot built-in /error page instead of displaying error messages in the signup page.
Your issue in SignupForm object and signup model attribute. Spring can not map both.
You need to redirect to signup page like :
#RequestMapping(value = "/signup", method = RequestMethod.POST)
public String signup(#Valid #ModelAttribute("signup") SignupForm signup, BindingResult result) {
log.debug("signup form #" + signup);
if (result.hasErrors()) {
return "signup";
}
//AccountDetails details = accountService.register(form);
return "redirect:/login";
}

Migrating to Spring MVC 4

We are migrating our mvc code to Spring 4. Previously we had a a method formBackingObject which we converted to get method initForm.
But trouble is - in previous controller which was extending SimpleFormController, formBackingObject was getting called even before submit method. We have now removed SimpleFormController. But initForm is getting called only only once on page load. It doesn't get called before submit. And there is some custom logic of creating user object and adding to UserProfileForm object.
Have you faced similar issue.
Old code
protected Object formBackingObject(HttpServletRequest request) throws Exception {
final UserProfileForm userProfileForm = new UserProfileForm();
final String id = request.getParameter("id");
if (id != null && !id.trim().equals("")) {
final User user = authenticationServices.findUser(ServletRequestUtils.getLongParameter(request, "id"));
userProfileForm.setUser(user);
} else {
final User user = new User();
userProfileForm.setUser(user);
}
return userProfileForm;
}
new code
#RequestMapping(method = RequestMethod.GET)
public String initForm(HttpServletRequest request, ModelMap model) throws Exception{
final UserProfileForm userProfileForm = new UserProfileForm();
final String id = request.getParameter("id");
if (id != null && !id.trim().equals("")) {
final User user = authenticationServices.findUser(ServletRequestUtils.getLongParameter(request, "id"));
userProfileForm.setUser(user);
} else {
final User user = new User();
userProfileForm.setUser(user);
}
addToModel(request, model);
model.addAttribute("userProfileForm", userProfileForm);
return "user-management/user-profile";
}
Create a method annotated with #ModelAttribute to fill your model.
#ModelAttribute("userProfileForm");
public UserProfileForm formBackingObject(#RequestParam(value="id", required=false) Long id) throws Exception{
final UserProfileForm userProfileForm = new UserProfileForm();
if (id != null) {
final User user = authenticationServices.findUser(id);
userProfileForm.setUser(user);
} else {
final User user = new User();
userProfileForm.setUser(user);
}
return userProfileForm;
}
#RequestMapping(method = RequestMethod.GET)
public String initForm() {
return "user-management/user-profile";
}
This way you can also use the #RequestParam annotation instead of pulling out parameters yourself.
See the reference guide for more information on the subject.
Certain inter-module dependencies are now optional at the Maven POM level where they were once required. For example, spring-tx and its dependence on spring-context. This may result in ClassNotFoundErrors or other similar problems for users that have been relying on transitive dependency management to pull in affected downstream spring-* . To resolve this problem, simply add the appropriate missing jars to your build configuration.

Resources