I am using Spring Security (version 4.0.3.RELEASE) with ACL's-level permissions - I am trying to apply PreAuthorize ACL's permissions to a delete method in my CrudRepository interface using the #PreAuthorize annotation with a call to the SPEL method "hasPermission" (see below). The hasPermission is referencing the input argument using the hash notation that I've seen in several forums, tutorials, and reference docs, but the input argument (the object that I want to delete) is not being picked up by the SPEL parser or supporting framework. So, in other words, I want to delete the object named "apiKey" which is the input argument below. I reference it in the SPEL annotation using "#apiKey" but when I put a breakpoint in the method org.springframework.security.acls.AclPermissionEvaluator.hasPermission(...) the "domainObject" input argument is NULL.
I'm pretty new to Spring and ACL's so I must be doing something fundamentally wrong. I've tried putting breakpoints in the Spring SPEL Parser and walking through during runtime, but I can't find the piece of the code that resolves the reflective arguments based on the SPEL token. I think it's "hidden" in a native method or something.
I've also tried fiddling with the SPEL reference syntax. Nothing else seems to parse correctly, so I think the "#argument_name" is the correct syntax - it's just that SPEL fails to dereference the argument object.
My CrudRepository code w/ the object reference ("#apiKey") that isn't working:
public interface ApiKeyRepository extends CrudRepository<ApiKey, Long> {
#Override
#PreAuthorize("hasPermission(#apiKey, 'DELETE')")
void delete(ApiKey apiKey);
}
Spring Security code which should be receiving my apiKey object, but instead is getting a NULL value for the domainObject:
public class AclPermissionEvaluator implements PermissionEvaluator {
...
public boolean hasPermission(Authentication authentication, Object domainObject, Object permission) {
if (domainObject == null) {
return false; //<== This is where I'm getting access denied from
}
...
Any ideas as to why SPEL is unable to properly reference by apiKey input argument? Thanks in advance.
I figured out my problem thanks to THIS POST which referenced THIS DOC:
Apparently you can't make object references to method arguments in an interface without using the #Param annotation on the arg. So, here is my updated code that fixes the issue:
public interface ApiKeyRepository extends CrudRepository<ApiKey, Long> {
#Override
#PreAuthorize("hasPermission(#apiKey, 'DELETE')")
void delete(#Param("apiKey") ApiKey apiKey);
}
Related
I'm writing a REST API using Spring and have certain clients to the service that cannot or will not change how they call my service.
Normally when sending a query param with a list of values you would just comma delimit the parameter and Spring will do the rest
curl http://host.com/api/endpoint?listParam=1,2,3
And the controller
#GetMapping("/api/endpoint")
public ResponseEntity endpoint(#RequestParam("listParam" List<String> listParam){
// Here, listParam is populated with 1,2,3
}
Unfortunately my clients are going to be passing lists with the bar | delimiter and it simply isn't possible to get them to change that.
Example: curl http://host.com/api/endpoint?listParam=1%7C2%7C3%7C
I would still like to use Spring to break these calls out into lists so I don't have to clutter my code with manual String.split() calls.
What I've already tried:
I found the #InitBinder annotation and wrote the following
#InitBinder
public void initBinder(WebDataBinder dataBinder){
dataBinder.registerCustomEditor(String[].class, new StringArrayPropertyEditor("|"));
}
However, this code doesn't seem to ever be called (watching with breakpoints) and requests using the bar as the delimiter fail with a 400 BAD REQUEST.
Any suggestions would be much appreciated, thanks!
404 is coming due to URL encoding issue.
You need to encode | then it will work, but it will create another problem, params would not be split.
To work around this you need to create a custom conversion that can convert String to Collection. For the custom conversion, you can check the StringToCollectionConverter class. Once you have custom conversion then you can register that service, in any of the configuration classes add following function
#Autowired
void conversionService(GenericConversionService genericConversionService) {
genericConversionService.addConverter(myStringToCollectionConvert());
}
#Bean
public MyStringToCollectionConvert myStringToCollectionConvert() {
return new MyStringToCollectionConvert();
}
In this MyStringToCollectionConvert is class that will parse String and converts to a collection of Strings.
I've accepted Sonus21's answer since his suggestion allowed me to hunt down an example that worked, but my solution was not exactly his.
The class StringToCollectionConverter did in fact exist for me, but it wasn't accessible and I couldn't use it in any way. However, in looking at the interface it implemented (ConditionalGenericConverter) and searching for more examples with Spring converters I eventually settled on the following solution.
The listParam in my question actually refers to a set of Enum values. The first thing I did was rewrite my controller to actually use the Enum values instead of raw Integers.
#GetMapping("/api/endpoint")
public ResponseEntity endpoint(#RequestParam("listParam" List<EnumClass> listParam){
// ...
}
Next, I wrote a Spring Custom Converter (Baeldung Doc)
public class CustomStringToEnumClassListConverter implements Converter<String, List<EnumClass>> {
#Override
public List<EnumClass> convert(String str) {
return Stream.of(
str.split("\\|")) // Here is where we manually delimit the incoming string with bars instead of commas
.map(i -> EnumClass.intToValue(Integer.parseInt(i))) // intToValue is a method I wrote to get the actual Enum for a given int
.collect(Collectors.toList());
}
}
Finally, I wrote a Config Bean and registered this Custom Converter with Spring:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Override
public void addFormatters(FormatterRegistry registry){
registry.addConverter(new CustomStringToEnumClassListConverter());
}
}
Once all of this was done, Spring automatically populated the listParam list with EnumClass objects.
I'd like to have a method in my Repository that returns a single value.
Like this:
TrainingMode findByTrainingNameAndNameEng( String trainingName, String nameEng );
http://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/
Spring Data Docs describe that in this case the method can return null if no entity is found.
I'd like to throw an exception with generic message like No TrainingMode found by %trainingName% and %nameEng% or smth like that.
I can use Optional<TrainingMode> as a return value and then use orElseThrow
Optional<TrainingMode> findByTrainingNameAndNameEng( String trainingName, String nameEng );
repository.findByTrainingNameAndNameEng(name, nameEng).orElseThrow(() -> new RuntimeException(...));
But I should call this method each time when this method is called. It's not clear - DRY priciple is broken.
How to get nonnull single value with orElseThrow using Spring Data?
The DRY principle would be violated if you duplicate null handling throughout the application logic where it is being invoked. If DRY principle is the thing you are worried the most then i can think of:
You can make a "Service" class which would delegate calls to annotated repository and handle null response logic to it, and use that service class instead of calling repositories directly. Drawback would be introducing another layer to your application (which would decouple repositories from your app logic).
There is possibility of adding custom behavior to your data repositories which is described in "3.6.1. Adding custom behavior to single repositories" section of documentation. Sorry for not posting the snippet.
The issue I personally have with second approach is that it pollutes app with interfaces, enforces you to follow a certain naming patterns (never liked 'Impl' suffixes), and might make migrating code a bit more time consuming (when app becomes big it becomes harder to track which interface is responsible for which custom behavior and then people just simply start creating their own behavior which turns out to be duplicate of another).
I found a solution.
First, Spring Data processes getByName and findByName equally. And we can use it: in my case find* can return null (or returns not null Optional, as you wish) and get* should return only value: if null is returned then exception is thrown.
I decided to use AOP for this case.
Here's the aspect:
#Aspect
#Component
public class GetFromRepositoryAspect {
#Around("execution(public !void org.springframework.data.repository.Repository+.get*(..))")
public Object aroundDaoMethod( ProceedingJoinPoint joinpoint ) throws Throwable {
Object result = joinpoint.proceed();
if (null == result) {
throw new FormattedException( "No entity found with arhs %s",
Arrays.toString( joinpoint.getArgs() ) );
}
return result;
}
}
That's all.
You can achieve this by using the Spring nullability annotations. If the method return type is just some Entity and it's not a wrapper type, such as Optional<T>, then org.springframework.dao.EmptyResultDataAccessException will be thrown in case of no results.
Read more about Null Handling of Repository Methods.
In the Spring Statemachine reference doc is this sample code:
#WithStateMachine
static class Bean1 {
#OnTransition(source = "S1", target = "S2")
public void fromS1ToS2() {
}
}
Is it possible to access the StateContext object from a method annotated with #OnTransition? Perhaps I don't understand the correct use of the annotation...I thought it could be used in a similar manner as an Action, where I could access data stored in the ExtendedState.
It seems that I've totally forget to add this specific information on our docs. We can't access StateContext but event headers and ExtendedState are available.
There's a unit test for this in MethodAnnotationTests.
Short story is that processor handling method calling detects argument types ExtendedState and Map if it's annotated with #EventHeaders. I've also been thinking to support StateContext in a same way via method arguments but haven't yet got that far.
#WithStateMachine
public class Bean2 {
#OnTransition(source = "S1", target = "S2")
public void method1(#EventHeaders Map<String, Object> headers, ExtendedState extendedState) {
}
}
I'll also get docs sorted out for this, thx for pointing this out!
Their documentation says:
A method annotated with #OnTransition may accept a parameter of type
ExtendedState, Map if map argument itself is annotated with
EventHeaders, StateMachine, Message or Exception.
https://docs.spring.io/spring-statemachine/docs/current/api/org/springframework/statemachine/annotation/OnTransition.html
I'm using spring's PreAuthorize annotation as follows:
#PreAuthorize("hasRole('role')");
However, I already have 'role' defined as a static String on another class. If I try to use this value:
#PreAuthorize("hasRole(OtherClass.ROLE)");
I get an error:
org.springframework.expression.spel.SpelEvaluationException: EL1008E:(pos 14): Field or property 'OtherClass' cannot be found on object of type 'org.springframework.security.access.expression.method.MethodSecurityExpressionRoot'
Is there a way to access static variables like this with a PreAuthorize annotation?
Try the following which uses Spring Expression Language to evaluate the type:
#PreAuthorize("hasRole(T(fully.qualified.OtherClass).ROLE)");
Be sure to specify the fully qualified class name.
Documentation
You can also create a bean container with roles, like:
#Component("R")
public final class RoleContainer {
public static final String ROLE_A = "ROLE_A";
}
then on controller you can use:
#PreAuthorize("hasRole(#R.ROLE_A)")
To make it possible to write expressions without package names:
<sec:global-method-security>
<sec:expression-handler ref="methodSecurityExpressionHandler"/>
</sec:global-method-security>
<bean id="methodSecurityExpressionHandler" class="my.example.DefaultMethodSecurityExpressionHandler"/>
Then extend the DefaultMethodSecurityExpressionHandler:
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("my.example");
return standardEvaluationContext;
}
}
Now create my.example.Roles.java :
public class Roles {
public static final String ROLE_UNAUTHENTICATED = "ROLE_UNAUTHENTICATED";
public static final String ROLE_AUTHENTICATED = "ROLE_AUTHENTICATED";
}
And refer to it without package name in annotations:
#PreAuthorize("hasRole(T(Roles).ROLE_AUTHENTICATED)")
instead of:
#PreAuthorize("hasRole(T(my.example.Roles).ROLE_AUTHENTICATED)")
Makes it more readable imho. Also roles are now typed. Write:
#PreAuthorize("hasRole(T(Roles).ROLE_AUTHENTICATEDDDD)")
and you will get startup errors that wouldn't have been there if you wrote:
#PreAuthorize("hasRole('ROLE_AUTHENTICATEDDDD')")
Try something like this:
#PreAuthorize("hasRole(T(com.company.enumpackage.OtherClass).ROLE.name())");
If your OtherClass enum is declared as public static, then you need to use $ sign:
#PreAuthorize("hasRole(T(com.company.ParentTopLevelClass$OtherClass).ROLE.name())");
name() to prevent futer problems if toString() will be overriden later
The accepted answer from Kevin Bowersox works, but I didn't like having the T(fully.qualified.path) stuff so I kept looking. I started by creating a custom security method using the answer from James Watkins here:
How to create custom methods for use in spring security expression language annotations
However, instead of a String, I used my enums.Permissions class as the parameter type:
#Component
public class MySecurityService {
public boolean hasPermission(enums.Permissions permission) {
...do some work here...
return true;
}
}
Now the neat part is that when I call the hasPermission from an annotation, I don't have to have to type the whole path, but I do have to enclose it in single quotes:
#PreAuthorize("#mySecurityService.hasPermission('SOME_ROLE_NAME')")
Because the hasPermission method expects an Enum, it will automatically find the Enum value with that name. If it doesn't find it you'll get an exception:
org.springframework.expression.spel.SpelEvaluationException: Type conversion problem, cannot convert from java.lang.String to enums.Permissions
You can rename hasPermission to hasRole, in which case the only trade off is that you are trading T(fully.qualified.path) for #mySecurityService and extra single quotes.
Not sure if it is any better, but there it is. Since none of this is going to verify the values at compile time anyways, my next step is to make an annotation processor.
I also have to give credit to krosenvold for pointing out that spring can automatically convert to an enum:
https://stackoverflow.com/a/516899/618881
I want to provide annotations with some values generated by some methods.
I tried this so far:
public #interface MyInterface {
String aString();
}
#MyInterface(aString = MyClass.GENERIC_GENERATED_NAME)
public class MyClass {
static final String GENERIC_GENERATED_NAME = MyClass.generateName(MyClass.class);
public static final String generateName(final Class<?> c) {
return c.getClass().getName();
}
}
Thought GENERIC_GENERATED_NAME is static final, it complains that
The value for annotation attribute MyInterface.aString must be a constant expression
So how to achieve this ?
There is no way to dynamically generate a string used in an annotation. The compiler evaluates annotation metadata for RetentionPolicy.RUNTIME annotations at compile time, but GENERIC_GENERATED_NAME isn't known until runtime. And you can't use generated values for annotations that are RetentionPolicy.SOURCE because they are discarded after compile time, so those generated values would never be known.
The solution is to use an annotated method instead. Call that method (with reflection) to get the dynamic value.
From the user's perspective we'd have:
#MyInterface
public class MyClass {
#MyName
public String generateName() {
return MyClass.class.getName();
}
}
The annotation itself would be defined as
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface #MyName {
}
Implementing the lookup for both of these annotations is rather straight-forward.
// as looked up by #MyInterface
Class<?> clazz;
Method[] methods = clazz.getDeclaredMethods();
if (methods.length != 1) {
// error
}
Method method = methods[0];
if (!method.isAnnotationPresent(MyName.class)) {
// error as well
}
// This works if the class has a public empty constructor
// (otherwise, get constructor & use setAccessible(true))
Object instance = clazz.newInstance();
// the dynamic value is here:
String name = (String) method.invoke(instance);
There is no way to modify the properties of an annotation dynamically like others said. Still if you want to achieve that, there are two ways to do this.
Assign an expression to the property in the annotation and process that expression whenever you retrieve the annotation. In your case your annotation can be
#MyInterface(aString = "objectA.doSomething(args1, args2)")
When you read that, you can process the string and make the method invocation and retrieve the value. Spring does that by SPEL (Spring expression language). This is resource intensive and the cpu cycles are wasted every time we want to process the expression. If you are using spring, you can hook in a beanPostProcessor and process the expression once and store the result somewhere. (Either a global properties object or in a map which can be retrieved anywhere).
This is a hacky way of doing what we want. Java stores a private variable which maintains a map of annotations on the class/field/method. You can use reflection and get hold of that map. So while processing the annotation for the first time, we resolve the expression and find the actual value. Then we create an annotation object of the required type. We can put the newly created annotation with the actual value (which is constant) on the property of the annotation and override the actual annotation in the retrieved map.
The way jdk stores the annotation map is java version dependent and is not reliable since it is not exposed for use (it is private).
You can find a reference implementation here.
https://rationaleemotions.wordpress.com/2016/05/27/changing-annotation-values-at-runtime/
P.S: I haven't tried and tested the second method.