Java 8/Spring constants in PreAuthorize annotation - java

In my Spring Boot project I have defined a following RestController method:
#PreAuthorize("hasAuthority('" + Permission.APPEND_DECISION + "')")
#RequestMapping(value = "/{decisionId}/decisions", method = RequestMethod.PUT)
public DecisionResponse appendDecisionToParent(#PathVariable #NotNull #DecimalMin("0") Long decisionId, #Valid #RequestBody AppendDecisionRequest decisionRequest) {
....
return new DecisionResponse(decision);
}
Right now in order to provide allowed authority name I use a following code construction:
#PreAuthorize("hasAuthority('" + Permission.APPEND_DECISION + "')")
where Permission.APPEND_DECISION is a constant:
public static final String APPEND_DECISION = "APPEND_DECISION";
Is there any more elegant way in Java/Spring in order to define such kind of code ?

Here is a simple approach to defining authorities in a single place that doesn't require any in-depth Spring Security config.
public class Authority {
public class Plan{
public static final String MANAGE = "hasAuthority('PLAN_MANAGE')";
public static final String APPROVE = "hasAuthority('PLAN_APPROVE')";
public static final String VIEW = "hasAuthority('PLAN_VIEW')";
}
}
Securing services...
public interface PlanApprovalService {
#PreAuthorize(Authority.Plan.APPROVE)
ApprovalInfo approvePlan(Long planId);
}
}

Thanks to oli37 I have implemented this logic in a following way:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
private DefaultMethodSecurityExpressionHandler defaultMethodExpressionHandler = new DefaultMethodSecurityExpressionHandler();
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return defaultMethodExpressionHandler;
}
public class DefaultMethodSecurityExpressionHandler extends org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler {
#Override
public StandardEvaluationContext createEvaluationContextInternal(final Authentication auth, final MethodInvocation mi) {
StandardEvaluationContext standardEvaluationContext = super.createEvaluationContextInternal(auth, mi);
((StandardTypeLocator) standardEvaluationContext.getTypeLocator()).registerImport(Permission.class.getPackage().getName());
return standardEvaluationContext;
}
}
}
#PreAuthorize("hasAuthority(T(Permission).APPEND_DECISION)")
#RequestMapping(value = "/{decisionId}/decisions", method = RequestMethod.PUT)
public DecisionResponse appendDecisionToParent(#PathVariable #NotNull #DecimalMin("0") Long decisionId, #Valid #RequestBody AppendDecisionRequest decisionRequest) {
...
return new DecisionResponse(decision);
}

I thing the good way is not to mixed both
You can have constants
public static final String ROLE_ADMIN = "auth_app_admin";
and have that other side
#PreAuthorize("hasRole(\"" + Constants.ROLE_ADMIN + "\")")
this is much clear

Related

How to Use DI to get a final variable as #PostMapping's path

I have a final class Constants, which holds some final data.
#Component
public final class Constants {
public final String TOKEN;
public final String HOST;
public final String TELEGRAM;
public Constants(#Value("${myapp.bot-token}") String token,
#Value("${myapp.host}") String host) {
this.TOKEN = token;
this.HOST = host;
this.TELEGRAM = "https://api.telegram.org/bot" + TOKEN;
}
}
The problem is that, when I want to use a variable as #PostMapping path, I faced this error:
Attribute value must be constant
#RestController
#RequestMapping
public class Controller {
private final Constants constants;
#Autowired
public Controller(Constants constants) {
this.constants = constants;
}
#PostMapping(constants.TOKEN)// Problem is here
public ResponseEntity<?> getMessage(#RequestBody String payload) {
return new ResponseEntity<HttpStatus>(HttpStatus.OK);
}
}
I've tried to load TOKEN in my controller class but faced the same issue.
#RestController
#RequestMapping
public class Controller {
#Value("${myapp.bot-token}") String token
private String token;
#PostMapping(token)// Problem is here
public ResponseEntity<?> getMessage(#RequestBody String payload) {
return new ResponseEntity<HttpStatus>(HttpStatus.OK);
}
}
When I do something like this the problem will gone. But I don't want to declare my token in source-code.
#RestController
#RequestMapping
public class Controller {
private final String TOKEN = "SOME-TOKEN";
#PostMapping(TOKEN)// No problem
public ResponseEntity<?> getMessage(#RequestBody String payload) {
return new ResponseEntity<HttpStatus>(HttpStatus.OK);
}
}
Can anyone please give me a solution to this?
Try to paste string with property path inside #PostMapping annotation. Like this
#GetMapping(value = "${app.path}")
public String hello() {
return "hello";
}
You can only use a constant (i.e. a final static variable) as the parameter for an annotation.
Example:
#Component
class Constants {
public final static String FACEBOOK = "facebook";
}
#RestController
class Controller {
#PostMapping(Constants.FACEBOOK)
public ResponseEntity<ResponseBody> getMessage(#RequestBody String payload) {
return new ResponseEntity<>(HttpStatus.OK);
}
}
You must use builder pattern(use Lombok for ease) and freeze the value that you are getting from the properties and then use that in your program.

