Is it possible to turn on and off #Secured annotation [duplicate] - java

Is there a way I can disable the global method security using the boolean securityEnabled from my config.properties? Any other approach?
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled=true)
#PropertySource("classpath:config.properties")
public class SecurityConfig
extends WebSecurityConfigurerAdapter {
#Value("${securityconfig.enabled}")
private boolean securityEnabled;
...
}

The easiest way to do this is:
Extract method security to its own class
Remove the securedEnabled attribute entirely
Override the customMethodSecurityMetadataSource method and return the result based on the configured value.
For example:
#EnableWebSecurity
#Configuration
#PropertySource("classpath:config.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
}
#EnableGlobalMethodSecurity
#Configuration
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Value("${securityconfig.enabled}")
private boolean securityEnabled;
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return securityEnabled ? new SecuredAnnotationSecurityMetadataSource() : null;
}
}

I've managed this by defining a Spring "securityDisabled" profile and conditionally applying security config based off that. I'm using Spring Boot 2.0.2. I believe this should work if not using Spring Boot and in previous versions of Spring Boot, but I have not tested. It's possible some tweaks may be required to property and class names because I know in Spring 2.0 some of that changed.
// In application.properties
spring.profiles.include=securityDisabled
Then my security config looks like this:
#Configuration
public class SecurityConfig {
// When the securityDisabled profile is applied the following configuration gets used
#Profile("securityDisabled")
#EnableWebSecurity
public class SecurityDisabledConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// Configure http as needed from Spring Security defaults when
// NO security is desired
}
}
// When the securityDisabled profile is NOT applied the following configuration gets used
#Profile("!securityDisabled")
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SecurityEnabledConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// Configure http as needed from Spring Security defaults when
// security is desired
}
}
}

In Springboot2, a simple solution consists in replacing the security method interceptor by a dummy one when the security is off :
#EnableGlobalMethodSecurity(prePostEnabled = true)
static class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Value("${disableSecurity}")
private boolean disableSecurity;
public MethodInterceptor methodSecurityInterceptor(MethodSecurityMetadataSource methodSecurityMetadataSource) {
return disableSecurity ? new SimpleTraceInterceptor()
: super.methodSecurityInterceptor(methodSecurityMetadataSource);
}
}

Thanks to Rob Winch for the solution. For folks who would like to do something similar but with prePostEnabled i have tried and tested the below similar approach and works just fine.
#EnableGlobalMethodSecurity(securedEnabled = true)
#Configuration
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Value("${security.prePostEnabled}")
private boolean prePostEnabled;
#Autowired
private DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler;
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return prePostEnabled ? new PrePostAnnotationSecurityMetadataSource(new ExpressionBasedAnnotationAttributeFactory(defaultMethodSecurityExpressionHandler)) : null ;
}}
EDIT: In addition to above i realized it is required to add following beans to the class. The below will help using the expression based pre invocation checks along with avoiding "ROLE_" prefix that is defaulted in all the handlers
protected AccessDecisionManager accessDecisionManager() {
AffirmativeBased accessDecisionManager = (AffirmativeBased) super.accessDecisionManager();
ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
expressionAdvice.setExpressionHandler(getExpressionHandler());
//This is required in order to allow expression based Voter to allow access
accessDecisionManager.getDecisionVoters()
.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));
//Remove the ROLE_ prefix from RoleVoter for #Secured and hasRole checks on methods
accessDecisionManager.getDecisionVoters().stream()
.filter(RoleVoter.class::isInstance)
.map(RoleVoter.class::cast)
.forEach(it -> it.setRolePrefix(""));
return accessDecisionManager;
}
/**
* Allow skip ROLE_ when check permission using #PreAuthorize, like:
* #PreAuthorize("hasAnyRole('USER', 'SYSTEM_ADMIN')")
* Added all the Beans
*/
#Bean
public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
defaultMethodSecurityExpressionHandler.setDefaultRolePrefix("");
return defaultMethodSecurityExpressionHandler;
}

