How to extend OAuth2 principal - java

We are developing an application that uses OAuth 2 for two use cases:
Access to backend microservies (using client_credentials)
Authenticating the application's users (using authorization_code, so redirecting the users to Keycloak for login, roughly configured like shown in the tutorial).
While authenticating our users, we receive part of the information from the auth server (such as login) and the other part can be found in a local user table. What we like to do is to create a Principal object containing also the data from the local database.
PrincipalExtractor seems to be the way to go. Since we have to use manual OAuth configuration to not interfere with OAuth use case 1, we create it and set it:
tokenServices.setPrincipalExtractor(ourPrincipalExtractor);
The implementation basically does a DB lookup and returns a CustomUser object in the mapping function. Now although this seems to work (extractor is called), it is not persisted in the session correctly. So in many of our REST resource we are injecting the current user:
someRequestHandler(#AuthenticationPrincipal CustomUser activeUser) {
and receive null there. Looking into an injected Authentication it shows that it is an OAuth2Authentication object with the default Principal object (I think it is a Spring User / UserDetails). So null because it is not our CustomUser returned before.
Have we misunderstood the way PrincipalExtractor works? Can it be a misconfiguration of our filter chain because we have two different OAuth mechanisms in the same application as mentioned before? A breakpoint in Spring's Principal repository showed us that CustomUser is saved there, followed by a save with the original type which seems to overwrite it.

Ok, to answer my own question:
PrincipalExtractor seems to be the usual and standard way to customize the principal
It doesn't work in our case because we are using a JHipster application that simply overwrites the principal right after the login with it's own User. So all mapping in PrincipalExtractor is reset. If anyone has the same question: Look into UserService.
That's the downside of using generated code you don't know in detail I guess.

I can tell you how I managed to do something similar using JWT. If you aren't using JWT then I'm not sure if this will help.
I had a very similar issue in that my injected principal was only containing the username. Not null like yours, but obviously not what I wanted. What I ended up doing was extending both the TokenEnhancer and JwtAccessTokenConverter.
I use the TokenEnhancer to embed my extended principal of type CustomUserDetailsinside the JWT additional information.
public class CustomAccessTokenEnhancer implements TokenEnhancer {
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Authentication userAuthentication = authentication.getUserAuthentication();
if (userAuthentication != null) {
Object principal = authentication.getUserAuthentication().getPrincipal();
if (principal instanceof CustomUserDetails) {
Map<String, Object> additionalInfo = new HashMap<>();
additionalInfo.put("userDetails", principal);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
}
}
return accessToken;
}
}
And then manually extract the extended principal when building the Authentication object when processing an authenticated request.
public class CustomJwtAccessTokenConverter extends JwtAccessTokenConverter {
#Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication authentication = super.extractAuthentication(map);
Authentication userAuthentication = authentication.getUserAuthentication();
if (userAuthentication != null) {
LinkedHashMap userDetails = (LinkedHashMap) map.get("userDetails");
if (userDetails != null) {
// build your extended principal here
String localUserTableField = (String) userDetails.get("localUserTableField");
CustomUserDetails extendedPrincipal = new CustomUserDetails(localUserTableField);
Collection<? extends GrantedAuthority> authorities = userAuthentication.getAuthorities();
userAuthentication = new UsernamePasswordAuthenticationToken(extendedPrincipal,
userAuthentication.getCredentials(), authorities);
}
}
return new OAuth2Authentication(authentication.getOAuth2Request(), userAuthentication);
}
}
and the AuthorizationServer configuration to tie it all together.
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private DataSource dataSource;
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
CustomJwtAccessTokenConverter accessTokenConverter = new CustomJwtAccessTokenConverter();
accessTokenConverter.setSigningKey("a1b2c3d4e5f6g");
return accessTokenConverter;
}
#Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(tokenStore());
defaultTokenServices.setSupportRefreshToken(true);
return defaultTokenServices;
}
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomAccessTokenEnhancer();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource).passwordEncoder(passwordEncoder());
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints
.tokenStore(tokenStore())
.tokenEnhancer(tokenEnhancerChain)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.passwordEncoder(passwordEncoder());
security.checkTokenAccess("isAuthenticated()");
}
}
I am then able to access my extended principal in my resource controller like this
#RestController
public class SomeResourceController {
#RequestMapping("/some-resource")
public ResponseEntity<?> someResource(Authentication authentication) {
CustomUserDetails userDetails = (CustomUserDetails) authentication.getPrincipal();
return ResponseEntity.ok("woo hoo!");
}
}

Related

Role-based authentication for OAuth2

