I have in a given project the circumstance that I need to extend or overwrite the default AccessDeniedHandler of spring.security. The reason is not important, but I need this to be able to implement custom behaviour whenever a MissingCsrfTokenException or InvalidCsrfTokenException is thrown.
My question is: How to override the org.springframework.security.web.access.AccessDeniedHandler class properly?
Meanwhile I figured out 3 ways which seem to be promising:
Set my custom version of AccessDeniedHandlerImpl when configuring spring.security as instance of my class
Set my custom accessDeniedHandler when configuring spring.security and get it as a bean
Override AccessDeniedHandlerImpl with #Component named AccessDeniedHandler and set it as #Primary
I am new to spring framework and unsure how to solve this the best way.
From my understanding as a technician, I would prefer setting my custom AccessDeniedHandler through spring.security configuration (independent of the way I configure it there). Because using a component will require any other developer to look up for the annotations (but maybe I am wrong with this).
Implementing it through spring security configuration
// in the spring security configuration class
protected void configure(final HttpSecurity http) throws Exception {
http
.exceptionHandling().accessDeniedHandler(new CustomAccessDeniedHandler());
}
or
// in the spring security configuration class
#Bean
public AccessDeniedHandler accessDeniedHandler(){
return new CustomAccessDeniedHandler();
}
protected void configure(final HttpSecurity http) throws Exception {
http
.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
}
As a component override
// in custom AccessDeniedHandler class
#Component
#Primary
public CustomAccessDeniedHandler implements AccessDeniedHandler {
// custom implementation
}
Which will automatically fetch my custom AccessDeniedHandler instead of the default AccessDeniedHandlerImpl defined in org.springframework.security.
Finally the point for me is to figure out, what is the better way to implement this, through configuration or #Component and #Primary.
I am trying to load the required parameters like accessURI,client_id, client_secret from properties file like below. It never loads the properties, I see many sites mention this as example. It is working if I tries to set it explicitly.
In this example
https://dzone.com/articles/build-a-spring-boot-app-with-secure-server-to-serv
#Bean()
#ConfigurationProperties(prefix ="my.oauth2.client")
protected ClientCredentialsResourceDetails oAuthDetails() {
return ClientCredentialsResourceDetails();
But it never loaded the properties so I need to change to use set methods.I am not able to figure out why it did not load, I believe I cannot mention this in #EnableConfigurationProperties as it does not have the configuration configured.
I tried searching but could not find a matching reason.
Basically, the binding of properties to class members works as described in the example.
The properties have to be present either as YAML or properties file:
spring:
security:
oauth2:
client:
registration:
oauth:
client-id: XXXXXX
client-secret: YYYYYYY
scope: openid
redirect-uri: http://redirect
access-token-uri: https://token
client-authentication-method: basic
authorization-grant-type: client_credentials
Then the #ConfigurationProperties annotation has to be declared with the correct prefix. In this example it is "spring.security.oauth2.client.registration.oauth".
#Bean
#ConfigurationProperties(prefix ="spring.security.oauth2.client.registration.oauth")
protected ClientCredentialsResourceDetails oAuthDetails() {
return new ClientCredentialsResourceDetails();
}
As stated in the Spring Boot documentation you normally do not have to set the #EnableConfigurationProperties annotation unless you are developing your own auto-configuration.
But if you are working on a non Spring Boot project the #EnableConfigurationProperties annotation has to be declared, describing the class where we want to use the #ConfigurationProperties annotation:
#Configuration()
#EnableConfigurationProperties(MyProperties.class)
public class MyConfig {
}
with
#ConfigurationProperties(prefix="myproperties")
public class MyProperties {
}
Alternatively you can also use configuration property scanning which works similarly to the component scanning by declaring the #ConfigurationPropertiesScan annotation:
#SpringBootApplication
#ConfigurationPropertiesScan({ "my.app" })
public class MyApplication {
}
Hope, this helps. Pero
In this example there is an #Autowired annotation on the configureGlobal method:
#EnableWebSecurity
public class MultiHttpSecurityConfig {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth
.inMemoryAuthentication()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
Is that necessary or does Spring automatically inject the AuthenticationBuilder on methods annotated with #EnableWebSecurity??
The code snippet is pulled from when-to-use-spring-securitys-antmatcher
According to Spring documentation #EnableWebSecurity is an annotation that only switches off the default web application security configuration in order to let you add some custom features like the configureGlobal.
configureGlobal should be #Autowired in order to get the AuthenticationManagerBuilder bean and define the authentication type for the application.
In conclusion #EnableWebSecurity doesn't inject beans, it only provides a way to customize the web security application.
#EnableWebSecurity
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 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);