Spring Reactive Security | How to implement a reactive PermissionEvaluator - spring-security

Assuming we got an annotated rest controller method with:
#PreAuthorize("hasPermission(#username, 'USER_PROFILE', 'WRITE')")
In Spring MVC we would implement a PermissionEvaluator to implement the authorization hidden behind the following method signature
boolean hasPermission(
Authentication authentication,
Serializable targetId,
String targetType,
Object permission
)
This still seems to work when using Spring WebFlux as long as you do not need to call a reactive service/method inside of the hasPermission method, which I guess is rarely the case since you would typically like to use the reactive interface for your database layer as well. If you would call a reactive service inside this method anyway you would need to call block() on some Mono at some time and thereby run into trouble since you are called from within a reactive pipeline.
In the tutorial from Josh Long about Security with Spring WebFlux he explains how to implement authorization directly on the SecurityWebFilterchain using path matchers by providing custom ReactiveAuthorizationManagers. But there is no explanation of how to use the PreAuthorize annotation in Spring WebFlux.
I was expecting to implement some ReactivePermissionEvaluator
Mono<Boolean> hasPermission(
Authentication authentication,
Serializable targetId,
String targetType,
Object permission
)
that would allow using reactive services in the implementation as well but I wasn't able to find any implementation of the ReactiveAuthorizationManager that would scan for the PreAuthorize annotation and dispatch the evalutation to a reactive PermissionEvaluator nor does the ReactivePermissionEvaluator interface exist.
So finally the question is, how to implement a reactive PermissionEvaluator that allows calling a reactive service, e.g. to query the database for the autorization information without blocking?

Sadly... this is not yet supported by Spring Security in https://github.com/spring-projects/spring-security/issues/5046

Here is how you should do it. Just get DefaultMethodSecurityExpressionHandler bean from applicationContext and explicitly replace permissionsEvaluator.
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class SecurityConfiguration {
#Autowire
private ApplicationContext applicationContext;
#Bean
#DependsOn({"methodSecurityExpressionHandler"})
public SecurityWebFilterChain springSecurityFilterChain(
ServerHttpSecurity http) {
DefaultMethodSecurityExpressionHandler defaultWebSecurityExpressionHandler = this.applicationContext.getBean(DefaultMethodSecurityExpressionHandler.class);
defaultWebSecurityExpressionHandler.setPermissionEvaluator(permissionEvaluator());
return http.csrf().disable()
.httpBasic().disable()
.formLogin().disable()
.logout().disable()
.securityContextRepository(NoOpServerSecurityContextRepository.getInstance())
.addFilterAt(tokenAuthenticationFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.authorizeExchange()
.anyExchange().authenticated()
.and().build();
}
PermissionEvaluator permissionEvaluator() {
return new PermissionEvaluator() {
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
//Custom logic to evaluate #PreAuthorize("hasPermission('123', '123')")
return false;
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
//Custom logic to evaluate #PreAuthorize("hasPermission('123', '123','123')")
return false;
}
};
}

Related

How can I configure multiple authentication managers depending on the incoming URL for an authorization server?

With Spring Security <5.2
In a legacy project, for a password grant scenario, I need to configure an authorization server.
Currently it is done extending AuthorizationServerConfigurerAdapter, and the authorization endpoints are configured overriding the configure(AuthorizationEndpointsServerConfigurer) method.
My problem is that this configurer takes one AuthenticationManager for the password grant, when I would need something like an AuthenticationManagerResolver (but I can't upgrade to 5.2) to be able to apply a different authentication depending on the incoming URL (an authentication manager for admin URLs, e.g. "/admin/**", and another one for non-admin).
How can I do that? I can change the approach, but again I can't upgrade.
You can try implementing your own DelegatingAuthenticationManager, inject the list of your AuthenticationManagers in it, and put your logic in authenticate method. E.g:
#Component("delegatingAM")
public class DelegatingAuthenticationManager implements AuthenticationManager {
private final List<AuthenticationManager> ams;
#Autowire
public DelegatingAuthenticationManager(#Qualifier("x") AuthenticationManager amX, #Qualifier("y") AuthenticationManager amY) {
this.ams = List.of(amX, amY); // Arrays.asList(amX, amY);
// you can inject variables for your conditions here
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (...) this.ams.get(0).authenticate(authentication);
if (...) this.ams.get(0).authenticate(authentication);
// Or you can loop over the list like AuthenticationManager is implemented with AuthenticatioProvider
}
}
Then inject it to AuthorizationServerConfigurerAdapter
#Configuration
#EnableAuthorizationServer
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("delegatingAM")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.authenticationManager(this.authenticationManager)
}
...
}
Hope it will help, for the worse case, you could start to think about using many AuthorizationServerSecurityFilterChains, one AuthenticationManager for each. And based on the URL, direct the request to the right SecurityFilterChain.

