Can you set a dynamic value to #PreAuthorize in Spring? - java

Right now I use
#PreAuthorize("hasAuthority('CREATE_USER_PRIVILEGE')")
But I want the CREATE_USER_PRIVILEGE to come from a function(). Is this possible?

You could do something like this:
#RestController
class FooController {
#PreAuthorize("hasAuthority(#securityService.privilege)")
#GetMapping("/")
public ResponseEntity<String> helloSecurity(#RequestParam("id") Integer id){
return ResponseEntity.ok("Hello World");
}
}
#Service("securityService")
class SecurityService {
public String getPrivilege(){
return "CREATE_USER_PRIVILEGE";
}
}

Based on this great article
you have first to autowire your service using constructor or annotation then you can use the Spel language to use it as stated in the following example
#RequestMapping(value="/id/{domainObjectId}/dostuff", method=RequestMethod.POST, produces="application/json")
#PreAuthorize(value="hasRole('ROLE_DomainObjectAdmin') or #domainObjectServiceImpl.findDomainObject(#domainObjectId).getOwners().contains(#userAccount.getEmployee())")
public String setObjectiveComplete(#PathVariable String domainObjectId, UserAccount userAccount) {
// Do stuff
}

Based on the above solution, I've implemented something like this:
#Controller
class TestController {
//calling a PreAuthorize on method level/ can be used on class level as well
#PreAuthorize("hasAnyAuthority(#authorityService.authorities)")
#RequestMapping("/application")
public ModelAndView newPage() throws{
return new ModelAndView(view);
}
}
#Service("authorityService")
class AuthorityService{
#Value("${app.authorities}") // read roles from properties file
private String authorities;
public List<String> getAuthorities(){
// convert the comma separated Strings to list.
List<String> items = Arrays.asList(authorities.split("\\s*,\\s*"));
return items;
}
}

Related

How to split classes to use serveral #Controller