Related

Intermittent issue with Spring security #PreAuthorize annoation

I am currently working on Spring boot project which is acting as datasource for UI layer and all endpoint within project are protected using OIDC.
Everything is working as expected however when i try to protect any controller endpoint by putting #PreAuthorize annotation it works well in local development environment (JDK 8) but when things gets deployed over cloud (PCF, OpenJDK) it having weird behavior as 8 out of 10 times it works and 2 times it allows user to bypass security at method level and get desired output.Not sure how to fix this, i am suspecting something do with AOP proxy here but not sure.
Any pointer will be much appreciated.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true, jsr250Enabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
Security config class for reference.
#Configuration
#EnableWebSecurity(debug = true)
public class SecurityConfig extends ResourceServerConfigurerAdapter {
#Value("${jwt-token.audience}")
private String resourceId;
#Value("${jwt-token.issuer-uri}")
private String issuer;
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(resourceId).stateless(false);
}
#Override
public void configure(final HttpSecurity http) throws Exception {
// #formatter:off
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/swagger-ui/**", "/v3/api-docs", "/swagger-ui.html").permitAll().anyRequest()
.authenticated().and().oauth2ResourceServer().jwt().decoder(jwtDecoder())
.jwtAuthenticationConverter(new CustomJwtAuthenticationConverter());
// #formatter:on
}
#Bean
JwtDecoder jwtDecoder() {
//omitted for brevity
return jwtDecoder;
}
#Bean
public RequestContextListener requestContextListener() {
return new RequestContextListener();
}
}

Converting XML configuration to java configuration in spring security for global method security

I am trying to convert xml configuration to java config
My XML configuration like this
<security:global-method-security secured-annotations="enabled" pre-post-annotations="enabled" access-decision-manager-ref="methodAccessDecisionManager">
<security:expression-handler ref="methodExpressionHandler"/>
</security:global-method-security>
I am tried to convert with annotation
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
But I am not getting how I can convert and use with global security method access-decision-manager-ref="methodAccessDecisionManager"
and <security:expression-handler ref="methodExpressionHandler"/>
You could write a custom method security configruation, see Spring Security Reference:
5.10.2 GlobalMethodSecurityConfiguration
Sometimes you may need to perform operations that are more complicated than are possible with the #EnableGlobalMethodSecurity annotation allow. For these instances, you can extend the GlobalMethodSecurityConfiguration ensuring that the #EnableGlobalMethodSecurity annotation is present on your subclass. For example, if you wanted to provide a custom MethodSecurityExpressionHandler, you could use the following configuration:
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
// ... create and return custom MethodSecurityExpressionHandler ...
return expressionHandler;
}
}
For additional information about methods that can be overridden, refer to the GlobalMethodSecurityConfiguration Javadoc.
Your modified code:
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
private AccessDecisionManager accessDecisionManager;
#Autowired
private MethodSecurityExpressionHandler methodSecurityExpressionHandler;
protected MethodSecurityExpressionHandler createExpressionHandler() {
return methodSecurityExpressionHandler;
}
protected AccessDecisionManager accessDecisionManager() {
return accessDecisionManager;
}
}

How can I use my custom ConfigurationProperties in custom WebAuthenticationDetails?

In Spring 4.3.x, I have a custom class, call it MyWebAuthenticationDetails that extends WebAuthenticationDetails. I need to use properties in that class that are defined in application.properties. I get those properties via a custom class, called AuthenticationProperties, that uses #ConfigurationProperties. Normally I would autowire in AuthenticationProperties on the class constructor, but that is not possible for MyWebAuthenticationDetails. How can I access properties from within my extension of WebAuthenticationDetails?
Since your MyWebAuthenticationDetails custom details object will be constructed through an AuthenticationDetailsSource bean (which you should have already declared), you can access the AuthenticationProperties as an injected bean thus you will have all your properties accessible.
A simple Java configuration template would be as follows (note that this is not a full functional configuration and aims only to highlight the important configuration entries):
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authenticationDetailsSource(myAuthenticationDetailsSource())/* and all the missiong HTTP configuration*/;
}
#Bean
private AuthenticationDetailsSource<HttpServletRequest, MyWebAuthenticationDetails> myAuthenticationDetailsSource() {
return new MyAuthenticationDetailsSource<HttpServletRequest, MyWebAuthenticationDetails>();
}
private final class MyAuthenticationDetailsSource extends AuthenticationDetailsSourceImpl<HttpServletRequest, MyWebAuthenticationDetails> {
#Autowired
private AuthenticationProperties authenticationProperties;
#Override
public MyWebAuthenticationDetails buildDetails(HttpServletRequest request) {
return new MyWebAuthenticationDetails(request, this.authenticationProperties);
}
}
}

