Using custom method security annotation in spring security - java

I want to tag methods in a class with a custom annotation that will control authorization decisions using spring security. For example:
#Role("ADMIN")
public void accessControlledMethod(){}
I understand that this means I somehow need to register my custom annotation "Role" so that it can result in ConfigAttributes being present when an authorization decision is made by the AccessDecisionManager. However, I do not understand how to register my custom annotation with spring security so that it will be recognized.
I see one potential solution in the framework code. There is a class called SecuredAnnotationSecurityMetadataSource whose documentation says "inject AnnotationMetadataExtractor for custom annotations". If that is the preferred method, I'm not sure how to configure the SecuredAnnotationSecurityMetadataSource or how to inject the AnnotationMetadataExtractor into it.

You can extend GlobalMethodSecurityConfiguration in your configuration :
#EnableGlobalMethodSecurity
#Configuration
public class MyMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return SecuredAnnotationSecurityMetadataSource(...);
}
}
In xml, you can do :
<global-method-security metadata-source-ref="customMethodSecurityMetadataSource">
...
</global-method-security>
<bean id="customMethodSecurityMetadataSource" class="org.springframework.security.access.annotation.SecuredAnnotationSecurityMetadataSource">
...
</bean>
customMethodSecurityMetadataSource can be any instanceof MethodSecurityMetadataSource

This is not working in Spring 5 becuase default bean overriding is disabled by default. It works only with spring.main.allow-bean-definition-overriding property set to true.
If anyone have some idea how to add custom MethodSecurityMetadataSource to GlobalMethodSecurityConfiguration without bean override enabling, it will be helpful for newer Spring version

In Spring Boot you can add custom MethodSecurityMetadataSources and AccessDecisionVoters by overriding the corresponding methods in GlobalMethodSecurityConfiguration and adding/modifying the values form the superclass.
#Configuration
#AutoConfigureAfter(SecurityConfiguration.class)
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override
public MethodSecurityMetadataSource methodSecurityMetadataSource() {
var source = (DelegatingMethodSecurityMetadataSource) super.methodSecurityMetadataSource();
source.getMethodSecurityMetadataSources().add(new FooSecurityMetadataSource());
return source;
}
#Override
protected AccessDecisionManager accessDecisionManager() {
var manager = (AffirmativeBased) super.accessDecisionManager();
manager.getDecisionVoters().add(new FooVoter());
return manager;
}
}

Related

Is it possible to configure custom argument resolvers without #EnableWebMvc and WebMvcConfigurerAdapter

The context
REST API implemented as Spring boot 1.5.3 project without #EnableWebMvc
The objective
For each API call create a UUID string and inject it into controller methods for audit purposes (the UUID is used in response body and for logging). Should be used as follows:
#PostMapping("/reserveCredits")
public ResponseEntity<Result> reserveCredits(String uuid) {
...
... new Result(uuid) ...
According to the documentation this can be achieved like so:
#Configuration
#EnableWebMvc
public class MyWebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new MyCustomArgumentResolver());
}
}
The problem
My whole project uses only REST controllers. I'm currently not using #EnableWebMvc and I don't want to introduce it now due to possible conflicts with my existing configuration. When I try using ...
#Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
... in my #Configuration bean I get BeanCreationException: Error creating bean with name 'defaultServletHandlerMapping' due to ServletContext is required.
The questions
How does Spring boot register its default argument resolvers without #EnableWebMvc?
Can I add custom argument resolver without #EnableWebMvc?
Is using #EnableWebMvc highly recommendable and I should retrofit it into my code?
Should I go for alternative solution?
The alternatives
Invasive AOP that overrides method parameter value
HandlerInterceptor that adds the uuid to request parameters and also updates response body
The answer based on the comment by #M. Denium:
Yes it's possible without #EnableWebMvc. Configuration via extension of WebMvcConfigurerAdapter does not have risk of regression impact. RequestMappingHandlerAdapter doesn't have to be autowired into the config class.
#Configuration
public class MyWebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new MyCustomArgumentResolver());
}
}

What is the use case of #Import annotation?

