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);
Related
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());
}
}
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;
}
}
I successfully configured spring-security-oauth2 so that external apps can authenticate with my application. However based on the external app and based on what the user allows, only a subset of my API should be accessible to clients. The available subset is determined by the OAuth Scopes.
In classic Spring applications I could use #PreAuthorize to enforce boundaries based on roles:
#Controller
public class MyController {
#PreAuthorize("hasRole('admin')")
#RequestMapping("...")
public String doStuff() {
// ...
}
}
How do I do the same when using OAuth and with Scopes instead of roles?
Spring OAuth ships with the OAuth2MethodSecurityExpressionHandler, a class that adds the ability to do such checks using the #PreAuthorize expressions. All you need to do is register this class, e.g. like this if you are using Javaconfig:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public static class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
Now you can simply use:
#PreAuthorize("#oauth2.hasScope('requiredScope')")
to secure your request methods. To see which further methods are available besided hasScope check the class OAuth2SecurityExpressionMethods.
The downside is that OAuth2MethodSecurityExpressionHandler extends the DefaultMethodSecurityExpressionHandler and thus you cannot combine it with other classes that also extend this class.
As an alternative you could also map OAuth scopes to classic user roles.
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.
Can Spring Security use #PreAuthorize on Spring controllers methods?
Yes, it works fine.
You need <security:global-method-security pre-post-annotations="enabled" /> in ...-servlet.xml. It also requires CGLIB proxies, so either your controllers shouldn't have interfaces, or you should use proxy-target-class = true.
See Spring Security FAQ (emphasis mine).
In a Spring web application, the application context which holds the
Spring MVC beans for the dispatcher servlet is often separate from the
main application context. It is often defined in a file called
myapp-servlet.xml, where “myapp” is the name assigned to the Spring
DispatcherServlet in web.xml. An application can have multiple
DispatcherServlets, each with its own isolated application context.
The beans in these “child” contexts are not visible to the rest of the
application. The “parent” application context is loaded by the
ContextLoaderListener you define in your web.xml and is visible to all
the child contexts. This parent context is usually where you define
your security configuration, including the
element). As a result any security constraints applied to methods in
these web beans will not be enforced, since the beans cannot be seen
from the DispatcherServlet context. You need to either move the
declaration to the web context or moved the
beans you want secured into the main application context.
Generally we would recommend applying method security at the service
layer rather than on individual web controllers.
If you apply pointcuts to service layer you only need to set <global-method-security> in your app's security context.
If you're using Spring 3.1, you can do some pretty cool stuff with this. Take a look at https://github.com/mohchi/spring-security-request-mapping. It's a sample project that integrates #PreAuthorize with Spring MVC's RequestMappingHandlerMapping so that you can do something like:
#RequestMapping("/")
#PreAuthorize("isAuthenticated()")
public String authenticatedHomePage() {
return "authenticatedHomePage";
}
#RequestMapping("/")
public String homePage() {
return "homePage";
}
A request for "/" will call authenticatedHomePage() if the user is authenticated. Otherwise it will call homePage().
It's over two years since this question was asked but because of problems I had today I'd rather discourage using #Secured, #PreAuthorize, etc. on #Controllers.
What didn't work for me was #Validated combined with #Secured controller:
#Controller
#Secured("ROLE_ADMIN")
public class AdministrationController {
// #InitBinder here...
#RequestMapping(value = "/administration/add-product", method = RequestMethod.POST)
public String addProductPost(#ModelAttribute("product") #Validated ProductDto product, BindingResult bindingResult) {
// ...
}
Validator simply does not fire (Spring MVC 4.1.2, Spring Security 3.2.5) and no checks are performed.
Similar problems are caused by CGLIB proxies used by Spring (when there is no interface implemented by a class, Spring creates CGLIB proxy; if the class implements any interface then JDK Proxy is generated - documentation, well explained here and here).
As mentioned in the answers that I linked above, is't better to use Spring Security annotations on service layer that usually implements interfaces (so JDK Proxies are used) as this does not lead to such problems.
If you want to secure web controllers, the better idea is to use <http> and <intercept-url /> that are bound to specific urls rather than methods in controllers and work pretty well. In my case:
<http use-expressions="true" disable-url-rewriting="true">
...
<intercept-url pattern="/administration/**" access="hasRole('ROLE_ADMIN')" />
</http>
There is already a reply regarding how to make it work by changing xml configuration; however, if you are working with code-based configuration, you can achieve the same by placing the following annotation over your #Configuration class:
#EnableGlobalMethodSecurity(prePostEnabled=true)
To Extend the answer provided by Andy, you can use:
#PreAuthorize("hasRole('foo')")
to check the specific role.
step1: add #EnableGlobalMethodSecurity(prePostEnabled = true) annotation in SecurityConfig class .
like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
.....
}
step 2: you can add #PreAuthorize() annotation in controller or service or repository layer. in a method or class level.
for example:
#RestController
#PreAuthorize("isAuthenticated()")
public class WebController {
#PreAuthorize("permitAll()")
#GetMapping("/")
public String home() {
return "Welcome home!";
}
#GetMapping("/restricted")
public String restricted() {
return "restricted method";
}
}
or
#RestController
public class AdminController {
#PreAuthorize("hasRole('ADMIN')")
#GetMapping("/admin")
public String adminMethod() {
}
}
First you need to add this annotation in your WebSecurityConfig to enable #Pre and #Post annotations.
#EnableGlobalMethodSecurity(prePostEnabled = true)
You can also check roles/authorities as follows
#PreAuthorize("hasAuthority('ROLE_ADMIN')")
equivalent to
#PreAuthorize("hasRole('ROLE_ADMIN')")
You can also check multiple roles/authorities as follows
#PreAuthorize("hasAuthority('ROLE_ADMIN') or hasAuthority('ROLE_USER') or ...")