Using custom AccessDecisionManager with HttpSecurity addFilterBefore does not work

The custom AccessDecisionManager does not get invoked either when filter is added or otherwise. Ideally would like to set filterBefore and custom AccessDecisionManager (using SpringBoot 1.5.2-release version). Alternately would like to call setRolePrefix on default RoleVoter. Also added DefaultRolesPrefixPostProcessor as mentioned in Section 8.3 of Spring 3 to 4 migration guide, but still RoleVoter looks for "ROLE_" prefix
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
AuthenticationFilter authenticationFilter;
#Bean
public AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters
= Arrays.asList(
new WebExpressionVoter(),
new RoleVoter(),
new AuthenticatedVoter());
return new AffirmativeBased(decisionVoters);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().accessDecisionManager(accessDecisionManager())
.anyRequest().authenticated();
http.addFilterBefore(authenticationFilter, BasicAuthenticationFilter.class);
}
}
It seems like you're expecting your AccessDecisionManager to get called to grant/deny access to your secure methods.
Try the following:
Remove the #EnableGlobalMethodSecurity annotation from your SecurityConfig.
Move the annotation to another configuration class that extends GlobalMethodSecurityConfiguration.
For example:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Autowired
AccessDecisionManager accessDecisionManager;
#Override
protected AccessDecisionManager accessDecisionManager() {
return this.accessDecisionManager;
}
}
Explanation:
GlobalMethodSecurityConfiguration takes care of creating the method interceptors, and it doesn't look for an AccessDecisionManager bean to use. It must be provided one via an overridden method.
NOTE:
By default, two AccessDecisionManagers are used: one for filters (created by AbstractInterceptUrlConfigurer) and another to secure methods (created by GlobalMethodSecurityConfiguration).
Alternately would like to call setRolePrefix on default RoleVoter
You could do this without ever touching the default AccessDecisionManagers:
#Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("");
}
This will set the role prefix to "" on both default AccessDecisionManagers.

Spring Security 3.2 - configuring global method security to use role hierarchy