In addition to bearer token authentication, I am also trying to validate user roles/authorities to ensure that they are permitted to access this resource. Here is what I have so far:
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${oauth.enabled}")
boolean oauthEnabled;
#Value("${spring.security.oauth2.resourceserver.opaque.introspection-uri}")
String introspectionUri;
#Value("${spring.security.oauth2.resourceserver.opaque.introspection-client-id}")
String clientId;
#Value("${spring.security.oauth2.resourceserver.opaque.introspection-client-secret}")
String clientSecret;
#Override
public void configure(final HttpSecurity http) throws Exception {
if (oauthEnabled) {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().cors()
.and()
.authorizeRequests(urlRegistry -> urlRegistry
.antMatchers("/api/health").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(resourceServer -> resourceServer
.opaqueToken(opaqueToken -> opaqueToken
.introspectionUri(this.introspectionUri)
.introspectionClientCredentials(this.clientId, this.clientSecret)
)
);
}
#Slf4j
#RestController
#RequestMapping("/api")
#ControllerAdvice
#CrossOrigin(originPatterns = "*")
public class InventoryCountdownController extends BaseController {
#GetMapping("/icd")
//#PreAuthorize("permitAll()")
#PreAuthorize("hasAuthority('SOME_USER_ROLE')")
public ResponseEntity<List<Countdown>> getIcd(#RequestParam(value = "val") String val) {
...
}
The problem that I am running into is that I am getting back "Unexpected error: Access is denied". When I replace the "hasAuthority" annotation with #PreAuthorize("permitAll()"), it seems to work fine. What am I missing?
As per https://github.com/spring-projects/spring-security/wiki/OAuth-2.0-Migration-Guide, I am using org.springframework.boot:spring-boot-starter-oauth2-resource-server to implement my resource server.
The default behaviour is to populate the authorities based on the "scope" attribute that is typically included in the response from the introspection endpoint.
For example, if the introspection endpoint responds with { …​, "scope" : "messages"} then the authority list will be ["SCOPE_messages"].
You can customise this using a custom OpaqueTokenIntrospector and exposing it as a bean.
#Bean
public OpaqueTokenIntrospector introspector() {
return new CustomAuthoritiesOpaqueTokenIntrospector();
}
where CustomAuthoritiesOpaqueTokenIntrospector will look similar to this
public class CustomAuthoritiesOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
private OpaqueTokenIntrospector delegate =
new NimbusOpaqueTokenIntrospector("https://idp.example.org/introspect", "client", "secret");
public OAuth2AuthenticatedPrincipal introspect(String token) {
OAuth2AuthenticatedPrincipal principal = this.delegate.introspect(token);
return new DefaultOAuth2AuthenticatedPrincipal(
principal.getName(), principal.getAttributes(), extractAuthorities(principal));
}
private Collection<GrantedAuthority> extractAuthorities(OAuth2AuthenticatedPrincipal principal) {
List<String> scopes = principal.getAttribute(OAuth2IntrospectionClaimNames.SCOPE);
return scopes.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}
This is all described in the Spring Security reference documentation.
the client et secret should be the credentials of the front application or the backend( resource server). should I store in the resource server th cient id ans secret of all clients that are calling my rest api

#AuthenticationPrincipal UserModel user is always Null. I tried many solution methods

There is a controller whose task is to return the user profile to me using the REST API. Code further:
#PostMapping("/me")
public UserProfileResponse getUserProfile(#AuthenticationPrincipal UserAuthenticationPrincipalModel user ) {
return userProfileService.getUserProfile(user.getUserId());
}
I created a model for the User entity. The entity class is created as:
public class User implements UserDetails { ... }
The model has the following structure:
public class UserAuthenticationPrincipalModel extends User {
private String userId;
private String avatarUrl;
public UserAuthenticationPrincipalModel(***.********.entity.User user) {
super(user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(),
user.isCredentialsNonExpired(), user.isAccountNonLocked(), user.getAuthorities());
this.userId = user.getUserId();
this.avatarUrl = user.getUserPic();
}
// + equals and hashCode
}
In the model, the data that I will ever (or so far plan so) to pull from the AuthPrincipal an authorized user. According to the statement of work, I can’t use the default Principal, I haven’t even tried it. Implementation of UserDetailsService:
#Service
public class UserDetailsServiceImpl extends AbstractMySQLService<User, String, UserRepository> implements UserDetailsService {
private final UserRepository userRepository;
public UserDetailsServiceImpl(final UserRepository userRepository, final UserRepository repository) {
super(repository);
this.userRepository = userRepository;
}
#Override
public UserAuthenticationPrincipalModel loadUserByUsername(final String email) {
User user = userRepository.findByEmail(email);
if (user == null) {
throw new UsernameNotFoundException("Invalid username or user not e: " + email);
}
return new UserAuthenticationPrincipalModel(user);
}
}
Error: Null always flies into methods. Made a lot of additions, which are recommended on the Baeldang and this stack - nothing :(
Please, write a comment, if I should add some more information.
UPD 1:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/ca/**").hasRole("COMPANY_ADMIN")
.antMatchers("/d/**").hasRole("DRIVER")
.antMatchers("/u/**").authenticated()
.antMatchers("/sign_up", "/oauth/token", "/swagger-ui.html", "/resources").permitAll();
}
I can give you a few pointers of how to approach this issue.
Ensure you are using org.springframework.security.core.annotation.AuthenticationPrincipal instead of #org.springframework.security.web.bind.annotation.AuthenticationPrincipal (Both should work but just pre-caution because the later is deprecated)
Now the issue is to isolate the problem to ONE of the following areas so you can concentrate there:
Your UserDetailsServiceImpl is not used
Something wrong with getUserProfile method with #AuthenticationPrincipal
user is not associated with a logged in session.
To identify that, replace your public UserProfileResponse getUserProfile method with the following:
[Do not change anything else]
#Autowired
private UserDetailsService userDetailsService;
#PostMapping("/me")
public void getUserProfile(#AuthenticationPrincipal UserDetails user ) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println("principal : " + authentication.getPrincipal());
System.out.println("Implementing class of UserDetails: " + authentication.getPrincipal().getClass());
System.out.println("Implementing class of UserDetailsService: " + userDetailsService.getClass());
}
Check the logs and it will tell you where the problem is and if you can't figure out from it, you can post the outcome here for more help
Update: Answers for point 4 given as below in comments.
principal : anonymousUser
Implementing class of UserDetails : class java.lang.String
Implementing class of UserDetailsService : class
Conclusion : endpoint is not protected and user accessing without logging in
Solution : Protect the endpoint by replacing .antMatchers("/u/**").authenticated() with .antMatchers("/api/u/**").authenticated()

Spring Security - Verify user in DB first and then authenticate against AD

We have a requirement to verify whether a username exists in database and then authenticate against AD. If username doesn’t exist application will return error instead of trying to authenticate against AD. I have authenticated against multiple AD’s and/or database but I have trouble getting this to work. Any hints would be helpful. Thank you
In my class that extends WebSecurityConfigurerAdapter I tried to play with authenticationProvider where I could verify the existence in DB. But not sure of what to return so that the authentication could be proceed to LDAP.
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(authenticationProvider)
.authenticationEventPublisher(authenticationEventPublisher)
.ldapAuthentication()
.....;
}
I also tried adding a before/after filter but not successful in there either
#Override
protected void configure(HttpSecurity http) throws Exception {
http
....
.and()
.addFilterBefore(preAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
;
In the filter preAuthenticationFilter the instance of request passed in doFilter() method FirewalledRequest. From this instance I am unable to get the username; looks like this is by design. If anyone has any advice on how we could retrieve username from the instance of FirewalledRequest please share it here. I will give it a try.
So instead of using the filter I decided to play with the custom AuthenticationProvider. In the AuthenticationProvider implementation under the method authenticate() I return null (and log, notify, etc.) when user exist. If user doesn’t exits I return the same instance of authentication passed. This breaks the chain and stops proceeding to authenticating against AD. Throwing any instance of AuthenticationException doesn’t work as Spring security captures this exception and proceeds further (per docs).
Here is how the code snippet looks like
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
Optional<User> user = service.findUserByUsername((String) authentication.getPrincipal());
if (user.isPresent())
return null;
return authentication;
}
Please share any better ideas.
#gandr solution got me thinking, and my final solution was:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(PreLdapAuthenticationProvider)
// authenticationEventPublisher not used anymore
.ldapAuthentication()
.....;
private static class PreLdapAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
this.userService.checkUserEnabled((String) authentication.getPrincipal());
return null;
}
}
public class UserService {
public void checkUserEnabled(String username) throws AuthenticationException {
UserEntity entity = userRepository.findByLogin(username);
if (entity == null) {
throw new PreLdapUsernameNotFoundException();
// normal UsernameNotFoundException extends AuthenticationException
// and will be caught and ignore, but not if my custom class
// extends AccountStatusException
}
if (!entity.isEnabled()) {
throw new DisabledException("DisabledException");
}
}
}
public class PreLdapUsernameNotFoundException extends AccountStatusException {
public PreLdapUsernameNotFoundException() {
super("PreLdapUsernameNotFoundException");
}
}
Then you can catch PreLdapUsernameNotFoundException and InternalAuthenticationServiceException in
private AuthenticationFailureHandler authenticationFailureHandler() {
return (request, response, authenticationException) -> {
if (authenticationException instanceof PreLdapUsernameNotFoundException){
...
} else ...
}
}

How to correctly retrieve the logged user information from a Spring MVC\Boot Controller method?

I am pretty new in Spring Security and I have the following problem.
I am working on a Spring Boot prject using Spring Security to protect all the resources into /Extranet/".
So basically my WebSecurityConfig configuration class contains this code:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
#EnableAutoConfiguration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/Extranet/**").access("hasRole('ROLE_USER')")
.anyRequest().permitAll()
.and()
.httpBasic()
.and()
.csrf().disable();
}
}
It works fine, infact to access to a resource as /Extranet/login I have to set the Basic Authentication and specify the correct username and password (performing the request using Postman tool to test it).
Ok, this works fine.
In my Spring Security configuration is involed this CustomUserDetails class that implements the Spring Security interface UserDetails.
public class CustomUserDetails extends User implements UserDetails {
private static final long serialVersionUID = 1L;
public CustomUserDetails(User user){
super(user);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for(UserRole role : this.getUserRoles() ){
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getName());
authorities.add(grantedAuthority);
}
return authorities;
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
#Override
public String getUsername() {
return super.getUsername();
}
}
An instance of this object contains the user details of the user currently logged.
Ok my doubt is: how can I retrieve this object from a controller method? (I think that is should be into the context and that I can retrieve it in some way).
I have tryied to do in this way:
#RestController
#RequestMapping("/Extranet")
public class AccessController {
#RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity<String> login(CustomUserDetails userInfo) {
System.out.println("login() START");
return ResponseEntity.ok("LOGGED IN");
}
}
but in this way I obtain an exception like this:
[ERROR] 2017-01-23 14:18:04 [com.betrivius.controller.exceptionHandler.ControllerExceptionHandler.handleException(ControllerExceptionHandler.java:106)] [http-nio-8080-exec-1] ControllerExceptionHandler - Request: http://localhost:8080/Extranet/login throws:
org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.betrivius.security.bean.CustomUserDetails]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.betrivius.security.bean.CustomUserDetails.<init>()
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:105) ~[spring-beans-4.3.3.RELEASE.jar:4.3.3.RELEASE]
It seems that it can't instantialete the CustomUserDetails object. Why?
From what I know I can retrieve the CustomUserDetails object related to the logged user. How can I do it?
I also try to do in this way:
#RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity<String> login(Principal principal) {
System.out.println("login() START");
return ResponseEntity.ok("LOGGED IN");
}
In this way the principal parameter is correctly instantiated and contains an instance of the previous CustomUserDetails object containing the information of the logged user.
So is it the correct way to access to the current logged user information?
Another doubt is: Why I can pass this Principal principal parameter to my login() method? What exactly happen under the hood? Who is that effectively pass it to the login() method?
I think that should happen something like this:
1) There is a POST HttpRequest toward the"/Extranet/login" resource. The dispatcher servlet send it to the login() method.
2) The Principal principal was put into the Spring Context after that the user was enabled for this resource (before that the controller method was called) so the Spring factory can retrieve it from the context and pass it to the login() method.
But I am absolutly not sure about it. How exactly works? What am I missing?
You probably need then #AuthenticationPrincipal annotation:
#RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity<String> login(#AuthenticationPrincipal CustomUserDetails userInfo) {
System.out.println("login() START");
return ResponseEntity.ok("LOGGED IN");
}
If that still doesn't solve the problem, try debugging using the old method:
#RequestMapping(value = "/login", method = RequestMethod.POST)
public ResponseEntity<String> login() {
CustomUserDetails userInfo = (CustomerUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
...
}

Spring Security LDAP authentication user must be a member of an AD group

I've configured the Spring Boot Security as per:
https://spring.io/guides/gs/securing-web/
I am able to login using my credentials perfectly. However, I need to add a checking that the AD user must also belong to a specific AD group (ie. AD-this-is-a-specific-group). On login, if the user does not belong to the specific AD group, then it should return a login error.
I've been searching for hours now and cannot seem to find a clear way to do this in the WebSecurityConfigurerAdapter , am I using the auth.groupSearchFilter correctly?
Here is my code:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
Environment env;
public LdapContextSource contextSource () {
LdapContextSource contextSource= new LdapContextSource();
contextSource.setUrl(env.getRequiredProperty("ldap.url"));
contextSource.setBase(env.getRequiredProperty("ldap.baseDn"));
contextSource.setUserDn(env.getRequiredProperty("ldap.bindDn"));
contextSource.setPassword(env.getRequiredProperty("ldap.batchPassword"));
contextSource.afterPropertiesSet();
return contextSource;
}
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.ldapAuthentication()
.userSearchFilter("(cn={0})")
.groupSearchBase("OU=Account Groups,OU=ITS Security")
.groupSearchFilter("(cn=AD-this-is-a-specific-group)")
.contextSource(contextSource());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().fullyAuthenticated()
.and()
.formLogin();
}
I am sorry for beeing 5 years late for the party but I had the exact same problem with my very simple LDAP authentication implemented in Spring Boot.
I only wanted this:
- Is it the correct username?
- Is it the correct password?
- If yes, is the usr in group MYGROUP?
So my configure method now looks really small. I added the populator in a separate bean, just realize that I needed to add it in "auth.ldapAuthentication" so it would be called.
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.ldapAuthentication()
.userSearchFilter("uid={0}")
.ldapAuthoritiesPopulator(ldapAuthoritiesPopulator())
.groupSearchFilter("(member={0})")
.contextSource(contextSource());
}
#Bean
public LdapAuthoritiesPopulator ldapAuthoritiesPopulator() {
DefaultLdapAuthoritiesPopulator populi = new DefaultLdapAuthoritiesPopulator(contextSource(), "") {
#Override
public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
Set<GrantedAuthority> groupMembershipRoles = super.getGroupMembershipRoles(userDn, username);
boolean isMemberOfSpecificAdGroup = false;
for (GrantedAuthority grantedAuthority : groupMembershipRoles) {
if ("ROLE_MYGROUP".equals(grantedAuthority.toString())) {
isMemberOfSpecificAdGroup = true;
break;
}
}
if (!isMemberOfSpecificAdGroup) {
throw new BadCredentialsException("User must be a member of " + "ROLE_MYGROUP");
}
return groupMembershipRoles;
}
};
return populi;
}
#Bean
public DefaultSpringSecurityContextSource contextSource() {
return new DefaultSpringSecurityContextSource("ldap://blabla-some-url:389/dc=something,dc=something,dc=ch");
}
And by the way: The url did not work like mentioned in the Spring Boot guide it only worked like this, like everything in one line:
return new DefaultSpringSecurityContextSource("ldap://blabla-some-url:389/dc=something,dc=something,dc=ch");
And by the way for everyone following that guide: If you connect to an already existing LDAP server you don't need all those "spring.ldap.embedded" application properties.
So thank you alot for your help!
Not sure if this is the best way to do this (in terms of Spring Security's lifecycle), but basically I provided my own DefaultLdapAuthoritiesPopulator, where I only override the getGroupMembershipRoles.
First thing though, I have wrong auth.groupSearchFilter above, it should be:
.groupSearchFilter("(member={0})")
Second, I've created an anonymous class with overridden method (that calls the super and checks for a the membership in the list of roles):
auth
.ldapAuthentication()
.ldapAuthoritiesPopulator(new DefaultLdapAuthoritiesPopulator(contextSource, "OU=Account Groups,OU=ITS Security") {
#Override
public Set<GrantedAuthority> getGroupMembershipRoles(String userDn, String username) {
Set<GrantedAuthority> groupMembershipRoles = super.getGroupMembershipRoles(userDn, username);
boolean isMemberOfSpecificAdGroup = false;
for (GrantedAuthority grantedAuthority : groupMembershipRoles) {
if ("ROLE_AD-this-is-a-specific-group".equals(grantedAuthority.toString())) {
isMemberOfSpecificAdGroup = true;
break;
}
}
if (!isMemberOfSpecificAdGroup ) {
throw new BadCredentialsException("User must be a member of " + "AD-this-is-a-specific-group");
}
return groupMembershipRoles;
}
})
.userSearchFilter("(cn={0})")
.groupSearchBase("OU=Account Groups,OU=ITS Security")
.groupSearchFilter("(member={0})")
.contextSource(contextSource);
I'll put this here since I think it's the easier way without overriding any method.
In user search filter (i'll use yours) add the following if it corresponds to your LDAP structure
Original:
.userSearchFilter("(cn={0})")
Modified to search roles:
.userSearchFilter("(&(cn={0})(memberOf=CN=MYGROUP,OU=GROUP,DC=com,DC=company)")
This searches both the user and the membership
In my case I had to do this because I have 3 possible roles:
(&(cn={0})(|(group1)(group2)(group3)))
As you can see it searches user AND 1 OR more roles
Credit to this question's answer: Spring Security Ldap, log in only users in specified group

Categories