Consider defining a bean of type 'form' in your configuration

Place that is complaining the error:
#Data
public class AluguelValorForm {
#Autowired
private ValorAluguelMultaService valorAluguelMultaService;
#NotNull #NotEmpty
private String idAluguel;
#NotNull
private Double valor;
public AluguelValor converter(AluguelValorRepository aluguelValorRepository, AluguelForm form ) {
Double valorAluguel = valorAluguelMultaService.valorAluguel(form);
return new AluguelValor(idAluguel,valorAluguel);
}
public AluguelValor update(String idAluguel,Double valor) {
AluguelValor aluguelValor = new AluguelValor();
aluguelValor.setId(idAluguel);
aluguelValor.setValor(valor);
return aluguelValor;
}
Repository:
#Repository
public interface AluguelValorRepository extends MongoRepository<AluguelValor, String> {
Aluguel getReferenceById(String id);
}
Place that I call the method in AluguelValorForm:
#PostMapping
//#CacheEvict(value = "listaDeTopicos",allEntries = true)
public void cadastrar(#RequestBody AluguelForm form) {
Optional<Carro> carro = carroRepository.findByPlaca(form.getPlaca_carro());
Optional<Cliente> cliente = clienteRepository.findByCpf(form.getCpf());
if(carro.isPresent() && cliente.isPresent()) {
Aluguel aluguel2 = form.converter(aluguelRepository);
aluguelRepository.save(aluguel2);
Double valorAluguel = valorAluguelMultaService.valorAluguel(form);
AluguelValor aluguelValor = aluguelValorForm.update(aluguel2.getId(), valorAluguel);
aluguelValorRepository.save(aluguelValor);
}
}
Problem solved. Apparently, it's not possible to #Autowired a class that doesn't have any bean, like my RentValue. That's why I got this error.

Architecture pattern for "microservice" with hard logic (Spring boot)

i've got a microservice which implements some optimization function by calling many times another microservice (the second one calculates so called target function value and the first micriservice changes paramters of this tagrget function)
It leads to necessity of writing some logic in Rest Controller layer. To be clear some simplified code will be represented below
#RestController
public class OptimizerController {
private OptimizationService service;
private RestTemplate restTemplate;
#GetMapping("/run_opt")
public DailyOptResponse doOpt(){
Data iniData = service.prepareData(null);
Result r = restTemplate.postForObject(http://calc-service/plain_calc", iniData, Result.class);
double dt = service.assessResult(r);
while(dt > 0.1){
Data newData = service.preapreData(r);
r = restTemplate.postForObject(http://calc-service/plain_calc", newData , Result.class);
dt = service.assessResult(r);
}
return service.prepareResponce(r);
}
As i saw in examples all people are striving to keep rest controller as simple as possible and move all logic to service layer. But what if i have to call some other microservices from service layer? Should i keep logic of data formin in service layer and return it to controller layer, use RestTemplate object in service layer or something else?
Thank you for your help
It is straightforward.
The whole logic is in the service layer (including other services).
Simple example:
Controller:
#RestController
#RequestMapping("/api/users")
public class UserController {
private final UserManager userManager;
#Autowired
public UserController(UserManager userManager) {
super();
this.userManager = userManager;
}
#GetMapping()
public List<UserResource> getUsers() {
return userManager.getUsers();
}
#GetMapping("/{userId}")
public UserResource getUser(#PathVariable Integer userId) {
return userManager.getUser(userId);
}
#PutMapping
public void updateUser(#RequestBody UserResource resource) {
userManager.updateUser(resource);
}
}
Service:
#Service
public class UserManager {
private static final Logger log = LoggerFactory.getLogger(UserManager.class);
private final UserRepository userRepository;
private final UserResourceAssembler userResourceAssembler;
private final PictureManager pictureManager;
#Autowired
public UserManager(
UserRepository userRepository,
UserResourceAssembler userResourceAssembler,
PictureManager pictureManager
) {
super();
this.userRepository = userRepository;
this.userResourceAssembler = userResourceAssembler;
this.pictureManager= pictureManager;
}
public UserResource getUser(Integer userId) {
User user = userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User with ID " + userId + " not found!"));
return userResourceAssembler.toResource(user);
}
public List<UserResource> getUsers() {
return userResourceAssembler.toResources(userRepository.findAll());
}
public void updateUser(UserResource resource) {
User user = userRepository.findById(resource.getId()).orElseThrow(() -> new NotFoundException("User with ID " + resource.getId() + " not found!"));
PictureResource pictureResource = pictureManager.savePicture(user);
user = userResourceAssembler.fromResource(user, resource);
user = userRepository.save(user);
log.debug("User {} updated.", user);
}
}
Service 2:
#Service
public class PictureManager {
private static final Logger log = LoggerFactory.getLogger(PictureManager.class);
private final RestTemplate restTemplate;
#Autowired
public PictureManager(RestTemplate restTemplate) {
super();
this.restTemplate = restTemplate;
}
public PictureResource savePicture(User user) {
//do some logic with user
ResponseEntity<PictureResource> response = restTemplate.exchange(
"url",
HttpMethod.POST,
requestEntity,
PictureResource.class);
return response.getBody();
}
}
Repository:
public interface UserRepository extends JpaRepository<User, Integer> {
User findByUsername(String username);
}

#JsonView with Spring PagedResources

I've a pojo exposed with Rest Controller. I need to hide some properties for one GET request, so I decided to use jackson's annotation #JsonView. I can't find any way to made it with #JsonView and PagedResources.
Here is my pojo :
public class Pojo {
interface RestrictedPojo {}
interface AllPojo extends RestrictedPojo {}
#Id
#JsonView(RestrictedPojo.class)
private String identifier;
#JsonView(AllPojo.class)
private String someproperty;
/**
* Property I want to hide
*/
#JsonView(RestrictedPojo.class)
private String someHiddenProperty;
}
Here is my Controller :
#RepositoryRestController
#RequestMapping(value = "/pojo")
#RequiredArgsConstructor(onConstructor = #__(#Autowired))
public class PojoController {
private final PojoService pojoService;
private final IdentityUtils identityUtils;
private final PagedResourcesAssembler<Pojo> pagedResourcesAssembler;
#PreAuthorize("hasRole('SOME_ROLE')")
#GetMapping
#JsonView(Pojo.RestrictedPojo.class)
public ResponseEntity<PagedResources<Resource<Pojo>>> getAllRestrictedPojos(final Pageable pageable) {
final Page<Pojo> allPojo = pojoService.getAllRestrictedPojos(pageable);
final PagedResources<Resource<Pojo>> resources = pagedResourcesAssembler.toResource(allPojo );
return ResponseEntity.ok(resources);
}
#PreAuthorize("hasRole('SOME_ROLE')")
#GetMapping
#JsonView(Pojo.AllPojo.class)
public ResponseEntity<PagedResources<Resource<Pojo>>> getAllPojos(final Pageable pageable) {
final Page<Pojo> allPojo = pojoService.getAllRestrictedPojos(pageable);
final PagedResources<Resource<Pojo>> resources = pagedResourcesAssembler.toResource(allPojo );
return ResponseEntity.ok(resources);
}
}
I didn't wrote specific config, it's a basic spring boot app.
Can anyone help ?
Thanks

implementing an interface contains #PreAuthorize to a controller causes something wrong, why?

I have this Controller and an Interface, when i try to implement the interface for applying Preauthorize annotation , it cause a damage to the controller , so the methods aren't working at that case . I know that i can apply the annotation directly inside the controller but i'll be happy if i can apply it using the interface as read in Spring's example
public interface PermissionsSecurity {
#PreAuthorize("hasRole('ROLE_ADMIN')")
String deletePost(#RequestParam(value = "id", required = true) Long id);
#PreAuthorize("hasRole('ROLE_ADMIN')")
String permissions(ModelMap model, #RequestParam(value = "q", required = false) String q);
}
Controller :
#Controller
public class PermissionsController implements PermissionsSecurity{
#Autowired
#Qualifier("permissionValidator")
private Validator validator;
#Autowired
private Permissions permissionsService;
#InitBinder
private void initBinber(WebDataBinder binder){
binder.setValidator(validator);
}
#RequestMapping(value="/permissions:delete", method=RequestMethod.POST)
public String deletePost(#RequestParam(value = "id", required = true) Long id) {
permissionsService.delete(id);
return "redirect:/permissions";
}
#RequestMapping(value = "/permissions", method=RequestMethod.GET)
public String permissions(ModelMap model, #RequestParam(value = "q", required = false) String q){
model.addAttribute("q", (q != null)? q : "");
model.addAttribute("viewTemplate", "permissions");
model.addAttribute("roles", permissionsService.getAll());
return "welcome";
}
}

Categories