Spring Boot role validation controller using aspect - spring-boot

I have several controller functions separated by role, and instead of doing role validation in each controller method, I found that it seems to be able to get done by using Aspect, however something isn't right in my implementation as the code in Aspect never runs
Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ForMerchantOnly {}
Aspect:
#Aspect
#Configuration
public class ForMerchantOnlyAspect {
private static final Logger logger = LogManager.getLogger(ForMerchantOnlyAspect.class);
#Before("#annotation(com.example.api.annotation.ForMerchantOnly) && args(request)")
public void before(HttpServletRequest request) throws ServiceException {
if (!(request instanceof HttpServletRequest)) {
throw new RuntimeException("request should be HttpServletRequesttype");
}
String domain = request.getServerName();
System.out.println("Aspect showing domain " + domain);
// -- other code
}
}
Controller
#ForMerchantOnly
#GetMapping("/list")
public ResponseEntity<ApiResp> list() {
System.out.println("Show something");
return ResponseEntity.ok().body();
}
I'm assuming when i call controller /list method via chrome browser, it would hit the code in ForMerchantOnlyAspect but it just went into the controller method directly. Am I missing something?

The Aspect was not working as it could not find a matching joinpoint . There are no controller methods that has annotation #ForMerchantOnly and has an argument of type HttpServletRequest
From the documentation :
args: Limits matching to join points (the execution of methods when
using Spring AOP) where the arguments are instances of the given
types.
Following aspect may be used for the requirement . Scoping designator within will set the scope to advice.
#Before("#annotation(com.example.api.annotation.ForMerchantOnly) && within(com.example.api..*)")
public void before() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
System.out.println("Aspect showing domain " + request.getServerName());
}
Also note that an Aspect is better annotated with #Component and #Configuration be used for configurations.
You may also have a look at Method Security of Spring security framework , which lets to secure a method with annotations.
From the documentation
From version 2.0 onwards Spring Security has improved support
substantially for adding security to your service layer methods. It
provides support for JSR-250 annotation security as well as the
framework’s original #Secured annotation. From 3.0 you can also make
use of new expression-based annotations. You can apply security to a
single bean, using the intercept-methods element to decorate the bean
declaration, or you can secure multiple beans across the entire
service layer using the AspectJ style pointcuts.

Related

Spring authorization at package level; AOP?

we have a spring boot app with a java package that has spring controllers with endpoints for admin-like functionality. right now they all start with the same request mapping.
so one way i could do authorization of every endpoint in the package is by WebSecurityConfigurerAdapter implementation...
http.authorizeRequests()
.antMatchers("/admin/**").access("hasRole('ADMIN')")
but i was thinking it would be nice to use AOP somehow to target the package of the admin controllers to PreAuthorize all controller methods in the package. just in case someone on the project creates a new controller in the proejct with a different request mapping it would automatically be projected. also, if we decided to PreAuthorize at the #Service level instead of the controller level then this way could be used as well.
so, is it possible to PreAuthorize at the package level with AOP or some other way?
Spring provides default AOP interceptor for #Secured and #PreAuthorized annotations, but it works only on class or method level, and AFAIK not intended to be expanded to package level.
To put it simply, Spring intercepts calls to certain methods or all class methods with these annotations and check whether SecurityContextHolder holds Authority object and whether its collection of GrantedAuthority matches any of the annotation's value field values. So, you can do the same thing using AOP, for example like this:
#Aspect
#Component
public class AdminServiceAOPAuthorization {
private static final List<String> ALLOWED_ROLES = List.of("ADMIN", "SUPER_ADMIN");
#Pointcut("within(com.example.service.admin.*)") // <- any method in any class of the package
public void adminServiceLayer() {}
#Before("adminServiceLayer()")
public void authorize(JoinPoint jp) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
// throw some exception depending on the logic
}
boolean authorized = authentication.getAuthorities().stream()
.anyMatch(ga -> ALLOWED_ROLES.contains(ga.getAuthority()));
if (!authorized) {
throw new AccessDeniedException("Access denied");
// log or whatever
}
}
}
For better performance I'd advise to use it at the service layer to let Spring create proxy using implemented interface (if you use them, of course) instead of proxying controller class.

Overriding a class defined in Spring Boot \ Spring Security