I am learning spring boot, and i developed the below simple example. I would like to annotate a class as Controller using #Controller. this class has constructor and I want to have access to GreetingFromDeuController as shown:
http://localhost:8080:/GreetingFromDeuController?str = "hi"
the error i am receiving is
#RequestMapping is not applicable on a constructor
please let me know how to solve.
code:
#Controller
#RequestMapping("/GreetingFromDeuController")
public class GreetingFromDeuController {
private String str;
#RequestMapping("/GreetingFrom/deu")
GreetingFromDeuController(#RequestParam(value = "str") String str) {
this.str = str;
}
#RequestMapping("/GreetingFromDeuController")
public String getGreetingFromDeu() {
return this.str;
}
}
First of all your constructor gets initialize much before you hit your URL. So you need to work on your design or tell me your business requirement and I will try to provide you a solution. My refactor code solution will help you to achieve that in two steps. First hit POST method which will do work on setting variable and then subsequent hits of GET method will return that set value.
We can refactor code like below. It will explain use of RequestMapping on method and class.
Considering we have to write two API, one for reading and one for writing.
URLS :
1. POST http://localhost:8080/example/greetings (in request body send {str:'hi'})
2. GET http://localhost:8080/example/greetings
#Controller
#RequestMapping("/example")
public class GreetingFromDeuController {
private String str;
#RequestMapping(value="/greetings" , method = RequestMethod.POST)
public void setGreetingFromDeu(#RequestBody(value = "str") String str)
{
this.str = str;
}
#RequestMapping(value="/greetings" , method = RequestMethod.GET)
public String getGreetingFromDeu()
{
return this.str;
}
}
The #RequestMapping documentation says:
Annotation for mapping web requests onto methods in request-handling
classes with flexible method signatures.
Then you can not do that, if you want to initialize your variables or whatever you can use several ways:
1.- Use #PostConstruct
#PostContruct
public void init() {
this.str = "Anything";
}
2.- Use a simple request to set anything only
#RequestMapping(value="/refresh/anythings", method = RequestMethod.PUT)
public void refresh(#RequestBody(value = "str") String str) {
this.str = str;
}
3.- Use #Value
In application.properties / application.yaml
properties.str = anything
In the Controller
#Value("${properties.str:default}") // by default str is "default"
public String str;
#RequestMapping(value="/greetings" , method = RequestMethod.GET)
public String getGreetingFromDeu() {
return this.str;
}
As far I am concerned, #RequestMapping is not meant for constructors. It should be used for annotating methods or classes. Methods that are responsible for handling requests.
#RequestMapping should be used to map request with endPoint. which can be used as class level and method level.
You can use #RestController (improved from #Controller see difference).
The ideal flow for Spring Boot is Controller -> Service -> Repository
Controller -> maps request with endPoint and return response
Service -> perform business logic
Repository -> Handle database operation
Example
#RestController
#RequestMapping("/api")
public class GreetingController {
#Autowired GreetinService greetingService;
// Request http://localhost:8080/api/GreetingFrom
#GetMapping("/GreetingFrom")
public ResponseEntity<String> GreetingRequestParam(#RequestParam(value = "name") String name) {
greetingService.performBusinessLogic(name);
return new ResponseEntity<String>("Greetings from "+name,HttpStatus.OK);
}
// Request http://localhost:8080/api/GreetingFrom/user2121
#GetMapping("/GreetingFrom/{name}")
public ResponseEntity<String> GreetingPathVariable(#PathVariable(value = "name") String name) {
return new ResponseEntity<String>("Greetings from "+name,HttpStatus.OK);
}
}

Spring REST get list from another class

I am new to both Spring and the REST API overall but I am now trying to make one. So from my controller, I want to return a list of files that another method returns in another class. The code probably says more then I can explain:
#RequestMapping("/backup")
public #ResponseBody List<FileInfo> backupFiles() {
return //Here i want to return the list of files
}
This is in my restController the "method" i want to return a list of FileInfo and today it already exists a method that does this that looks like this
private List<String> listBackupFiles() {
List<FileInfo> files = util.listBackupFilesInLocalDir(localStorage);
fileNameToSize = files.stream()
.collect(toMap(f -> f.name, f -> f.size));
return files.stream()
.map(f -> f.name)
.collect(toList());
}
So basically, I want to when someone goes to /backup I want the above method to trigger and return the list of files to my restController that then returns it to the requester. I don't know if this is even possible or if there is a better way to do this. I take any tips on how to tackle this problem.
The controller would be:
// #RestController = #Controller + #ResponseBody
#RestController
public class BackupController {
#Autowired
private BackupService backupService;
// you don't need #ResponseBody as you use #RestController
#RequestMapping("/backup")
public List<FileInfo> backupFiles() {
return backupService.listBackupFiles()
}
}
The service interface would be:
public interface BackupService {
public List<FileInfo> listBackupFiles();
}
The service implementation would be:
#Service
public class BackupServiceImpl implements BackupService {
public List<FileInfo> listBackupFiles() {
// localStorage come from
// maybe Util has it as static method, else inject it
return util.listBackupFilesInLocalDir(localStorage);
}
}
Hope was helpful :)

Error: Ambiguous handler methods mapped for HTTP path in spring data rest