According to official doc:
Annotation Type Configuration
Indicates that a class declares one or more #Bean methods and may be
processed by the Spring container to generate bean definitions...
#Configuration classes may be composed using the #Import annotation,
not unlike the way that works in Spring XML. Because
#Configuration objects are managed as Spring beans within the
container..
But i can also use #Configuration annotation without #Import. I have tested the code listed below and it works as expected. So what is the purpose to use #Import?
DispatcherServletInitializer
public class ApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
WebMvcConfigurerAdapter
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = { "package.name" })
// #Import(OptionalConfig.class)
public class WebConfig extends WebMvcConfigurerAdapter {
// ...
}
OptionalConfig
#Configuration
public class OptionalConfig {
#Bean(name = "myClass")
public MyClass myClass() {
return new MyClass();
}
}
Service
#Service
public class MyServiceImpl implements MyService {
#Autowired
private MyClass myClass; // yes, it works
// ...
}
If component scanning is enabled, you can split bean definitions in multi #Configuration classes without using #Import. And you don't need to provide all of them to the application context constructor.
I think the main purpose for #Import is to provide you a way to simplify multiple configurations registration if you'd like to avoid component scanning (as of Spring Framework 4.2, per reference manual).
There's a note in Spring Reference Documentation about #Import usage:
As of Spring Framework 4.2, #Import also supports references to regular component classes, analogous to the AnnotationConfigApplicationContext.register method. This is particularly useful if you’d like to avoid component scanning, using a few configuration classes as entry points for explicitly defining all your components.
Thus far, we've seen how to break up bean definitions into multiple #Configuration classes and how to reference those beans across #Configuration boundaries. These scenarios have required providing all #Configuration classes to the constructor of a JavaConfigApplicationContext, and this is not always ideal. Often it is preferable to use an aggregation approach, where one #Configuration class logically imports the bean definitions defined by another.
The #Import annotation provides just this kind of support, and it is the direct equivalent of the <import/> element found in Spring beans XML files.
http://docs.spring.io/spring-javaconfig/docs/1.0.0.M4/reference/html/ch04s03.html
I found a use of using the #Import annotation. I don't think it's the use case.
If you are developing a set of libraries using Spring, probably you don't have a SpringBootApplication. So, you have not enabled any auto-scan to resolve beans.
If a bean declared in a configuration file in the library library-a is referred through dependency injection in library-b you need to use #Import to tell Spring how to resolve the bean.
As we said, in library-a you have this configuration file.
#Configuration
public class ConfigurationA {
#Bean
public BeanA beanA() {
return new BeanA();
}
}
In library-b you must have this configuration file if you want to use BeanA
#Configuration
#Import(ConfigurationA.class)
public class ConfigurationB {
#Bean
public BeanB beanB(BeanA beanA) {
return new BeanB(beanA);
}
}
Hope it helps.
With component scanning enabled it's difficult to immediately see where #Import adds value if your view of the world is limited to your own application and its packages. Where it can help is if you are importing bean libraries with their own package structure that you don't want to component scan.
You can place such libraries on your classpath and use #Import to cherry-pick #Configuration classes from within them. That's why it's often referred to as composition because you are composing your #Configuration class from multiple sources.
A typical use case of #Import is when teams develop REST services and realize they need some common configuration.
Every service will have its own namespace i.e. org.mybusiness.specific and will have its #SpringBootApplication (and therefore component scan root) start at this package.
On the other hand, the common library will have its namspace set to org.mybusiness.common and will therefore be out of reach for component scanning.
Thus the need to reference the common configuration class using #Import(CommonConfig.class)
#Import works great when for some reasons you need to register not all but some of the components from a package. In such case #ComponentScan would be too complicated.
See details in the Spring boot component scan include a single class question.

Spring adding ProtobufHttpMessageConverter to controllers without xml config

