How to Map AD Groups to User Role Spring Security LDAP - java

I have a web application built using Java Spring MVC.
I'm just setting up spring security connecting to an LDAP server for authentication.
I've successfully set it up so that I am able to login to my application but I can't find anything to help me in mapping an AD group to a user role within Java as I can only get a 403 forbidden page i.e. I've been authenticated but don't have permissions yet.
I currently have:
<http auto-config="true">
<intercept-url pattern="/**" access="ROLE_USER" />
</http>
<ldap-server id="ldapServer" url="LDAPURL" manager-dn="USER" manager-password="PASSWORD" />
<authentication-manager >
<ldap-authentication-provider
group-search-base="OU=GROUPS"
group-search-filter="sAMAccountName={0}"
user-search-base="OU=USERS"
user-search-filter="sAMAccountName={0}"
/>
</authentication-manager>
Say that user was a part of the AD group g-group-UK-user I then want to be able to map that AD group to ROLE_USER so that user can then see the whole web app.
I can only seem to find very simple examples where the groups are either ADMIN or USER in which case the prefix ROLE is just added to the group or the other method seems to be using UserDetailContextMapper but I can't find a clear use of this.

To do this I used the following within authentication manager:
user-context-mapper-ref="customUserContextMapper"
I then used the following class to check if that user belongs to a certain AD group and then assign the ROLE_USER role to their authorities:
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities)
{
Attributes attributes = ctx.getAttributes();
Object[] groups = new Object[100];
groups = ctx.getObjectAttributes("memberOf");
LOGGER.debug("Attributes: {}", attributes);
Set<GrantedAuthority> authority = new HashSet<GrantedAuthority>();
for(Object group: groups)
{
if (group.toString().toLowerCase().contains("AD_GROUP_NAME".toLowerCase()) == true)
{
authority.add(new SimpleGrantedAuthority("ROLE_USER"));
break;
}
}
User userDetails = new User(username, "", false, false, false, false, authority);
return userDetails;
}
Please note that the class is a little more complicated than usual because of the LDAP server I was connecting which has a different structure than usual in that the groups a user has access to are stored in an attribute under the user and not the other way round in which a group would have as an attribute all the users that belong to it.

Related

spring : how to apply role to existing JWT token