this is the code:
#Import(Appconfig.class)
#RestController
//#RequestMapping("/api/destination/find")
public class RestApi01_Controller {
#Autowired
public CountryRepo Country_Repository;
#Autowired
public CityRepo City_Repository;
#Autowired
public AirportRepo Airport_Repository;
#Autowired
public ResortRepo Resort_Repository;
#RequestMapping(value="/api/destination/find/{city}", method=RequestMethod.GET)
public List<Master_City> getCity(#PathVariable String city) {
return City_Repository.findByCityLikeIgnoreCase(city);
}
#RequestMapping(value="/api/destination/find/{airportname}", method=RequestMethod.GET)
public List<Master_Airport> getAirportname(#PathVariable String airportname) {
return Airport_Repository.findByAirportnameLikeIgnoreCase(airportname);
}
#RequestMapping(value="/api/destination/find/{resortname}", method=RequestMethod.GET)
public List<Master_Resort> getResortname(#PathVariable String resortname) {
return ResortRepository.findByResortnameLikeIgnoreCase(resortname);
}
}
Your REST Endpoints are pointing basically to the same URI pattern.
Taking the first two as an example,
/api/destination/find/{city}
and
/api/destination/find/{airportname}
Since {city} and {airportname} are placeholders, and both the URI are HTTP GET methods, Spring is not able to make out which API to be used when a call to say /api/destination/find/london.
Instead of this, use request parameters. Something like this
/api/destination/find?airport={airportname}
and
/api/destination/find?city={city}

Spring Data Rest / Spring Hateoas Custom Controller - PersistentEntityResourceAssembler