This should be really simple but i cannot figure how to add ProtobufHttpMessageConverter for Spring Controllers while keeping default HttpMessageConverters.
I have setup client side (RestTemplate) but for every request i send there is error 415: content not supported.
Every example i have found so far refers to either Spring Boot or XML configuration, however neither of these work for me.
In the
answer about similar issue,
extending the WebMvcConfigurerAdapter apparently removes default handlers.
It is stated to extend WebMvcConfigurationSupport to keep default handlers, but given implementation doesn't work for Spring 4x as method call super.addDefaultHttpMessageConverters(); requires List of converters.
I have tried variantions on theme but neither seems to work:
#EnableWebMvc
#Configuration
#ComponentScan
public class RestServiceConfiguration extends WebMvcConfigurationSupport {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new ProtobufHttpMessageConverter());
// getMessageConverters().add(new ProtobufHttpMessageConverter());
// super.configureMessageConverters(getMessageConverters());
super.addDefaultHttpMessageConverters(converters);
}
}
Could somebody help me to add ProtobufHttpMessageConverter while keeping default converters, without xml configuration ?
With your approach you could make it work. However due to the fact that you extended WebMvcConfigurationSupport and used #EnableWebMvc is isn't working. You are basically configuring web support twice now, as #EnableWebMvc is importing WebMvcConfigurationSupport (actually DelegatingWebMvcConfiguration).
To make your current setup work remove the #EnableWebMvc annotation.
#Configuration
#ComponentScan
public class RestServiceConfiguration extends WebMvcConfigurationSupport {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new ProtobufHttpMessageConverter());
super.addDefaultHttpMessageConverters(converters);
}
}
However there is a better way, instead of extend WebMvcConfigurationSupport you should extend WebMvcConfigurerAdapter and implement the extendMessageConverters method instead of the configureMessageConverters.
#EnableWebMvc
#Configuration
#ComponentScan
public class RestServiceConfiguration extends WebMvcConfigurerAdapter {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new ProtobufHttpMessageConverter());
}
}
Note: The extendMessageConverters method has been added in Spring 4.1.3 for earlier versions use the first method!

