I'm using Spring Boot (2.3.4.RELEASE) to implement a webservice acting as a OAuth2 resource server. So far I'm able to secure all endpoints and ensure that a valid token is present. In the next step I want to use Spring Method Security. The third step would be to populate custom user details (via UserDetailsService).
How to configure Spring Method Security properly?
I'm not able to enable Spring Method Security (correctly). I have entities saved in database and also set the permissions via MutableAclService. Creating new resource is no problem.
I get the following error on reading the entity
o.s.s.acls.AclPermissionEvaluator : Checking permission 'OWNER' for object 'org.springframework.security.acls.domain.ObjectIdentityImpl[Type: io.mvc.webserver.repository.entity.ProjectEntity; Identifier: my-second-project]'
o.s.s.acls.AclPermissionEvaluator : Returning false - no ACLs apply for this principal
o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter@120d62d, returned: -1
o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.vote.RoleVoter@429b9eb9, returned: 0
o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.access.vote.AuthenticatedVoter@65342bae, returned: 0
o.s.web.servlet.DispatcherServlet : Failed to complete request: org.springframework.security.access.AccessDeniedException: Zugriff verweigert
o.s.s.w.a.ExceptionTranslationFilter : Access is denied (user is not anonymous); delegating to AccessDeniedHandler
I use the following Expression:
@PreAuthorize("hasPermission(#projectKey, 'io.mvc.webserver.repository.entity.ProjectEntity', 'OWNER')")
ProjectEntity findByKey(String projectKey);
How to provide Custom User Details Service?
As far as I understand Spring Security sets the SecurityContext accordingly to the authenticated user (by OAuth2 JWT). I want to set a custom user object (principal) based on the identified user from the token. But just providing a Bean of type UserDetailsService does not seem to work. My UserDetailsService is never invoked...
Security configuration
@Configuration
@EnableWebSecurity
public class ResourceServerConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.httpBasic().disable()
.formLogin().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests(authorize -> authorize
.antMatchers("/actuator/**").permitAll() // TODO: Enable basic auth for actuator
.anyRequest().authenticated()
)
.oauth2ResourceServer().jwt();
}
}
ACL configuration
@Configuration
public class AclConfiguration {
@Bean
public MethodSecurityExpressionHandler methodSecurityExpressionHandler(PermissionEvaluator permissionEvaluator) {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
@Bean
public PermissionEvaluator permissionEvaluator(PermissionFactory permissionFactory, AclService aclService) {
AclPermissionEvaluator permissionEvaluator = new AclPermissionEvaluator(aclService);
permissionEvaluator.setPermissionFactory(permissionFactory);
return permissionEvaluator;
}
@Bean
public PermissionFactory permissionFactory() {
return new DefaultPermissionFactory(MvcPermission.class);
}
@Bean
public MutableAclService aclService(LookupStrategy lookupStrategy, AclCache aclCache, AclRepository aclRepository) {
return new MongoDBMutableAclService(aclRepository, lookupStrategy, aclCache);
}
@Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(
new SimpleGrantedAuthority("ROLE_ADMIN"));
}
@Bean
public PermissionGrantingStrategy permissionGrantingStrategy() {
return new DefaultPermissionGrantingStrategy(new ConsoleAuditLogger());
}
@Bean
public AclCache aclCache(PermissionGrantingStrategy permissionGrantingStrategy,
AclAuthorizationStrategy aclAuthorizationStrategy,
EhCacheFactoryBean ehCacheFactoryBean) {
return new EhCacheBasedAclCache(
ehCacheFactoryBean.getObject(),
permissionGrantingStrategy,
aclAuthorizationStrategy
);
}
@Bean
public EhCacheFactoryBean aclEhCacheFactoryBean(EhCacheManagerFactoryBean ehCacheManagerFactoryBean) {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(ehCacheManagerFactoryBean.getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
@Bean
public EhCacheManagerFactoryBean aclCacheManager() {
EhCacheManagerFactoryBean cacheManagerFactory = new EhCacheManagerFactoryBean();
cacheManagerFactory.setShared(true);
return cacheManagerFactory;
}
@Bean
public LookupStrategy lookupStrategy(MongoTemplate mongoTemplate,
AclCache aclCache,
AclAuthorizationStrategy aclAuthorizationStrategy) {
return new BasicMongoLookupStrategy(
mongoTemplate,
aclCache,
aclAuthorizationStrategy,
new ConsoleAuditLogger()
);
}
}
dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>