I'm attempting to add some additional business logic to the auto-generated endpoints from the RepositoryRestResource. Please see the code below:
Resource:
#RepositoryRestResource(collectionResourceRel="event", path="event")
public interface EventRepository extends PagingAndSortingRepository<Event, Long> {
}
Controller:
#RepositoryRestController
#RequestMapping(value = "/event")
public class EventController {
#Autowired
private EventRepository eventRepository;
#Autowired
private PagedResourcesAssembler<Event> pagedResourcesAssembler;
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedResources<PersistentEntityResource> getEvents(Pageable pageable,
PersistentEntityResourceAssembler persistentEntityResourceAssembler) {
Page<Event> events = eventRepository.findAll(pageable);
return pagedResourcesAssembler.toResource(events, persistentEntityResourceAssembler);
}
}
I've looked at the following two stackoverflow articles:
Can I make a custom controller mirror the formatting of Spring-Data-Rest / Spring-Hateoas generated classes?
Enable HAL serialization in Spring Boot for custom controller method
I feel like I am close, but the problem that I am facing is that:
return pagedResourcesAssembler.toResource(events, persistentEntityResourceAssembler);
returns an error saying:
"The method toResource(Page<Event>, Link) in the type PagedResourcesAssembler<Event> is not applicable
for the arguments (Page<Event>, PersistentEntityResourceAssembler)".
The toResource method has a method signature that accepts a ResourceAssembler, but I'm not sure how to properly implement this and I can't find any documentation on the matter.
Thanks in advance,
- Brian
Edit
My issue was that I thought I could override the controller methods that are auto-created from #RepositoryRestResource annotation without having to create my own resource and resource assembler. After creating the resource and resource assembler I was able to add my business logic to the endpoint.
Resource:
public class EventResource extends ResourceSupport {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Resource Assembler:
#Component
public class EventResourceAssembler extends ResourceAssemblerSupport<Event, EventResource> {
public EventResourceAssembler() {
super(EventController.class, EventResource.class);
}
#Override
public EventResource toResource(Event entity) {
EventResource eventResource = createResourceWithId(entity.getId(), entity);
eventResource.setName(entity.getName());
return eventResource;
}
}
Updated Controller:
#RepositoryRestController
#RequestMapping(value = "/event")
public class EventController {
#Autowired
private EventRepository eventRepository;
#Autowired
private EventResourceAssembler eventResourceAssembler;
#Autowired
private PagedResourcesAssembler<Event> pageAssembler;
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedResources<EventResource> getEvents(Pageable pageable) {
Page<Event> events = eventRepository.findAll(pageable);
// business logic
return pageAssembler.toResource(events, eventResourceAssembler);
}
}
The thing I don't like about this is that it seems to defeat the purpose of having a RepositoryRestResource. The other approach would be to use event handlers that would get called before and/or after the create, save, delete operations.
#RepositoryEventHandler(Event.class)
public class EventRepositoryEventHandler {
#HandleBeforeCreate
private void handleEventCreate(Event event) {
System.out.println("1");
}
}
There doesn't seem to be any events for the findAll or findOne operations. Anyways, both these approaches seem to solve my problem of extending the auto generated controller methods from RepositoryRestResource.
It requires a PagedResourcesAssembler, Spring will inject one for you if you ask.
public PagedResources<Foo> get(Pageable page, PagedResourcesAssembler<Foo> assembler) {
// ...
}
In this case the resource is Foo. It seems in your case the resource you're trying to return is an Event. If that's so, I would expect your code to look something like:
private ResourceAssembler<Event> eventAssembler = ...;
public PagedResources<Event> get(Pageable page, PagedResourcesAssembler<Event> pageAssembler) {
Event event = ...;
return eventAssembler.toResource(event, pageAssembler);
}
You provide the ResourceAssembler<Event> that tells Spring how to turn Event into a Resource. Spring injects the PagedResourcesAssembler<Event> into your controller method to handle the pagination links. Combine them by calling toResource and passing in the injected pageAssembler.
The final result can be returned simply as a body as above. You could also use things like HttpEntity to gain more control over status codes and headers.
Note: The ResourceAssembler you provide can literally be something as simple as wrapping the resource, such as Event, with a Resource object. Generally you'll want to add any relevant links though.
To hack it you can use just PagedResourcesAssembler<Object> like:
#RequestMapping(method = RequestMethod.GET, value = "")
#ResponseBody
public PagedModel<PersistentEntityResource> getEvents(
Pageable pageable,
PersistentEntityResourceAssembler persistentAssembler,
PagedResourcesAssembler<Object> pageableAssembler
) {
return pageableAssembler.toModel(
(Page<Object>) repository.findAll(pageable),
persistentAssembler
);
}

Spring MVC 3.0 Rest problem

I'm trying out Spring MVC 3.0 for the first time and like to make it RESTfull.
This is my controller:
#Controller
#RequestMapping(value = "/product")
#SessionAttributes("product")
public class ProductController {
#Autowired
private ProductService productService;
public void setProductValidator(ProductValidator productValidator, ProductService productService) {
this.productService = productService;
}
#RequestMapping(method = RequestMethod.GET)
public Product create() {
//model.addAttribute(new Product());
return new Product();
}
#RequestMapping(method = RequestMethod.POST)
public String create(#Valid Product product, BindingResult result) {
if (result.hasErrors()) {
return "product/create";
}
productService.add(product);
return "redirect:/product/show/" + product.getId();
}
#RequestMapping(value = "/show/{id}", method = RequestMethod.GET)
public Product show(#PathVariable int id) {
Product product = productService.getProductWithID(id);
if (product == null) {
//throw new ResourceNotFoundException(id);
}
return product;
}
#RequestMapping(method = RequestMethod.GET)
public List<Product> list()
{
return productService.getProducts();
}
}
I have 2 questions about this.
I'm a believer in Convention over Configuration and therefor my views are in jsp/product/ folder and are called create.jsp , list.jsp and show.jsp this works relatively well until I add the #PathVariable attribute. When I hit root/product/show/1 I get the following error:
../jsp/product/show/1.jsp" not found how do I tell this method to use the show.jsp view ?
If I don't add the RequestMapping on class level my show method will be mapped to root/show instead of root/owner/show how do I solve this ? I'd like to avoid using the class level RequestMapping.
add your 'product' to Model and return a String /product/show instead of Product. In your show.jsp, you can access the product object form pageContext
Check out the section in the manual about "Supported handler method arguments and return types".
Basically, when your #RequestMapping method returns just an object, then Spring uses this as a single model attribute, and, I'm guessing, attempts to use the request URL as the basis for the view name.
The easiest way to return the view and data you want from the same method is probably to just have the method return a ModelAndView, so you can explicitly specify the viewName and the model data.

Categories