Get mapper from AuthenticationSuccessHandler

I'm trying to return some User data after a successful login in a Spring Boot application.
To do so, I need to serialize my Principal with the Jackson mapper that is already set-up at bootstrap via a factory.
Is there a way to inject it into the authentication handler?
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setStatus(HttpServletResponse.SC_OK);
UserDetails me = (UserDetails) authentication.getPrincipal();
PrintWriter writer = response.getWriter();
/*mapper.writeValue(writer, user); <- how to get the mapper?
writer.flush();*/
}
tl;dr;
Instead of using a Factory, make the ObjectMapper a Spring bean. That way it can be injected into both Spring and Jersey components.
#Bean
public ObjectMapper mapper() {
ObjectMapper mapper = new ObjectMapper();
return mapper;
}
When you use an HK2 (Jersey's DI framework) Factory to create the ObjectMapper, the injection is only available to components that are retrieved through the HK2 ServiceLocator, which is the HK2 analogue of the Spring ApplicationContext.
When you use Spring-Boot with Jersey, how the two interact under the hood, is through the HK2 spring-bridge. So for example, say the AuthenticationSuccessHandler is registered in the ApplicationContext. With the spring-bridge configured, we can also get the AuthenticationSuccessHandler through the ServiceLocator
ServiceLocator l = ...
AuthenticationSuccessHandler handler = l.getService(AuthenticationSuccessHandler.class);
That should work. If you have the ObjectMapper binded through an HK2 Factory, and you were to #Inject the mapper into the AuthenticationSuccessHandler, it would be injected, when retrieved through the ServiceLocator.
That being said, Spring Security doesn't go through HK2, so it never has a chance to get the ObjectMapper through HK2. But if you register the mapper as a Spring bean, then Spring can inject it into the AuthenticationSuccessHandler. And the mapper is still available through HK2 ServiceLocator (because of the spring-bridge), so you can still use it with Jersey, same way as if it were binded with a Factory.

Spring OAuth and Boot Integration Test

What is the best way to run Spring Boot integration tests agains a OAuth Resource server configured web application.
I can think of two theoretical approaches:
Mock the security context in the resource server without acutally calling the Authorization server.
Embed the Authorization server as part of the test and redirect the authentication to it.
I was wondering how others have approach this problem.
This answer is very similar to the one provided by Ondrej, but is quite a bit simpler.
Spring Security 4 provides Test support. To use it ensure you have spring-security-test-4.0.2.RELEASE.jar (or newer version on your classpath). You will also want to ensure you are working with spring-test-4.1.0.RELEASE (or newer).
Next you can use MockMvc as the other answer indicates. However, if you setup MockMvc with the following:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
#WebAppConfiguration
public class OAuthTests {
#Autowired
private WebApplicationContext context;
private MockMvc mvc;
#Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
// ADD THIS!!
.apply(springSecurity())
.build();
}
This makes it so
You no longer need to worry about running in stateless mode or not
It also means you do not need to use apply(springSecurity()) as indicated in the other answer.
In short, you should be able to do something like this:
#Test
#WithSecurityContext('user')
public void performOAuth() throws Exception {
...
// No need for apply(security())!!
restParcelMockMvc.perform(get("/api/some-resource"))
.andExpect(...);
}
I'd encourage you to read through the rest of the Spring Security Testing section of the reference as it provides lots of additional details including how to use custom authentication.
I use spring security 4.x #WithSecurityContext('user') annotation to create mock SecurityContext with 'user' logged in. Then when calling my REST API using MockMvc I retrieve SecurityContext and attach it to the call.
Like this:
#Test
#Transactional
#WithSecurityContext('user')
public void getAllParcels() throws Exception {
// Initialize the database
long size = parcelRepository.count();
parcelRepository.saveAndFlush(parcel);
// Get all the parcels
restParcelMockMvc.perform(get("/api/parcels").with(security()))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[" + size + "].id").value(parcel.getId()))
.andExpect(jsonPath("$.[" + size + "].lot").value(DEFAULT_LOT))
.andExpect(jsonPath("$.[" + size + "].localName").value(DEFAULT_LOCAL_NAME));
}
where security() is static method:
public static RequestPostProcessor security() {
return SecurityMockMvcRequestPostProcessors.securityContext(SecurityContextHolder.getContext());
}
So using #WithSecurityContext('user') mock SecurityContext with authenticated user with login 'user' is created for my test method. Then in that method I retrieve this mock SecurityContext and attach it to the REST API call to make my oAuth think user is allready authenticated. It's basically the first approach you suggested in your question.
For this to work you must switch your OAuth to be statefull for the tests. Otherwise it won't work.
ie like this:
#Configuration
public class OAuth2ServerConfiguration {
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Autowired(required = false)
#Qualifier("oauth2StatelessSecurityContext")
private Boolean stateless = Boolean.TRUE; // STATEFUL switching for tests!
#Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
#Inject
private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.and()
.csrf()
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
.disable()
.headers()
.frameOptions().disable().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/logs/**").hasAnyAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/**").authenticated()
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/protected/**").authenticated();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(stateless);
super.configure(resources);
}
}
...
You see my stateless property which gets injected only in tests. In normal run it uses it's default value true (so it's stateless). For tests I declare oauth2StatelessSecurityContext Bean with value false so it turns statefull for tests.
I define this configuration for tests:
#Configuration
public class OAuth2Statefull {
#Bean
#Primary
public Boolean oauth2StatelessSecurityContext() {
return Boolean.FALSE;
}
}
That's how I did it. I hope my explanation is understandable.

Lookup LDAP entries from inside LdapAuthoritiesPopulator

I'm using Spring Security and spring-security-ldap for authentication in a Spring Boot application.
I want to implement an LdapAuthoritiesPopulator that looks up some entries from the LDAP server to decide the roles of the user. These entries are not under the user, so the userData object provided to getGrantedAuthorities method is not enough. (I have no control over the LDAP server so I cannot add the roles to user entries).
I thought about injecting the ContextSource that Spring Security creates when calling auth.ldapAuthentication().contextSource() (see (1) in the code below) into the LdapAuthoritiesPopulator. But that doesn't work because the LdapAuthoritiesPopulator is created before the WebSecurityConfigurerAdapter, and so the ContextSource does not exist yet.
My WebSecurityConfigurerAdapter contains the following method:
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.contextSource() // (1)
.url("ldap://example.org/o=organization")
.and()
.ldapAuthoritiesPopulator(ldapAuthoritiesPopulator)
.userSearchFilter("uid={0}");
}
ldapAuthoritiesPopulator is autowired, the class is currently a dummy implementation that simply adds ROLE_USER:
#Component
public class CustomLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(
DirContextOperations userData, String username) {
// I want to look up some entries in LDAP here, so I need a ContextSource
// but I can't inject it because it does not exist when this bean is created
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("USER"));
return authorities;
}
}
You don't need the roles populated till after the application is running (I suppose). So one thing that would work would be to inject the ApplicationContext instead of the ContextSource and look up the latter lazily. If that works there might be a truck with #Lazy.

Grails custom security evaluator

I'm working on an app that has to do object level security checks, and the checks will be made by a service because it will need to make REST calls to a separate application. Because of this I'm not able to use Spring Security roles or ACLs because none of this information will be stored locally in the app. I'm trying to find an elegant way to handle this, and here are two options I can think of:
1) Create a custom annotation that will check permissions
2) Extend a Spring security annotation permission check (possibly with Permission Evaluator?) that lets me write the logic for checking access
For #1 I've created a custom annotation and am using filters to read the annotation and check access, although this seems to be more brittle and will only give me protection for controller actions, and it would be nice to also secure other services as well.
I've found bits an pieces of this information, but nothing complete.
THIS talks about customizing ACL but only for a new permission, not controlling the logic
THIS talks about using SpEL, but I'd like to have checks before a method runs, to make sure that no effect takes place that would be unauthorized.
THIS appears to be the closest to what I want to do, but is specific to Spring Security and not Grails - my biggest challenge is converting the information in applicationContext.xml into resources.groovy
Thanks in advance for any suggestions or advice you may have!
You should be able to do this with spring security and grails without much trouble.
I used the following 2 ways in the past for similar tasks. Both require the spring security ACL plugin which provides the #PreAuthorize and #PostAuthorize annotations.
Custom PermissionEvaluator
You can use the hasPermission() methods within security annotations and create a custom PermissionEvaluator. Within code this looks like this:
#PreAuthorize("hasPermission(#myObject, 'update')")
public void updateSomething(myObject) {
..
}
The hasPermission() calls are routed to a PermissionEvaluator by spring security. To write your own implementation you have to implement the PermissionEvaluator interface:
class MyPermissionEvaluator implements PermissionEvaluator {
#Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
// your custom logic..
}
#Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
// your custom logic
}
}
To register your PermissionEvaluator you have to override the bean named expressionHandler. You do this by adding the following lines in conf/spring/resources.groovy:
beans = {
expressionHandler(MyExpressionHandler) {
parameterNameDiscoverer = ref('parameterNameDiscoverer')
permissionEvaluator = ref('myPermissionEvaluator') // your PermissionEvaluator
roleHierarchy = ref('roleHierarchy')
trustResolver = ref('authenticationTrustResolver')
}
myPermissionEvaluator(MyPermissionEvaluator)
}
Within resources.groovy you can define beans like you would do in applicationContext.xml when using spring. The above lines create a bean of type MyPermissionEvaluator with the bean name myPermissionEvaluator. Spring securities expressionHandler bean is overridden with a bean of type MyExpressionHandler. The other dependencies are copied from the configuration file of the spring security ACL plugin.
Service calls in security annotations
If the design of the hasPermission() methods does not achive all you requirements you can use simple service calls instead. The #PostAuthorize and #PreAuthorize annotations use SPEL to evaluate the expression. Within SPEL you can use the # symbol to access beans. For example:
#PreAuthorize("#securityService.canAccess(#myObject)")
public void doSomething(myObject) {
..
}
This calls the canAccess method of the bean named securityService and passes the method argument to it.
To use this approach you have to register a BeanResolver on the EvaluationContext. To do this you have to override the DefaultMethodSecurityExpressionHandler which is configured by the spring security ACL plugin.
This can look like the following:
class MyExpressionHandler extends DefaultMethodSecurityExpressionHandler {
BeanResolver beanResolver
#Override
public EvaluationContext createEvaluationContext(Authentication auth, MethodInvocation mi) {
StandardEvaluationContext ctx = (StandardEvaluationContext) super.createEvaluationContext(auth, mi)
ctx.setBeanResolver(beanResolver) // set BeanResolver here
return ctx;
}
}
BeanResolver is a simple interface that resolves a bean name to a bean instance:
class GrailsBeanResolver implements BeanResolver {
GrailsApplication grailsApplication
#Override
public Object resolve(EvaluationContext evaluationContext, String beanName) throws AccessException {
return grailsApplication.mainContext.getBean(beanName)
}
}
And finally add the beans to resources.groovy:
expressionHandler(MyExpressionHandler) {
parameterNameDiscoverer = ref('parameterNameDiscoverer')
permissionEvaluator = ref('permissionEvaluator')
roleHierarchy = ref('roleHierarchy')
trustResolver = ref('authenticationTrustResolver')
beanResolver = ref('beanResolver') // this is your BeanResolver
}
// This is the service called within security expressions
// If you place your service in the grails service folder you can skip this line
securityService(MySecurityService)
// this is your BeanResolver
beanResolver(GrailsBeanResolver) {
grailsApplication = ref('grailsApplication')
}
Update (2013-10-22): Recently I wrote a blog post about exactly this which provides some additional information.

Resources