We are in the process of migrating a legacy application to Spring Boot. In order to continue with testing until we have assigned roles to users, I would like to override the following:
class: SecurityContextHolderAwareRequestWrapper
method: public boolean isUserInRole(String role)
I have created a new class which extends SecurityContextHolderAwareRequestWrapper and overrides isUserInRole(), as follows:
#Component
public class MySecurityContextHolderAwareRequestWrapper extends org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper {
public MySecurityContextHolderAwareRequestWrapper(HttpServletRequest request,
AuthenticationTrustResolver trustResolver, String rolePrefix) {
super(request, trustResolver, rolePrefix);
}
#Override
public boolean isUserInRole(String role) {
return true;
}
When the application is run, the new bean does not take the place of the existing SecurityContextHolderAwareRequestWrapper class. This is clear because when the new class is instantiated, the constructor is not injected with the beans being injected into SecurityContextHolderAwareRequestWrapper. The application fails to start because parameters of type AuthenticationTrustResolver and String to the new class MySecurityContextHolderAwareRequestWrappercould could not be found
What is the correct way to override SecurityContextHolderAwareRequestWrapper, or for that matter any class in the Spring Boot framework?
Thanks
The SecurityContextHolderAwareRequestWrapper class is ultimately used by the SecurityContextHolderAwareRequestFilter configured with http.servletApi(). Some information about this feature is available in the Spring Security reference docs.
This feature shields you from direct dependence on Spring Security and provides very high level integration with Spring Security through the Servlet API. You cannot directly influence the class used to wrap the request.
However, if you wish to temporarily modify the result of role checks, you can influence what roles are available in the Authentication object during authentication itself. See info in the docs on GrantedAuthority, and note that you will want to customize roles during authentication by providing a custom UserDetailsService.

How to Inject custom method argument in Spring WebFlux using HandlerMethodArgumentResolver?

I want to create an custom method argument Resolver using Spring WebFlux. I am following link but its seem to be not working.
I am able to create the custom argument resolver using WebMvc.
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
public class MyContextArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyCustomeObject.class.isAssignableFrom(parameter.getParameterType())
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
.....
return Mono.just(new MyCustomeObject())
}
Please note that i am using HandlerMethodArgumentResolver from .web.reactive. package.
My AutoConfiguration file look like
#Configuration
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the class-path
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)//checks that the app is a reactive web-app
public class RandomWebFluxConfig implements WebFluxConfigurer {
#Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
MyContextArgumentResolver[] myContextArgumentResolverArray = {contextArgumentResolver()};
configurer.addCustomResolver(myContextArgumentResolverArray );
}
#Bean
public MyContextArgumentResolver contextArgumentResolver() {
return new MyContextArgumentResolver ();
}
My spring.factories looks like
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.XXXX.XXX.XXX.RandomWebFluxConfig
Please note that above configuration is part of the jar which is added in Spring WebFlux Boot project enabled using #EnableWebFlux .
It seems you're conflating two different problems here.
First, you should make sure that your method argument resolver works in a regular project.
For that, you need a #Configuration class that implements the relevant method in WebFluxConfigurer. Your code snippet is doing that but with two flaws:
Your configuration is using #EnableWebFlux, which is disabling the WebFlux auto-configuration in Spring Boot. You should remove that
it seems you're trying to cast a list of MethodArgumentResolver into a single instance and that's probably why things aren't working here. I believe your code snippet could be just:
configurer.addCustomResolver(contextArgumentResolver());
Now the second part of this question is about setting this up as a Spring Boot auto-configuration. I guess that you'd like WebFlux applications to automatically get that custom argument resolvers if they depend on your library.
If you want to achieve that, you should first make sure to read up a bit about auto-configurations in the reference documentation. After that, you'll realize that your configuration class is not really an auto-configuration since it will be applied in all cases.
You should probably add a few conditions on that configuration like:
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the classpath
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) // checks that the app is a reactive web app

What does the #Secure annotation do and what package is it apart of

