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();
}
}
Related
I'm using io.github.lognet:grpc-spring-boot-starter:3.5.3 to add Grpc support. And I have a org.springframework.boot:spring-boot-starter-security dependency. I don't want to add org.springframework.boot:spring-boot-starter-web dependency, cause my application need to use Grpc Netty server without servlets and tomcat server.
Having two implementations of AuthentificationProvider I configured a AuthentificationManager:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final ClientAuthenticationProvider clientAuthenticationProvider;
private final ServiceAuthenticationProvider serviceAuthenticationProvider;
public WebSecurityConfig(ClientAuthenticationProvider clientAuthenticationProvider,
ServiceAuthenticationProvider serviceAuthenticationProvider) {
this.clientAuthenticationProvider = clientAuthenticationProvider;
this.serviceAuthenticationProvider = serviceAuthenticationProvider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(clientAuthenticationProvider)
.authenticationProvider(serviceAuthenticationProvider);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Build fails with message:
Error:(18, 8) java: cannot access javax.servlet.Filter class file
for javax.servlet.Filter not found
This because WebSecurityConfigurerAdapter needs javax.servlet.Filter.
I'm tried to add javax.servlet:javax.servlet-api:4.0.1 dependency, but application failes at runtime when calling authenticationManager.authenticate(...):
public class MytAuthService {
private final AuthenticationManager authenticationManager;
public AbstractAuthService(
#Qualifier("authenticationManagerBean") AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public TokenPair authenticate(AuthRequest request) throws AuthenticationException {
...
authenticationManager.authenticate(createAuthToken(username, password));
...
}
with stacktrace:
java.lang.IllegalStateException: This object has not been built at
org.springframework.security.config.annotation.AbstractSecurityBuilder.getObject(AbstractSecurityBuilder.java:55)
~[spring-security-config-5.2.1.RELEASE.jar:5.2.1.RELEASE] at
org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$AuthenticationManagerDelegator.authenticate(WebSecurityConfigurerAdapter.java:506)
~[spring-security-config-5.2.1.RELEASE.jar:5.2.1.RELEASE] at
org.my.service.auth.MyAuthService.authenticate(MyAuthService.java:75)
~[classes/:?]
When I adding org.springframework.boot:spring-boot-starter-web all works well, but I don't want add Tomcat and servlets to my application.
Can I configure AuthentificationManager to set custom AuthenticationProvider's without extending WebSecurityConfigurerAdapter class or any way? Or may be you can show a good sample/tutorial where Grpc and Spring Security uses only?
Answer myself, I should remove extending WebSecurityConfigurerAdapter and create a AuthenticationManager bean using ProviderManager class.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
private final ClientAuthenticationProvider clientAuthenticationProvider;
private final ServiceAuthenticationProvider serviceAuthenticationProvider;
public WebSecurityConfig(ClientAuthenticationProvider clientAuthenticationProvider,
ServiceAuthenticationProvider serviceAuthenticationProvider) {
this.clientAuthenticationProvider = clientAuthenticationProvider;
this.serviceAuthenticationProvider = serviceAuthenticationProvider;
}
#Bean
public AuthenticationManager authenticationManagerBean() {
return new ProviderManager(Arrays.asList(clientAuthenticationProvider, serviceAuthenticationProvider));
}
}
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;
}
I'm building an application using Spring Data Rest, Spring Boot and Spring Security. I need to use #Secured annotations on methods and I've configured Spring Security in the following way:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// #formatter:off
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.securityContext().securityContextRepository(securityContextRepository())
.and()
.exceptionHandling()
.accessDeniedPage(RestPath.Errors.ROOT + RestPath.Errors.FORBIDDEN)
.and()
.csrf().disable();
}
// #formatter:on
#Bean
public SecurityContextRepository securityContextRepository() {
return new ApiUserSecurityContextRepository();
}
#Bean
public UserDetailsService userDetailsService() {
return new ApiUserDetailsService();
}
#Bean
public AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(Collections.singletonList(authenticationProvider()));
}
#Bean
public AuthenticationProvider authenticationProvider() throws Exception {
final DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
This type of configuration works well for regular MVC controllers and returns 403 when I try to access them. For example, the following controller security works:
#ResponseBody
#RequestMapping(value = RestPath.Configs.SLASH_TEST, method = RequestMethod.GET, produces = MediaTypes.HAL_JSON_VALUE)
#Secured({"ROLE_USER"})
public ResponseEntity test(#RequestParam(value = RestParam.DB_TEST, required = false) final boolean dbTest) throws ApplicationAvailabilityException {
final AppTestData appTestData = configService.testAppAvailability(dbTest);
return ResponseEntity.ok(projectionFactory.createProjection(AppTestProjection.class, appTestData));
}
However, when I try to use #Secured annotation over a rest repository - it does NOT, e.g.:
#RepositoryRestResource(collectionResourceRel = Shop.COLLECTION_NAME, path = RestResourceRel.SHOPS, excerptProjection = StandardShopProjection.class)
#Secured({"ROLE_USER"})
public interface RestShopRepository extends MongoRepository<Shop, String> {
#Secured({"ROLE_ADMIN"})
#Override
Shop findOne(String s);
}
ApiUserSecurityContextRepository is getting called for both of the methods, but only a custom MVC controller is get to the end of chain and I can check that it accesses vote() method in RoleVoter class for granting access.
As an example, I've checked Spring Data Rest + Spring Security sample, so #Secured or #PreAuthorize annotations should work with Spring Data Rest. Any ideas why they don't work?
Finally resolved the issue. The problem was in the following, I had another ShopRepository in different application module, which was not annotated with #RepositoryRestResource and it was the one which was used when accessing it using REST.
The following line of configuration in custom RepositoryRestConfigurerAdapter fixed the exploration of repositories which need to be exposed, so only annotated ones are exposed now:
config.setRepositoryDetectionStrategy(RepositoryDetectionStrategy.RepositoryDetectionStrategies.ANNOTATED);
After that I could not access the resource at all using REST, so I've figured out that it is not visible to Spring. I just had to enable Mongo repositories on API level with annotation #EnableMongoRepositories.
I'm struggling with my Spring Security configuration which I wasn't able to make it works so far.
I don't know why my custom PermissionEvaluator is not getting invoked and my #PreAuthorize annotation using hasPermission expression are ignored.
I'm using Spring 4.2.4 and Spring security 4.1.0
Her is my code :
Web Security configuration
#Configuration
#EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http //
.addFilterBefore(wafflePreAuthFilter(), AbstractPreAuthenticatedProcessingFilter.class) //
.authenticationProvider(preauthAuthProvider()) //
.csrf().disable() //
.authorizeRequests() //
.antMatchers("/ui/**").authenticated() //
.anyRequest().permitAll();
}
#Bean
public WafflePreAuthFilter wafflePreAuthFilter() throws Exception {
WafflePreAuthFilter filter = new WafflePreAuthFilter();
filter.setAuthenticationManager(authenticationManager());
return filter;
}
#Bean
public PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
PreAuthenticatedAuthenticationProvider preauthAuthProvider = new PreAuthenticatedAuthenticationProvider();
preauthAuthProvider.setPreAuthenticatedUserDetailsService(userDetailsServiceWrapper());
return preauthAuthProvider;
}
#Bean
public UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> userDetailsServiceWrapper() {
UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken> wrapper = new UserDetailsByNameServiceWrapper<PreAuthenticatedAuthenticationToken>();
wrapper.setUserDetailsService(myUserDetailsService());
return wrapper;
}
#Bean
public UserDetailsService myUserDetailsService() {
return new myUserDetailsService();
}
}
Method Security configuration
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
public class MyServiceMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Bean
public PermissionEvaluator myPermissionEvaluator() {
return new DcePermissionEvaluator();
}
#Override
public MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(myPermissionEvaluator());
return expressionHandler;
}
}
PermissionEvaluator
public class MyPermissionEvaluator implements PermissionEvaluator {
#Autowired
private MyService myAutowiredService;
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
// checking permissions
return true;
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
// checking permissions
return true;
}
}
Anyone can give me an hint on what to do ?
By the way if I change MyServiceMethodSecurityConfig into this, then myPermissionEvaluator is processed but dependencies injection doesn't work as it isn't managed by Spring :
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = false)
public class MyServiceMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#Override
public MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new DcePermissionEvaluator());
return expressionHandler;
}
}
I ran into this issue. It seemed to be caused by the annotation #EnableGlobalMethodSecurity being specified in multiple places.
Once I removed it from locations other than above my implementation of GlobalMethodSecurityConfiguration things started working as expected.
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.