Spring Security, Method Security annotation (#Secured ) is not working (java config)

I am trying to set up a method security annotation using #Secured("ADMIN") (without any XML, only java config, Spring Boot). But access via roles does not work.
Security Config:
#Configuration
#EnableWebSecurity
public class AppSecurityConfiguration extends WebSecurityConfigurerAdapter{
.....
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/**").fullyAuthenticated().and()
.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
}
.....
}
I want restrict access to the method of the controller:
#RestController
#RequestMapping("/api/groups")
public class GroupController {
#Autowired
private GroupService groupService;
#Secured("ADMIN")
#RequestMapping
public List<Group> list() {
return groupService.findAll();
}
}
Restrict access by the url is working, with:
.antMatchers("/api/**").hasAuthority("ADMIN")
Maybe I forgot to specify that I want restrict by roles?
UPD:
By the rules, At what layer must be #PreAuthorize("hasRole('ADMIN')") in Controller layer or in Service layer?
Kindly add this
#EnableGlobalMethodSecurity(securedEnabled = true)
This element is used to enable annotation-based security in your application (by setting the appropriate attributes on the element), and also to group together security pointcut declarations which will be applied across your entire application context specifically for #Secured.
Hence your code should look like this
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
public class AppSecurityConfiguration extends WebSecurityConfigurerAdapter{..
I know this thread is quite old and my answer alludes to portions of the answers by various people in this thread; but here is a list combined list of pitfalls and answers:
When using #Secured, and the role name is (e.g.) ADMIN; this means an annotation of #Secured("ROLE_ADMIN").
WebSecurityConfigurerAdapter must have #EnableGlobalMethodSecurity(securedEnabled = true)
As with most Spring related proxies, make sure that the class and the secured methods are not in any way final. For Kotlin this means "open" every method as well as the class.
When the class and its methods are virtual ("open"), then there is no implied need for an interface.
Here is part of a working Kotlin example:
#RestController
#RequestMapping("api/v1")
open class DiagnosticsController {
#Autowired
lateinit var systemDao : SystemDao
#RequestMapping("ping", method = arrayOf(RequestMethod.GET))
#Secured("ROLE_ADMIN")
open fun ping(request : HttpServletRequest, response: HttpServletResponse) : String { ...
}
and
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
open class WebSecurityConfig : WebSecurityConfigurerAdapter() {
Regards
There may be many reasons for which method security on a controller does not work.
First because it is never cited as example in Spring Security manual ... joking but it may be tricky to take Spring tools where they do not want to go.
More seriously, you should enable method security as already said by #Mudassar. The manual says :
We can enable annotation-based security using the #EnableGlobalMethodSecurity annotation on any #Configuration instance. For example, the following would enable Spring Security’s #Secured annotation.
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
public class MethodSecurityConfig {
// ...
}
Note that Mudassar's answer is correct till here.
But method security is based on AOP, which by default uses JDK proxying on interfaces. That's the reason why all examples applies method security on the service layer, because the service classes are normally injected in controllers as interfaces.
You can of course use it on controller layer, but :
either all your controllers implement interfaces for you all #Secured annotated methods
or you must switch to class proxying
The rule that I try to follow is :
if I want to secure an URL, I stick to HTTPSecurity
if I need to allow finer grained access, I add security at service layer
This issue was solved.
I add #EnableGlobalMethodSecurity(prePostEnabled = true)
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class AppSecurityConfiguration extends WebSecurityConfigurerAdapter{
}
And in controller i changed #Secured("ADMIN") to #PreAuthorize("hasRole('ADMIN')")
I want to share my decision, may be it will be helpful.
Used spring mvc + spring security, version 4.2.9.RELEASE
For example, i have a Service with method annotated #Secured
#Secured("ACTION_USER_LIST_VIEW")
List<User> getUsersList();
But, it didn't work, because GlobalMethodSecurityConfiguration has inside method.
protected AccessDecisionManager accessDecisionManager()
in which initialized the new RoleVoter() with default rolePrefix = "ROLE_"; (this makes it impossible to use beans to set your rolePrefix) that give to us not working annotations, because RoleVoter expects annotation value which starts with 'ROLE_'
For resolving this problem i override GlobalMethodSecurityConfiguration like this
#Configuration
#EnableGlobalMethodSecurity(securedEnabled = true)
public class AppMethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override
protected AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<>();
ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
expressionAdvice.setExpressionHandler(getExpressionHandler());
decisionVoters.add(getRoleVoter());
decisionVoters.add(new AuthenticatedVoter());
return new AffirmativeBased(decisionVoters);
}
private RoleVoter getRoleVoter() {
RoleVoter e = new RoleVoter();
e.setRolePrefix("");
return e;
}
}
You need to use #secured(ROLE_ADMIN) instead of #secured(ADMIN). You are required to write "ROLE_" infront of your role name. Please find the example mentioned below which is making sure only a user with Admin role can access list() method.
#RestController
#RequestMapping("/api/groups")
public class GroupController {
#Autowired
private GroupService groupService;
#Secured("ROLE_ADMIN")
#RequestMapping
public List<Group> list() {
return groupService.findAll();
}
}
Maybe you should register your AppSecurityConfiguration to same context as WebMvcConfig (that extends WebMvcConfigurerAdapter).
AnnotationConfigWebApplicationContext mvcContext = new AnnotationConfigWebApplicationContext();
mvcContext.register(WebMvcConfig.class, SecurityConfig.class);

Register Spring HandlerInterceptor Without WebMvcConfigurationSupport

I'm trying to register an instance of HandlerInterceptor in Spring using Java Config without extending WebMvcConfigurationSupport. I'm creating a library with an annotation that, when added to a #Configuration class, registers an interceptor that handles a security annotation.
I had an implementation using WebMvcConfigurationSupport#addInterceptors, but that conflicted with other automatic workings in spring, and overrode some of the application's own logic. It also seems incredibly heavy for something that should be simple. I'm now trying:
#Configuration
public class AnnotationSecurityConfiguration {
#Autowired private RequestMappingHandlerMapping requestMappingHandlerMapping;
#PostConstruct
public void attachInterceptors() {
requestMappingHandlerMapping.setInterceptors(new Object[] {
new SecurityAnnotationHandlerInterceptor()
});
}
}
However, it appears that the interceptor gets registered with a completely different instance of RequestMappingHandlerMapping than the one the application actually uses for web requests. Additionally, when implemeted as a BeanFactoryPostProcessor, I get a NullPointerException in HealthMvcEndpoint when I try beanFactory.getBean(RequestMappingHandlerMapping.class)
Just stating #Blauhirn's comment, WebMvcConfigurerAdapter is deprecated as of version 5.0:
Deprecated as of 5.0 WebMvcConfigurer has default methods (made possible by a Java 8 baseline) and can be implemented directly without the need for this adapter
Refer to the new way to do it:
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyCustomInterceptor())
// Optional
.addPathPatterns("/myendpoint");
}
}
Plus, as stated here, do not annotate this with #EnableWebMvc, if you want to keep Spring Boot auto configuration for MVC.
Edit: This class has since been deprecated. See #bosco answer below for the Spring 5 equivalent.
Figured it out, the solution is to use, simply:
#Configuration
public class AnnotationSecurityConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SecurityAnnotationHandlerInterceptor());
}
}
In spring boot, all beans of type WebMvcConfigurer are automatically detected and can modify the MVC context.

Categories

Resources