Using Spring Security 3.2.5 and Spring 4.1.2, 100% Java config
Our webapp has global method security enabled and service methods annotated with #PreAuthorize - everything is working as expected. I'm trying to add a role hierarchy and having no success at all. Here's the hierarchy I'm trying to achieve:
ROLE_ADMIN can access all methods that ROLE_USER can access.
ROLE_USER can access all methods that ROLE_DEFAULT can access.
Despite my best efforts, a user with ROLE_ADMIN receives a 403 when doing something that results in a call to a method annotated with #PreAuthorized("hasAuthority('ROLE_DEFAULT')")
Here's the relevant configuration code:
AppInitializer
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer
{
#Override
protected Class<?>[] getRootConfigClasses()
{
return new Class[]
{
AppConfig.class, SecurityConfig.class
};
}
#Override
protected Class<?>[] getServletConfigClasses()
{
return new Class[]
{
MvcConfig.class
};
}
// other methods not shown for brevity
}
AppConfig.java
#Configuration
#ComponentScan(basePackages={"myapp.config.profile", "myapp.dao", "myapp.service", "myapp.security"})
public class AppConfig
{
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth,
AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> detailSvc) throws Exception
{
PreAuthenticatedAuthenticationProvider authProvider = new PreAuthenticatedAuthenticationProvider();
authProvider.setPreAuthenticatedUserDetailsService(detailSvc);
auth.authenticationProvider(authProvider);
}
// other methods not shown for brevity
}
SecurityConfig.java
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
PKIAuthenticationFilter pkiFilter = new PKIAuthenticationFilter();
pkiFilter.setAuthenticationManager(authenticationManagerBean());
http.authorizeRequests()
.antMatchers("/app/**").fullyAuthenticated()
.and()
.anonymous().disable()
.jee().disable()
.formLogin().disable()
.csrf().disable()
.x509().disable()
.addFilter(pkiFilter)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(WebSecurity web) throws Exception
{
// ignore everything but /app/*
web.ignoring().regexMatchers("^(?!/app/).*");
}
}
MvcConfig.java
#Configuration
#EnableWebMvc
#ComponentScan({"myapp.controller"})
public class MvcConfig extends WebMvcConfigurerAdapter
{
// resource handlers, content negotiation, message converters configured here
}
In the same package as SecurityConfig (so it is thus part of the AppConfig component scan) I had this class:
GlobalMethodSecurityConfig.java
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration
{
#Bean
public RoleHierarchy roleHierarchy()
{
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER > ROLE_DEFAULT");
return roleHierarchy;
}
#Bean
public RoleVoter roleVoter()
{
return new RoleHierarchyVoter(roleHierarchy);
}
#Bean
#Override
protected AccessDecisionManager accessDecisionManager()
{
return new AffirmativeBased(Arrays.asList(roleVoter()));
}
// The method below was added in an attempt to get things working but it is never called
#Override
protected MethodSecurityExpressionHandler createExpressionHandler()
{
DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler();
handler.setRoleHierarchy(roleHierarchy());
return handler;
}
}
In another attempt I made AppConfig extend GlobalMethodSecurityConfiguration but a user with ROLE_ADMIN cannot call a method requiring ROLE_DEFAULT access.
I'm sure I've misconfigured something somewhere but I can't figure out where I've gone wrong despite reading everything I can find on configuring global method security with a role hierarchy. It appears this would be trivial using XML configuration but the Java config solution eludes me.
I'd override GlobalMethodSecurityConfiguration#accessDecisionManager method. You can see source code that RoleVoter uses.
Here is my suggested overridden source code.
#Override
protected AccessDecisionManager accessDecisionManager() {
var roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_SUPER > ROLE_ADMIN");
var expressionHandler = (DefaultMethodSecurityExpressionHandler) getExpressionHandler();
expressionHandler.setRoleHierarchy(roleHierarchy);
var expressionAdvice = new ExpressionBasedPreInvocationAdvice();
expressionAdvice.setExpressionHandler(expressionHandler);
return new AffirmativeBased(List.of(
new RoleHierarchyVoter(roleHierarchy),
new PreInvocationAuthorizationAdviceVoter(expressionAdvice),
new AuthenticatedVoter(),
new Jsr250Voter()
));
}
Since this question keeps getting views I thought I'd post a follow-up to it. The problem appears to be with the line
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER > ROLE_DEFAULT");
I don't remember why I wrote the hierarchy like that but it's not correct. The API for that method handles the same situation thusly:
Role hierarchy: ROLE_A > ROLE_B and ROLE_B > ROLE_C.
Directly assigned authority: ROLE_A.
Reachable authorities: ROLE_A, ROLE_B, ROLE_C.
Eventually it became clear that a hierarchical model didn't fit our roles so we instead implemented a finer-grained set of authorities mapped to roles, as mentioned in the Spring Security Reference:
For more complex requirements you may wish to define a logical mapping between the specific access-rights your application requires and the roles that are assigned to users, translating between the two when loading the user information.

Categories

Resources