I'm writing an API using Java EE, JAX-RS, Jersey. In doing this I've implemented my own security context and security filter.
Looking at questions like this one (How to get MIME type of uploaded file in Jersey) I've seen the #Secure annotation but what does it do? My hope was that is was an annotation that queries the isSecure method of the security context in the same way that #RolesAllowed does for checking if a user has the right to access a particular method. If so is there such a way of doing so with annotations or am I stuck to using the #Context to get the security context and just from that.
The #Secure annotation seems to be a custom one. JAX-RS/Jersey does not support such feature out-of-the-box but it's not that hard to implement. Lets say you have your own #Secure annotation and you want to do checks whether a communication channel is secure for methods annotated with this annotation. You need to create a custom ResourceFilterFactory in which you'll assign a special filter for such methods:
public class IsSecureResourceFilterFactory implements ResourceFilterFactory {
private class IsSecureFilter implements ResourceFilter, ContainerRequestFilter {
// ResourceFilter
#Override
public ContainerRequestFilter getRequestFilter() {
return this;
}
#Override
public ContainerResponseFilter getResponseFilter() {
return null;
}
// ContainerRequestFilter
#Override
public ContainerRequest filter(final ContainerRequest request) {
// Check whether the channel is secure.
if (request.isSecure()) {
return request;
}
// Throw an exception if it's not.
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
}
#Override
public List<ResourceFilter> create(final AbstractMethod abstractMethod) {
// Add IsSecureFilter for resource methods annotated with #Secure annotation (ignore other resource methods).
return abstractMethod.isAnnotationPresent(Secure.class)
? Collections.<ResourceFilter>singletonList(new IsSecureFilter()): null;
}
}
Now you need to tell Jersey about this ResourceFilterFactory. There are 2 ways:
via web.xml
<init-param>
<param-name>com.sun.jersey.spi.container.ResourceFilters</param-name>
<param-value>my.package.IsSecureResourceFilterFactory</param-value>
</init-param>
or via META-INF/services mechanism - you need to create a file called META-INF/services/com.sun.jersey.spi.container.ResourceFilterFactory which would contain a fully qualified name of your factory (in this case my.package.IsSecureResourceFilterFactory) and make sure this file is on the class-path of your application.

AspectJ autoproxy issues with Spring Controllers and Webflow Actions

I have two related issues regarding spring/AspectJ AOP. I have a typical logger aspect which logs exceptions thrown from any class in my application including services, daos, controllers and webflow actions...
#Aspect
public class AspectLogger {
#AfterThrowing(pointcut = "execution(* com.myapp..*.*(..))", throwing = "t")
public void logGustavoException(JoinPoint joinPoint, Throwable t) {
Log logger = LogFactory.getLog(joinPoint.getTarget().getClass());
logger.error(t.getMessage(), t);
}
}
In my application context I have an equally typical configuration...
<context:annotation-config />
<!-- AOP logging config -->
<aop:aspectj-autoproxy>
<aop:include name="aspectLogger" />
</aop:aspectj-autoproxy>
<bean id="aspectLogger" class="com.myapp.AspectLogger" />
This works fine for the most part, the issue I have is with the webflow actions and controllers which implement an interface.
1 - Controllers which implement an Interface...
One of our controllers implements an interface which defines one method, as well as defining several public methods which are used as #RequestMapping handlers...
#Controller
public class AmazingController implements OutstandingInterface {
// implements the method from OutstandingInterface
#Override
public Object doSomethingOutstanding(){
...
}
#RequestMapping(value="/amazingUrl.htm", method = RequestMethod.GET)
public String doSomethingAmazing(HttpSession session, ModelMap model) {
return "anAmazingViewName";
}
...
}
The issue here is that due to the fact that the controller implements an interface that doesn't define all its public methods (i.e. controller request mapping methods), a proxy is created for the controller which only proxies the 'doSomethingOutstanding' method from OutstandingInterface. As such, when a request comes in to /amazingUrl.htm, Spring does not route it to the appropriate request handler - it's as though the request mapping doesn't exist. I have solved this by defining an interface for the controller which extends OutstandingInterface and also defines the request handler methods required by the controller, but it seems odd/wrong to me to have to define an interface for a controller just so that the AspectJ stuff doesn't 'hide' the request handler...
#Controller
public interface IAmazingController extends OutstandingInterface{
#RequestMapping(value="/amazingUrl.htm", method = RequestMethod.GET)
public String doSomethingAmazing(HttpSession session, ModelMap model);
}
...
public class AmazingController implements IAmazingController {
#Override
public Object doSomethingOutstanding(){
...
}
#Override
#RequestMapping(value="/amazingUrl.htm", method = RequestMethod.GET)
public String doSomethingAmazing(HttpSession session, ModelMap model) {
return "anAmazingViewName";
}
...
}
2 - Webflow Actions
The second issue is very similar. After introducing the AspectJ configuration, none of my webflow Action classes were being autowired correctly - I kept getting 'cannot find bean of type FantasticAction' sort of errors. Again, I introduced interfaces for all of the Action classes and this solved the problem as it was the proxy that was being injected at runtime, not the actual action implementation class.
So finally... the question in both instances is - is there a way of getting around these AspectJ issues without having to define interfaces for every class I want to advise?
You should add CGLIB dependendy in your class path so you will not need to create interfaces for working with AOP
Take a look to the doc.

Resources