In SingleSignOn(SSO), i am getting jwt beareer token from my office network when application loads, basically i have written code in the rest web-service to get bearer token from different web-service.
What i want is:
I want to secure some of the rest endpoints in spring? How can i do that!
I don't want to provide login & password forms to re-login. Because already i am calling web-service to get the user-Info and bearer token. It means i am already login, and i am able to print user info.
I have user role table with userId, i just want to apply thoese roles available in the table to logged in User Id.
How can we do thia?
I found this but i am expecting this should not ask to re enter username and password again https://www.ekiras.com/2016/04/authenticate-user-with-custom-user-details-service-in-spring-security.html
https://stackoverflow.com/a/11314388/1684778
similar kind of my problem
https://stackoverflow.com/a/12057327/1684778
There are multiple things to consider here
User authentication: When the user credentials are verified, we mark the user authenticated and register it in SecurityContext
You must be using AuthenticationProvider to authenticate the token received.
Ex:
public class SSOAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication) {
// verified the authentication token
authentication.setAuthenticated(true);
// make the database call, get roles for the user
authentication.setAuthorities(<authorities - discussed below>);
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
}
Authorities: Once the user is authenticated, we need to get the granted authorities for the user and set them. For that, we need a custom class to represent the granted authorities.
public class CustomAuthority implements GrantedAuthority {
private String role;
public CustomAuthority(String role) {
this.role = role;
}
#Override
public String getAuthority() {
return role;
}
}
We create these custom authority instances from the roles we receive from the database. So in previous SSOAuthenticationProvider, we do the following
// make the database call, get roles for the user
List<String> roles = <db call to get roles>
authentication.setAuthorities(Collections.unmodifiableCollection(roles.stream().map(CustomAuthority::new).collect(Collectors.toList()));
SecurityContextHolder.getContext().setAuthentication(authentication);
This results in currently authenticated user holding all the roles they are entitled to.
The only pending step is to hardcode the authority/role for each endpoint. Now, when the user authentication process is done and an endpoint verification is performed, Spring looks at the authorities needed for the current endpoint and looks for them in the currently authenticated user. If it's present, the endpoint code gets executed. If not, AccessDeniedException is thrown

Spring security 4 #PreAuthorize(hasAuthority()) access denied

I am trying to convert a Spring Security 3 #Secured("admin") annotation into Spring Security 4 compatible fashion.
This is my usersService.java
#PreAuthorize("hasAuthority('admin')")
public List<User> getAllUsers() {
return usersDao.getAllUsers();
}
Then in security-context.xml I have:
<security:intercept-url pattern="/admin" access="permitAll" />
...
<security:global-method-security pre-post-annotations="enabled" />
getAllUsers() is called by a LoginController.java
#RequestMapping("/admin")
public String showAdmin(Model model) {
List<User> users = usersService.getAllUsers();
model.addAttribute("users", users);
return "admin";
}
In mySql database, there are two tables, users and authorities. authorities has 2 columns, username and authority. administrator has authority admin.
Now if I trie to access /admin, I will be redirected to /login, but after I log in with administrator, I still get "access denied".
I think I must have missed something very basic but as I am new to Spring, I could not figure it out. Any help would be appreciated. Thanks.
Update: I tried changing the annotation to #PreAuthorize("hasRole('ROLE_ADMIN')") and I also changed the "authority" column in mySql for admin from "admin" to "ROLE_ADMIN" but it still gives me 403. I did not have much faith on this because before this error, I had to change hasRole('admin') in securityContext.xml to hasAuthority('admin').
Although it's late, nevertheless
hasRole(...) set a prefix for the the content - the default one is ROLE_
hasAuthority(...) checks the content WITHOUT a prefix, i.e. just the pure content
You should add in Spring security
#EnableGlobalMethodSecurity(prePostEnabled = true)
Try this #PreAuthorize("hasRole('ROLE_ADMIN')")

SwitchUserFilter not working in Spring security when used with Basic Authentication

I am having a problem with SwitchUserFilter in Spring security. I have following configuration:
<bean id="ldapUserSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg name="searchBase" value=""/>
<constructor-arg name="searchFilter" value="(uid={0})"/>
<constructor-arg name="contextSource" ref="ldapContext"/>
</bean>
<security:ldap-server id="ldapContext" url="ldap://xxxxxxx"/>
<bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<constructor-arg name="authenticator">
<bean
class="org.springframework.security.ldap.authentication.BindAuthenticator">
<constructor-arg ref="ldapContext" />
<property name="userSearch" ref="ldapUserSearch" />
</bean>
</constructor-arg>
<constructor-arg name="authoritiesPopulator" ref="dbLDAPAuthPopulator" />
</bean>
<security:authentication-manager>
<security:authentication-provider ref="ldapAuthProvider"/>
</security:authentication-manager>
And the corresponding SwitchUserFilter bean is created as:
SwitchUserFilter switchUserFilter = new SwitchUserFilter();
switchUserFilter.setUserDetailsService(ldapUserDetailsService);
switchUserFilter.setTargetUrl("/");
switchUserFilter.setSwitchUserUrl("/impersonate");
switchUserFilter.setUsernameParameter("username");
switchUserFilter.setExitUserUrl("/unimpersonate");
When I go to the url "/impersonate" the user gets impersonated properly. However when the redirect is send to the target url i.e. "/" the user is again authenticated using basic auth.
I had a look at the code of both the SwitchUserFilter and BasicAuthenticationFilter and seems that SU will not work with basic auth.
This is what happens:
When the /impersonate?username=xyz url is called it goes to SwitchUserFilter which gets the details of xyz user from the ldap and it then sets the securitycontext in the session. Code snippet is as follows:
if (requiresSwitchUser(request)) {
// if set, attempt switch and store original
try {
Authentication targetUser = attemptSwitchUser(request);
// update the current context to the new target user
SecurityContextHolder.getContext().setAuthentication(targetUser);
// redirect to target url
successHandler.onAuthenticationSuccess(request, response, targetUser);
} catch (AuthenticationException e) {
logger.debug("Switch User failed", e);
failureHandler.onAuthenticationFailure(request, response, e);
}
return;
So in the SecurityContext you have information about xyz user.
Then when it redirects to target url i.e. "/" basicAuthenticationFilter is called which checks whether the user is authenticated. Code snippet:
Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();
if(existingAuth == null || !existingAuth.isAuthenticated()) {
return true;
}
// Limit username comparison to providers which use usernames (ie UsernamePasswordAuthenticationToken)
// (see SEC-348)
if (existingAuth instanceof UsernamePasswordAuthenticationToken && !existingAuth.getName().equals(username)) {
return true;
}
// Handle unusual condition where an AnonymousAuthenticationToken is already present
// This shouldn't happen very often, as BasicProcessingFitler is meant to be earlier in the filter
// chain than AnonymousAuthenticationFilter. Nevertheless, presence of both an AnonymousAuthenticationToken
// together with a BASIC authentication request header should indicate reauthentication using the
// BASIC protocol is desirable. This behaviour is also consistent with that provided by form and digest,
// both of which force re-authentication if the respective header is detected (and in doing so replace
// any existing AnonymousAuthenticationToken). See SEC-610.
if (existingAuth instanceof AnonymousAuthenticationToken) {
return true;
}
return false;
As you can see it checks existingAuth.getName().equals(username)) which in this case it is xyz. However logged in user is different so the filter again authenticates the user and all the work done by SwitchUserFilter is overridden.
Is their any way to solve this issue? Can I override the BasicAuthenticationFilter?
This question is quite old, however should anyone come across it the answer is still valid today. You don't show your <http /> stanzas for Spring Security but you need to ensure that the role granted by impersonation is the same role (authority) required to bypass authentication at /*. If it's not then yes, you will be asked to authenticate.
You can specify custom authorities to be granted on impersonation by implementing an extension of SwitchUserAuthorityChanger and passing a reference of it to SwitchUserFilter.

how to use Spring security with Custom java class/ custom spring framework's class?

My requirement is as below:
In our application the user's credentials are validated against the database(not using spring security since it is a legacy application) for the first time. If the user is a valid user, he will be logged into the application. Once the user logs into the application he can make few rest calls. Now, I want to once again validate the user's credentials by using spring security before making any rest call. Here, the challenge is we should not redesign the database schemas. We need to use a stored procedure which validates the user. This particular stored procedure returns an error message if authentication fails, otherwise nothing is returned. There are no roles defined in this case. Just simple authentication using a stored procedure. Now, I want to accomplish this whole thing by spring security. May be writing a java class/ custom spring framework's class and in which the stored procedure is called and using that class in spring security configuration files. Can anybody suggest ideas on how to start up with please?
I have implemented AuthenticationProvider. The following is the *security.xml.
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/rest/*" access="permitAll"></intercept-url>
</http>
<authentication-manager >
<authentication-provider ref="csAuthenticationProvider" />
</authentication-manager>
But, the security framework is looking for roles. In my case there are no roles defined. As I said earlier, the user is authenticated for the first time without using spring framework. If the user wants to make any rest call, the spring security needs to re authenticate the user. It doesn't mean that the user needs to re enter credentials. The user's credentials are available in the rest call/request since he is already authenticated. The only thing needs to be done is I need to use the credentials by using request and re validate using the stored procedure. of course, using AuthenticationProvider may be a good idea, but the parameter "Authentication authentication" of the authenticate(Authentication authentication) method is not useful for me since I need to call my own stored procedure call again. for time being, I did not used the Authentication object, but instead used the stored procedure calling code in the authenticate() method. But, strangely, authenticate() method is not getting called. I am surprised and in confusion. Does any body has any ideas on where I am doing wrong?
Sounds like you need to implement an Authentication Provider. Here's a pretty simple example that I think you could adapt to call your stored procedure.
http://danielkaes.wordpress.com/2013/02/20/custom-authentication-provider-in-spring/
You can implement your own UserDetailsService and configure spring to use it.
<security:authentication-manager>
<security:authentication-provider user-service-ref="userDetailsServiceImpl"/>
</security:authentication-manager>
You need to create a custom UserDetailsService implementation, that will check against the DB.
Here is an example UserDetailsService implementation that does just that:
#Service("userService")
public class UserDetailsServiceImpl implements UserDetailsService, InitializingBean {
#Autowired
private AccountService accountService;
public void afterPropertiesSet() throws Exception {
}
#Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
username = username.toLowerCase();
try {
Account account = accountService.loadUserAccountByEmail(username);
if (account == null) {
throw new UsernameNotFoundException("Could not find email: " + username + "in the DB.");
}
List<GrantedAuthority> auths = new ArrayList<GrantedAuthority>();
for (Role r : account.getRoles()) {
auths.add(new SimpleGrantedAuthority(r.getRole()));
}
ApplicationUser user = null;
try {
user = new ApplicationUser(new Long(account.getId()), username, account.getPassword(), true, true, true, true, auths);
} catch (Exception ex) {
ex.printStackTrace();
}
return user;
} catch (Exception e) {
e.printStackTrace();
throw new UsernameNotFoundException(username + "not found", e);
}
}
}
Which I config in code like so:
#Override
protected void registerAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsServiceImpl)
.passwordEncoder(bCryptPasswordEncoder());
}
(you can also see a blog post I wrote about switching from xml to #annotation config for spring security referncing that project here: http://automateddeveloper.blogspot.co.uk/2014/02/spring-4-xml-to-annotation-configuration.html)

Not able to access Spring Security Logged in User info

I am using Spring Security for authentication in my web-application.
Now, i need access to the User Information, for which the following method exists in the Controller:
#RequestMapping(value = "/userstuff")
#Controller
public class SomeUserController {
#RequestMapping(value = "getUser", method = RequestMethod.GET)
#ResponseBody
public UserDetails getUser(Locale locale, Model model) {
UserDetails userDetails = null;
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null) {
Object principal = authentication.getPrincipal();
userDetails = (UserDetails) (principal instanceof UserDetails ? principal
: null);
}
return userDetails;
}
}
IF the controller url is put out of security check like this in applicationContext-Security.xml:
<security:http pattern="/userstuff/**" security="none" />
Then invoking
http:// host:port /app/userstuff/getUser - returns null.
BUT, if do commented it out (allowing spring security to intercept it):
<!-- <security:http pattern="/userstuff/**" security="none" /> -->
Then invoking:
http:// host:port /app/userstuff/getUser returns logged in user correctly.
Why so?
When you use security="none" all spring security filters will be switched off (see this entry from the official documentation). So no SecurityContextHolder, no #PreAuthorize, no other Spring Security features are available. For example normally SecurityContextHolder is populated by one filter from security filter chain.
As a workaround almost always you can replace security="none" construction by restriction for anonymous user only:
<security:http ... />
...
<security:intercept-url pattern="/userstuff/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>
....
</security:http>

Categories

Resources