I'm working on a spring boot project v2.1.7 and I'm implementing the security with spring security.
In my case I've detected some hierarchies on roles and for this reason my current RoleHierarchy bean is as following
#Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
return roleHierarchy;
}
Now I need a new role called "BACK_OFFICE" that is less than ADMIN but doesn't have any link with the user role.
How can I represents this situation? I've tried with the following string representation but doens't works.
#Bean
public RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER ROLE_ADMIN > ROLE_BACK_OFFICE");
return roleHierarchy;
}
Where is my error?
You can define multiple role hierarchies by using a newline \n to separate them.
roleHierarchy.setHierarchy("ROLE_A > ROLE_B\nROLE_B > ROLE_C");
Related
I am working on a Spring Boot application, which is basically a resource server. As of now, my application has one tenant, which gets authenticated with an authorization server, external to my application.
In order to achieve the same, as of now, I have made the following changes in my application:
config changes are as following:
spring.security.oauth2.client.registration.tenant1.client-id=abcd
spring.security.oauth2.client.registration.tenant1.client-authentication-method=basic
spring.security.oauth2.client.registration.tenant1.authorization-grant-type=authorization_code
myapp.oauth2.path=https://external.authorization.server/services/oauth2/
spring.security.oauth2.client.provider.tenant1.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.tenant1.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.tenant1.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.tenant1.user-name-attribute=name
As of now, I am fetching client secrets from Vault, so I had to define the OAuth2 configuration as follows:
#EnableConfigurationProperties(OAuth2ClientProperties.class)
#Conditional(ClientsConfiguredCondition.class)
#Configuration
public class OAuth2Configuration {
static final String OAUTH2_CLIENT_SECRET_KEY = "oauth2_client_secret";
private static final Logger log = LoggerFactory.getLogger(OAuth2Configuration.class);
private static final String OAUTH2_REGISTRATION_MISSING =
"oAuth2 registration properties are missing";
private final ApplicationSecretProvider applicationSecretProvider;
private final Map<String, ClientAuthenticationMethod> clientAuthenticationMethodMap =
new HashMap<>();
private final String authenticationMethod;
public OAuth2Configuration(
#Value("${spring.security.oauth2.client.registration.tenant1.client-authentication-method}")
final String authenticationMethod,
final ApplicationSecretProvider applicationSecretProvider) {
this.authenticationMethod = authenticationMethod;
this.applicationSecretProvider = applicationSecretProvider;
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.POST.getValue(), ClientAuthenticationMethod.POST);
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.BASIC.getValue(), ClientAuthenticationMethod.BASIC);
this.clientAuthenticationMethodMap
.put(ClientAuthenticationMethod.NONE.getValue(), ClientAuthenticationMethod.NONE);
}
#Bean
public InMemoryClientRegistrationRepository getClientRegistrationRepository(
OAuth2ClientProperties properties) {
List<ClientRegistration> registrations = new ArrayList<>(
OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(properties).values());
//We will have only one client registered for oAuth
if (CollectionUtils.isEmpty(registrations)) {
log.error(OAUTH2_REGISTRATION_MISSING);
throw new IllegalStateException(OAUTH2_REGISTRATION_MISSING);
}
ClientRegistration registration = registrations.get(0);
ClientRegistration.Builder builder = ClientRegistration.withClientRegistration(registration);
ClientAuthenticationMethod clientAuthenticationMethod =
getClientAuthenticationMethod(authenticationMethod);
ClientRegistration completeRegistration = builder
.clientSecret(applicationSecretProvider.getSecretForKey(OAUTH2_CLIENT_SECRET_KEY))
.clientAuthenticationMethod(clientAuthenticationMethod)
.build();
return new InMemoryClientRegistrationRepository(completeRegistration);
}
protected ClientAuthenticationMethod getClientAuthenticationMethod(String grantType) {
ClientAuthenticationMethod retValue = clientAuthenticationMethodMap.get(grantType);
if (retValue == null) {
return ClientAuthenticationMethod.NONE;
}
return retValue;
}
}
Then I extended DefaultOAuth2UserService in order to save user details in my application as follows:
#Component
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private UserRepository userRepository;
private AuthorityRepository authRepository;
#Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Autowired
public void setAuthorityRepository(AuthorityRepository
authorityRepository) {
this.authorityRepository = authorityRepository;
}
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) {
DefaultOAuth2User oAuth2User = (DefaultOAuth2User) super.loadUser(userRequest);
Collection<GrantedAuthority> authorities = new HashSet<>(oAuth2User.getAuthorities());
Map<String, Object> attributes = oAuth2User.getAttributes();
...
return new DefaultOAuth2User(authorities, oAuth2User.getAttributes(), userNameAttributeName);
}
}
Security configuration is as follows:
#EnableWebSecurity
#Import(SecurityProblemSupport.class)
#ConditionalOnProperty(
value = "myapp.authentication.type",
havingValue = "oauth",
matchIfMissing = true
)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomOAuth2UserService customoAuth2UserService;
public SecurityConfiguration(CustomOAuth2UserService customoAuth2UserService) {
this.customoAuth2UserService = customoAuth2UserService;
}
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.authorizeRequests()
.antMatchers("/login**").permitAll()
.antMatchers("/manage/**").permitAll()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.anyRequest().authenticated()
//.and().oauth2ResourceServer().jwt()
.and()
//.and()
.oauth2Login()
.redirectionEndpoint()
.baseUri("/oauth2**")
.and()
.failureUrl("/api/redirectToHome")
.userInfoEndpoint().userService(customoAuth2UserService);
http.cors().disable();
}
}
Now, I would like to onboard multiple tenants using OAuth2 as well. Say I want to onboard another tenant tenant2. In order to achieve this, I think, I need to do the following changes in the existing code base as follows:
adding config entries in the properties file as above:
spring.security.oauth2.client.registration.tenant2.client-id=efgh
spring.security.oauth2.client.registration.tenant2.client-authentication-method=basic
spring.security.oauth2.client.registration.tenant2.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.tenant2.token-uri=${myapp.oauth2.path}token
spring.security.oauth2.client.provider.tenant2.authorization-uri=${myapp.oauth2.path}authorize
spring.security.oauth2.client.provider.tenant2.user-info-uri=${myapp.oauth2.path}userinfo
spring.security.oauth2.client.provider.tenant2.user-name-attribute=name
I need to do changes in the security configuration class:
SecurityConfiguration and OAuth2 configuration class OAuth2Configuration as well. But I am not able to understand what should I add there in order to make my applications work seamlessly for multiple tenants.
In this context, I found this related post: Dynamically register OIDC client with Spring Security OAuth in a multi-tenant stup, but could not get any concrete idea regarding what changes should I do in the existing code base to make my application work in multi-tenancy set up.
Could anyone please help here?
I think there's a bit of confusion that it might help to clear up.
First, it seems that you are not actually building a resource server, as a resource server would require an access token for authentication. Using .oauth2Login() is for either OAuth 2.0 or OpenID Connect 1.0 login, which is a regular application in most respects except how you log in. You still have a browser session after login is successful, which you would not have in a resource server.
Second, configuring a static number of client registrations isn't really quite the same as building a multi-tenant application. Perhaps you're building up to that later, by demonstrating two clients. When configuring two clients using static configuration properties, nothing is really different from a single configuration, other than that there are two possible registrationIds.
Start by building a simple hello world application, such as the OAuth 2.0 Login Sample. If you add a second client registration to your properties, you'll notice that the auto-generated login page (/login) simply shows two links, one for each client. See docs for more on this.
The default URI for initiating the authorization_code flow is /oauth2/authorization/{registrationId}, which means navigating to /oauth2/authorization/abcd launches the first client's login flow. Navigating to /oauth2/authorization/efgh launches the second client's login flow. There's not really anything else needed to support multiple login clients other than understanding how to initiate login.
If you wish to support a fully multi-tenant login configuration, you would then provide a custom ClientRegistrationRepository, which you have done. The only difference is that you should no longer seek to configure clients through the Spring Boot properties, as that seems to be the point that is confusing in your example. If you want to use properties for some of the configuration, create your own configuration properties for your custom repository implementation. Typically at that point, all of this configuration would come from a database.
I would start with that progression (hello world, two statically configured clients, custom ClientRegistrationRepository) then proceed to add other custom components. It will help illustrate the differences at each point.
Playing with Spring Session REST example, I'm curious is it possible to make the following flow:
1. In first time, an user passes its credentials, usename and password.
2. Spring Session will generate a token, puts it into Redis
3. Next time a user asks for some resources it passes its tiken.
My problem is that the following code is hardcoded:
code
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
}
code`
How can I do it to be dynamic?
Thanks in advance.
Spring Session works independent of the security framework you choose, so the answer depends on which security framework you want to use. This means selecting the username / password for your users is totally independent of Spring Session.
Since the question is unrelated to Spring Session, you should consult the documentation of the security framework you choose. In this instance, it is Spring Security so you can consult Spring Security's documentation on authentication.
The first step is to determine how you want to authenticate.
For example, if you want to use JDBC authentication, you can use something like the following.
#Autowired
private DataSource dataSource;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
By default jdbcAuthentication expects that:
select username, password, enabled from users where username = ?
will return the username, the password, and if that user is enabled. You can customize this query if you like using properties on jdbcAuthentication(). See the javadoc for details.
NOTE: It is important to understand that withDefaultSchema and withUser are really only intended for in memory databases since it will try to add the schema and the user every time. In a production environment your users and schema should be added using other mechanisms (i.e. liquibase).
The most flexible option is to implement a UserDetailsService. You can then look up users in any way you want. For example:
#Service
public class UserRepositoryUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
#Autowired
public UserRepositoryUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException {
MyUser user = userRepository.findByEmail(username);
if(user == null) {
throw new UsernameNotFoundException("Could not find user " + username);
}
List<GrantedAuthority> authorities = convert(user.getRoles());
return new User(user.getUsername(), user.getPassword(), authorities);
}
}
Then you can configure it using:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, UserDetailsService userDetailsService) throws Exception {
auth
.userDetailsService(userDetailsService);
}
You can find a complete example of using a UserDetailsService in my Spring Security 4 talk.
The authentication for our application happens through siteminder agent but the authorization is controlled through our application.
I am using org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter to check the header. I have also defined UserDetailsService to load the user details.
I also need to set the role for the user so that I can use spring security tag libraries and other spring methods to check the role and show the options.
How do I implement this?
I have tried the below statements in my user details service implementation, but doesn't seem to work.
Authentication auth = new UsernamePasswordAuthenticationToken(user, null, roles);
SecurityContextHolder.getContext().setAuthentication(auth);
I have also read about AbstractPreAuthenticatedProcessingFilter class but looks like this is may not be useful for this purpose.
Any help on this issue will be very helpful.
Thanks!
I was trying to set the roles(using the statements in my question) in the UserDetailsService
implementation and it was not working.
Solution-1:
I have written a sub class PreAuthenticatedAuthenticationProvider and overridden the authenticate method as below :
public class CustomPreAuthenticatedAuthenticationProvider extends PreAuthenticatedAuthenticationProvider {
#Autowired
DBConfig dbConfig;
#Override
public Authentication authenticate(Authentication authentication)throws AuthenticationException {
Authentication auth = super.authenticate(authentication);
User user = (User)auth.getPrincipal();
Set<Role> roles = new HashSet<Role>();
String[] rolesArray = dbConfig.getRoles(user.getAccessLevel());
for(String role: rolesArray){
Role r = new Role();
r.setName(role);
roles.add(r);
}
user.setRoles(roles);
auth = new UsernamePasswordAuthenticationToken(user, null, roles);
return auth;
}
}
Solution-2 :
I tried setting the roles in the controller (the home page after authentication) and it worked. But looks like Solution-1 is a standard solution.
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.
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.