i have come to need to invent a new type of annotations, one of fields of which would be a Spring Expression Language (aka SpEL) expression string.
After a bit googling and examining existing classes, i've figured out that the way of evaluating expression might be like this one (correct me if i am wrong in any way):
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("isAnonymous()"); // well, this is only an example
SecurityExpressionRoot context = ... obtaining the instance of subclass of SecurityExpressionRoot ...
System.out.println(exp.getValue(context)); // just an example
But here is the problem: the most suiting for my case MethodSecurityExpressionRoot is package-local. There is even a task about making it public in Spring Security JIRA which didn't got any attention from developers for a year.
And even if it wasn't package-local, i still have a weak understanding of where to obtain objects for methods setTrustResolver, setRoleHierarchy and setPermissionEvaluator of SecurityExpressionRoot class, which seems to be needed for it's proper functioning.
So, my question is: how do you properly get the correct SecurityExpressionRoot-subclass instance and how to populate it with required objects?
I am solving same problem. I have a list of menu items. Each menu item contains a security expression string (SpEl). I tried to use #PostFilter("filterObject.securityExpression") but I couldn't figure out how to evaluate a SpEl string inside a SpEl string.
So I ended up with custom evaluator bean. Heavily inspired by org.thymeleaf.extras.springsecurity4.auth.AuthUtils
The evaluator uses same SecurityExpressionHandler as web security filters. This means its necessary to provide request and response for an evaluation context. But this shouldn't be complicated since Spring injects those values into controller methods.
Evaluator:
#Component
public class WebSecurityExpressionEvaluator {
private static final FilterChain EMPTY_CHAIN = (request, response) -> {
throw new UnsupportedOperationException();
};
private final List<SecurityExpressionHandler> securityExpressionHandlers;
public WebSecurityExpressionEvaluator(List<SecurityExpressionHandler> securityExpressionHandlers) {
this.securityExpressionHandlers = securityExpressionHandlers;
}
public boolean evaluate(String securityExpression, HttpServletRequest request, HttpServletResponse response) {
SecurityExpressionHandler handler = getFilterSecurityHandler();
Expression expression = handler.getExpressionParser().parseExpression(securityExpression);
EvaluationContext evaluationContext = createEvaluationContext(handler, request, response);
return ExpressionUtils.evaluateAsBoolean(expression, evaluationContext);
}
#SuppressWarnings("unchecked")
private EvaluationContext createEvaluationContext(SecurityExpressionHandler handler, HttpServletRequest request, HttpServletResponse response) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
FilterInvocation filterInvocation = new FilterInvocation(request, response, EMPTY_CHAIN);
return handler.createEvaluationContext(authentication, filterInvocation);
}
private SecurityExpressionHandler getFilterSecurityHandler() {
return securityExpressionHandlers.stream()
.filter(handler -> FilterInvocation.class.equals(GenericTypeResolver.resolveTypeArgument(handler.getClass(), SecurityExpressionHandler.class)))
.findAny()
.orElseThrow(() -> new IllegalStateException("No filter invocation security expression handler has been found! Handlers: " + securityExpressionHandlers.size()));
}
}
Usage as a controller method:
#ModelAttribute("adminMenuItems")
public List<AdminMenuItem> getMenuItems(HttpServletRequest request, HttpServletResponse response) {
List<AdminMenuItem> menuItems = ...
return menuItems.stream().filter(item -> evaluator.evaluate(item.getSecurityExpression(), request, response)).collect(toList());
}
I managed to achieve exactly this without any new annotations. The first thing you need to do is wrap your menu item in a sec:authorize tag, where the sec namespace is from spring security taglibs. We use:
<sec:authorize access="hasRole('${menuItem.permission}')"></sec:authorzie>
where ${menuItem.permission} is the permission field of the current menuItem object (we're looping through menuItems that we've retrieved from the server). The SpEl hasRole() is implemented by spring in the org.springframework.security.access.expression.SecurityExpressionOperations class.
That won't give you security though, it'll just make the gui nice. The server also needs to be secured with something like this:
#PreAuthorize("hasRole('...')")
The #PreAuthorize annotation is also from spring security, and it stops a client from executing a method on your server unless the user has the given role. To make this work we had to implement the org.springframework.security.cas.userdetails.AbstractCasAssertionUserDetailsService. A similar class exists for most identity management servers. We also had to implement org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao, but we're using ldap too. YMMV.
Related
I have a scenario where I need to run a few db checks at the start of every web request, and in the case of success I need to store objects for use later in the request by the controller, or in the case of failure I need to render an error page.
A very similar real world example would be a SaaS app checking and loading the account based on a vanity url, then storing the account for use by controllers to avoid multiple db requests.
What are the best ways to achieve this in a Spring boot app? I have experimented with Filters but I think an Interceptor might be better at the task, that covers running the check but what about storing the objects for later use? Is there a request lifecycle context of some kind that I can store against?
Spring supports request scope for beans. You can use them for storing data used during request execution.
In my experience, best way I've done similar stuff is through HandlerMethodArgumentResolver.
Basically imagine you have a custom type, let's call it UserContext where you store the information that's needed for the request. And you have a UserContextService let's say that has a method getUserContext(HttServletRequest), that is used to retrieve the context based on the request, from which you can call your database based on whatever request parameter/header/path-variable, etc. You can refine that as you need. But based on this simple assumptions, you can have a controller that looks like this:
#RequestMapping("/some/url")
public SomeResponse someMethod(UserContext userContext, ...) {
//do something here with UserContext
}
The way that Spring will inject this UserContext into your controller would be with a custom HandlerMethodArgumentResolver like this:
#Component
public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
#Autowired
UserContextService
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterType().equals(UserContext.class);
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest req = (HttpServletRequest)webRequest.getNativeRequest();
UserContext userContext = userContextService.getUserContext(req);
if (userContext != null) {
return userContext;
} else {
return WebArgumentResolver.UNRESOLVED;
//Or throw exception
}
}
}
That you'll register by overriding the WebMvcConfigurer.addArgumentResolvers method in your WebMvcConfigurer bean/config-class.
This mechanism is the same used by #PathVariable, #RequestParam, etc...
I have a controller with a web method that looks like this:
public Response registerDevice(
#Valid final Device device,
#RequestBody final Tokens tokens
) {...}
And a validator that looks like this:
public class DeviceValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return Device.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
// Do magic
}
}
}
I'm trying to get Spring to validate the Device argument which is being generated by an interceptor. But every time I try, it validates the tokens argument instead.
I've tried using #InitBinder to specify the validator, #Validated instead of #Validand registering MethodValidationPostProcessor classes. So far with no luck.
Either the validator is not called at all, or tokens argument is validated when I was the Device argument validated.
I'm using Spring 4.1.6 and Hibernate validator 5.1.3.
Can anyone offer any clues as to what I'm doing wrong? I've searched the web all afternoon trying to sort this out. Can't believe that the validation area of spring is still as messed up as it was 5 years ago :-(
Ok. Have now solved it after two days of messing about with all sorts of variations. If there is one thing Spring's validation lets you do - it's come up with an incredible array of things that don't work! But back to my solution.
Basically what I needed was a way to manually create request mapping arguments, validate them and then ensure that no matter whether it was a success or failure, that the caller always received a custom JSON response. Doing this proved a lot harder than I thought because despite the number of blog posts and stackoverflow answers, I never found a complete solution. So I've endeavoured to outline each piece of the puzzle needed to achieve what I wanted.
Note: in the following code samples, I've generalised the names of things to help clarify whats custom and whats not.
Configuration
Although several blog posts I read talked about various classes such as the MethodValidationPostProcessor, in the end I found I didn't need anything setup beyond the #EnableWebMvc annotation. The default resolvers etc proved to be what I needed.
Request Mapping
My final request mapping signatures looked like this:
#RequestMapping(...)
public MyMsgObject handleRequest (
#Valid final MyHeaderObj myHeaderObj,
#RequestBody final MyRequestPayload myRequestPayload
) {...}
You will note here that unlike just about every blog post and sample I found, I have two objects being passed to the method. The first is an object that I want to dynamically generate from the headers. The second is a deserialised object from the JSON payload. Other objects could just as easily be included such as path arguments etc. Try something like this without the code below and you will get a wide variety of weird and wonderful errors.
The tricky part that caused me all the pain was that I wanted to validate the myHeaderObj instance, and NOT validate the myRequestPayload instance. This caused quite a headache to resolve.
Also note the MyMsgObject result object. Here I want to return an object that will be serialised out to JSON. Including when exceptions occur as this class contains error fields that need to be populated in addition to the HttpStatus code.
Controller Advice
Next I created an ControllerAdvice class which contained the binding for validation and a general error trap.
#ControllerAdvice
public class MyControllerAdvice {
#Autowired
private MyCustomValidator customValidator;
#InitBinder
protected void initBinder(WebDataBinder binder) {
if (binder.getTarget() == null) {
// Plain arguments have a null target.
return;
}
if (MyHeaderObj.class.isAssignableFrom(binder.getTarget().getClass())) {
binder.addValidators(this.customValidator);
}
}
#ExceptionHandler(Exception.class)
#ResponseStatus(value=HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody
public MyMsgObject handleException(Exception e) {
MyMsgObject myMsgObject = new MyMsgObject();
myMsgObject.setStatus(MyStatus.Failure);
myMsgObject.setMessage(e.getMessage());
return myMsgObject;
}
}
Two things going on here. The first is registering the validator. Note that we have to check the type of the argument. This is because #InitBinder is called for each argument to the #RequestMapping and we only want the validator on the MyHeaderObj argument. If we don't do this, exceptions will be thrown when Spring attempts to apply the validator to arguments it's not valid for.
The second thing is the exception handler. We have to use #ResponseBody to ensure that Spring treats the returned object as something to be serialised out. Otherwise we will just get the standard HTML exception report.
Validator
Here we use a pretty standard validator implementation.
#Component
public class MyCustomValidator implements Validator {
#Override
public boolean supports(Class<?> clazz) {
return MyHeaderObj.class.isAssignableFrom(clazz);
}
#Override
public void validate(Object target, Errors errors) {
...
errors.rejectValue("fieldName", "ErrorCode", "Invalid ...");
}
}
One thing that I still don't really get with this is the supports(Class<?> clazz) method. I would have thought that Spring uses this method to test arguments to decide if this validator should apply. But it doesn't. Hence all the code in the #InitBinder to decide when to apply this validator.
The Argument Handler
This is the biggest piece of code. Here we need to generate the MyHeaderObj object to be passed to the #RequestMapping. Spring will auto detect this class.
public class MyHeaderObjArgumentHandler implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyHeaderObj.class.isAssignableFrom(parameter.getParameterType());
}
#Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
// Code to generate the instance of MyHeaderObj!
MyHeaderObj myHeaderObj = ...;
// Call validators if the argument has validation annotations.
WebDataBinder binder = binderFactory.createBinder(webRequest, myHeaderObj, parameter.getParameterName());
this.validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors()) {
throw new MyCustomException(myHeaderObj);
}
return myHeaderObj;
}
protected void validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) {
Annotation[] annotations = methodParam.getParameterAnnotations();
for (Annotation ann : annotations) {
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] { hints });
binder.validate(validationHints);
break;
}
}
}
}
The main job of this class is to use whatever means it requires to build the argument (myHeaderObj). Once built it then proceeds to call the Spring validators to check this instance. If there is a problem (as detected by checking the returned errors), it then throws an exception that the #ExceptionHandler's can detect and process.
Note the validateIfApplicable(WebDataBinder binder, MethodParameter methodParam) method. This is code I found in a number of Spring's classes. It's job is to detect if any argument has a #Validated or #Valid annotation and if so, call the associated validators. By default, Spring does not do this for custom argument handlers like this one, so it's up to us to add this functionality. Seriously Spring ???? No AbstractSomething ????
The last piece, explicit Exception catches
Lastly I also needed to catch more explicit exceptions. For example the MyCustomException thrown above. So here I created a second #ControllerAdvise.
#ControllerAdvice
#Order(Ordered.HIGHEST_PRECEDENCE) // Make sure we get the highest priority.
public class MyCustomExceptionHandler {
#ExceptionHandler
#ResponseStatus(value = HttpStatus.BAD_REQUEST)
#ResponseBody
public Response handleException(MyCustomException e) {
MyMsgObject myMsgObject = new MyMsgObject();
myMsgObject.setStatus(MyStatus.Failure);
myMsgObject.setMessage(e.getMessage());
return myMsgObject;
}
}
Although superficially the similar to the general exception handler. There is one different. We need to specify the #Order(Ordered.HIGHEST_PRECEDENCE) annotation. Without this, Spring will just execute the first exception handler that matches the thrown exception. Regardless of whether there is a better matching handler or not. So we use this annotation to ensure that this exception handler is given precedence over the general one.
Summary
This solution works well for me. I'm not sure that I've got the best solution and there may be Spring classes which I've not found which can help. I hope this helps anyone with the same or similar problems.
I am using Spring 4.0.6 in a servlet application. I have an abstract base controller with some general methods for all my controllers to use.
One of these methods is a redirect. I want to have a method with signature
redirect(String path)
To send a redirect, I am using
response.sendRedirect(response.encodeRedirectURL(path));
As I would like to keep method signatures short and clean, I need to get access to the response object inside the superclass method.
In order to do this, I've followed a suggestion found online, and defined a servlet filter with a ThreadLocal HttpServletResponse.
public class ResponseFilter extends OncePerRequestFilter {
private static final ThreadLocal<HttpServletResponse> responses = new ThreadLocal<HttpServletResponse>();
public static HttpServletResponse getResponse() {
return responses.get();
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
responses.set(response);
} finally {
try {
filterChain.doFilter(request, response);
} finally {
responses.remove();
}
}
}
}
As I am using Spring security with a Java configuration, I'm adding this filter in my WebSecurityConfigurerAdapter subclass:
.addFilterAfter(rf, SwitchUserFilter.class)
Note that I have also tried adding the filter as first in the filterchain, and that I have tried using an Interceptor instead. All with the same results.
I have compared hashcodes on the response objects, and near as I can tell, the hashcodes match, but the redirect seems to be ignored. I have also looked at object ids on breakpoints in Eclipse, and there again, I have a match. The symptom is that the spring DispatcherServlet enters processDispatchResult and seems to think it needs to resolve a view. That view does not exist, as I expect to do a redirect:
javax.servlet.ServletException: File "/WEB-INF/views/application/redirecttest.jsp" not found
I have noticed that, if I add the response object back in my requestmapping controller method signature, the superclass redirect seems to work (even though I do not use the controller method response object at all).
Unfortunately, this behavior is reproducible both on a Mac and on Linux. I use Tomcat 7 as container.
Your filter should work just fine, but the problem you're facing is another. If you are using views (as you appear to do in the example) you need to return a redirect view from your controller in order to force a redirect; just instructing the response object to redirect won't work because Spring MVC infrastructure will try to do its thing (i.e. view resolution) before the Response is returned to the Servlet container.
For instance, if you use the convention to return the view name as a String from your controller method, you need to do the following in your controller:
#RequestMapping("/redirectTest")
public String redirectTest() {
return "redirect:http://www.example.com";
}
I'm trying to make use of springs security annotations like #PreAuthorize and #Secured but Im looking to evaluate a user not on a role but whether they have permissions to a particular entity in this case a firm. In my controller I receive a http request containing a firmId as a parameter and I want to make sure this user is permissioned to this firm. Is this possible using the current spring security annotations?. Im looking for an elegant solution, i've been looking at custom constraint validators as part of the jsr303 specification. Method header below.
public ModelAndView getSessionsJson(HttpServletRequest request,
HttpServletResponse response) throws ServletRequestBindingException {}
I think you can do something like this:
public ModelAndView getSessionsJson(HttpServletRequest request, HttpServletResponse response) throws ServletRequestBindingException {
Integer firmId = getFirmId(request);
Firm firm = getFirmById(firmId);
doSomeBusinessLogic(firm);
.....
}
#PreAuthorize("hasPermission(#firm, 'admin')")
public void doSomeBusinessLogic(Firm firm) {
....
}
....
using pre and post annotations in conjuction with ACL module. Of course before you need to set up ACL DB schema and prepare ACL permissions for each firm object.
I have a basic Spring MVC controller that looks like this:
#Controller
public void MyController {
#RequestMapping("/secret")
public String show() {
return "secret.jsp";
}
}
I am going to have several similar URLs that can only be reached by signed-in users. Since this is a cross-cutting concern, I'd like to use AOP, and I'd like to make this work via annotations. In other words, I'd like to throw a #RequiresLogin annotation on every controller method that needs to be secret.
AnnotationMethodHandlerAdapter supports the concept of interceptors, which seems on the surface like the right way to go for this. However, I want to know which method is going to be invoked so that I can check it for my #RequiresLogin annotation. I see that there's an "Object handler" parameter that's passed in, but I'm not sure how to turn that into a Class and Method that will be invoked.
Ideas?
There are no good ways to get a method signature in the interceptor.
Try to apply a regular AOP advise to your controller, Spring MVC plays well with it as long as target class proxying is used.
As axtavt writes correctly, Spring-AOP works well with controllers if using proxy-target-class. But there is also the possibility of using JDK proxies if you follow some (tedious) conventions:
Working with interface-based #Controller classes
A common pitfall when working with
annotated controller classes happens
when applying functionality that
requires creating a proxy proxy for
the controller object (e.g.
#Transactional methods). Usually you
will introduce an interface for the
controller in order to use JDK dynamic
proxies. To make this work you must
move the #RequestMapping annotations
to the interface as the mapping
mechanism can only "see" the interface
exposed by the proxy. As an
alternative, you may choose to
activate proxy-target-class="true" in
the configuration for the
functionality applied to the
controller (in our transaction
scenario in <tx:annotation-driven />).
Doing so indicates that CGLIB-based
subclass proxies should be used
instead of interface-based JDK
proxies. For more information on
various proxying mechanisms see
Section 7.6, “Proxying mechanisms”.
Source: 15.3.2 Mapping requests with #RequestMapping
While using spring security would be the optimal approach here, you can implement similar functionality using Spring Aspects. Here is an example of using an Aspect to check for a method containing a particular Annotation.
#Aspect
public class MyAspect {
#Around("execution(* com.test.controllers..*.**(..)) && " +
"within(#org.springframework.sterotype.Controller *)")
public Object execute(ProceedingJoinPoint joinPoint) {
Object target = joinPoint.getTarget();
if (target != null) {
Signature tSig = joinPoint.getSignature();
if (tSig instanceof MethodSignature) {
MethodSignature mSig = (MethodSignature) tSig;
Method method = mSig.getMethod();
if (method != null && method.isAnnotationPresent(MyAnnotation.class)) {
// do something
// parameters are available from joinPoint.getArgs();
}
}
}
}
// allow method invocation to continue
return joinPoint.proceed();
}
The format of the #Around advice will be specific to your application. In this example, it checks for any class annotated with Controller in the package com.test.controllers and all subpackages. See http://static.springsource.org/spring/docs/3.0.x/reference/aop.html for additional options.
Good luck!
How about ResolveHandlerMethodInterceptor using reflection.
Below code is experimental and version-dependent(spring 3.0.2).
import java.lang.reflect.Method;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.FrameworkServlet;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter;
public class ResolveHandlerMethodInterceptor implements HandlerInterceptor {
public final static String HANDLER_METHOD = "handlerMethod";
// Here is your servlet name
public final static String SERVLET_NAME = "XXXXX";
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object object, ModelAndView modelAndView)
throws Exception {
Method handlerMethod = (Method) request.getAttribute(HANDLER_METHOD);
System.out.println("postHandle>>>" + handlerMethod);
}
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object object, Exception exception)
throws Exception {
Method handlerMethod = (Method) request.getAttribute(HANDLER_METHOD);
System.out.println("afterCompletion>>>" + handlerMethod);
}
#Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object object) throws Exception {
ServletContext servletContext = request.getSession().getServletContext();
String attrName = FrameworkServlet.SERVLET_CONTEXT_PREFIX + SERVLET_NAME;
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext, attrName);
AnnotationMethodHandlerAdapter adapter = context.getBean(AnnotationMethodHandlerAdapter.class);
Method getMethodResolverMethod = adapter.getClass().getDeclaredMethod("getMethodResolver", Object.class);
getMethodResolverMethod.setAccessible(true);
Object servletHandlerMethodResolver = getMethodResolverMethod.invoke(adapter, object);
Method resolveHandlerMethod = servletHandlerMethodResolver.getClass().getMethod("resolveHandlerMethod", HttpServletRequest.class);
resolveHandlerMethod.setAccessible(true);
Method handlerMethod = (Method) resolveHandlerMethod.invoke(servletHandlerMethodResolver, request);
request.setAttribute(HANDLER_METHOD, handlerMethod);
System.out.println("preHandle>>>" + handlerMethod);
return true;
}
}
==reference==
http://toby.epril.com/?p=934
http://www.jarvana.com/jarvana/view/org/springframework/spring-webmvc/3.0.2.RELEASE/spring-webmvc-3.0.2.RELEASE-sources.jar!/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java?format=ok
So, these approaches listed are good, but they all have limitations. The AOP stuff is a good idea, but its limitation is that I need a way to get ahold of the request and response objects if I want to redirect or modify the response. The controller methods don't necessarily need the requests and responses, and requiring that they appear seems inelegant. I could use spring magic to get the request object from the Aspect, but I couldn't find a way to get the response.
Eventually, I came up with a middle way. I used a filter bean to get the request and the response objects and store them in a ThreadLocal. Then I created an aspect that has a reference to that filter, so that it could easily see the request and response objects.
Then I made the aspect wrap around methods based on the annotation, so I didn't even need to check on whether the annotation was present using code.
This combination approach appears to be working perfectly!
The only downside is that I can't figure out a good way to write an integration test that verifies that the aspect is invoked when there's an incoming request to that URL. It's a little scary that removing a single annotation leaves all my tests passing but allows unauthorized users through.
Thanks everybody for the great suggestions!