Spring Security Acquire Roles from a Web Service - java

Looking to use Spring MVC + Spring Security with annotations. Spring Security has great features for obtaining role information from an XML file or a database. However, our role information is accessible from a custom-built SOAP web service. Any ideas how I can get the role information from the Web Service?
Ideally, I'd like to override hasRoles(), etc and modify it so it would then call the SOAP WS and return back the user's roles. How would I do that?
Or any other ideas?

If you need custom attributes while using spring-security you have to implement your own UserDetailsService interface which contains the following method:
public UserDetails loadUserByUsername(final String email)
So after retrieving user from datasource add calls to your roles web-service... Something like that:
public class UserDetailsExtendedService implements UserDetailsService {
#Autowired
private UsersDAO usersDao;
private UserDetails prepare(com.data.User user) {
boolean enabled = user.getState().equals(UserState.Active);
Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
for (UserRole r: /*CALL TO WEB-SERVICE*/) {
authorities.add(new GrantedAuthorityImpl(r.getName()));
}
return new UserDetails(user.getId(), user.getEmail(), user.getPassword(), user.getNickname(), user.getPosition(), user.getAvatar(), user.getCommunicationLanguage().getCode(),
user.getNotificationChannel(), user.getPartnerId(), enabled, enabled, enabled, enabled, authorities);
}
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(final String email)
throws UsernameNotFoundException, DataAccessException, HibernateException {
com.data.User user = usersDao.getByEmail(email);
if (user == null)
throw new UsernameNotFoundException(email);
return prepare(user);
}
}

Related

Duplicate grantedAuthorities are removed in spring security

I am writing a user management system with spring boot, angularjs, jpa, ... all the user's features will be assigned to grantedauthorities and will send back to angularjs to design the home page accordingly but even though I am assigning the authorities to ArrayList and not HashSet, still duplicate features are removed.
Size of grantedauthorities is 12 at the end of loop and everything is fine but when it return the response, duplicates are removed.
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserJpaRepository userJpaRepository;
#Autowired
private RoleFeaturesJpaRepository roleFeaturesJpaRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userJpaRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(
"Opps! user not found with user-name: " + username);
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(),
getAuthorities(user)
);
}
private Collection<GrantedAuthority> getAuthorities(User user) {
ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
Role role = user.getRoles();
for (Features features : role.getFeatures()){
RoleFeaturesPK roleFeaturesPK = new RoleFeaturesPK();
roleFeaturesPK.setRoleId(role.getId());
roleFeaturesPK.setFeatureId(features.getId());
Optional<RoleFeatures> roleFeatures = roleFeaturesJpaRepository.findById(roleFeaturesPK);
RoleFeatures features_entity = roleFeatures.get();
grantedAuthorities.add(new SimpleGrantedAuthority(features.getName()));
grantedAuthorities.add(new SimpleGrantedAuthority(features_entity.getReadOption()));
grantedAuthorities.add(new SimpleGrantedAuthority(features_entity.getReadWriteOption()));
}
return grantedAuthorities;
}
}
Duplicated GrantedAuthorities are removed by Spring when creating the User with this instruction from the passed authorities collection in the constructor :
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
sortAuthorities will sort the authorities based on this comparator and the result will not contain duplications :
private static class AuthorityComparator implements Comparator<GrantedAuthority>,Serializable {
private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
public int compare(GrantedAuthority g1, GrantedAuthority g2) {
// Neither should ever be null as each entry is checked before adding it to
// the set.
// If the authority is null, it is a custom authority and should precede
// others.
if (g2.getAuthority() == null) {
return -1;
}
if (g1.getAuthority() == null) {
return 1;
}
return g1.getAuthority().compareTo(g2.getAuthority());
}
}
method getAuthorities seems ok, it is getting removed on return line maybe
private Collection<GrantedAuthority> getAuthorities(User user)
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(),
getAuthorities(user)
);

Getting the current logged in user name when using Spring Security

Its a coupon system app using Spring security, spring MVC,
now.
when the app starts, I need to somehow initialize the current logged in user into the controller.
Issue is:
If I try to get the current user via SecurityContextHolder it is impossible because it seems like spring is initializing the controllers before the security so I cannot get it in the controller.
Is there anything I'm missing? a different approach of getting the current logged in user after he logs in?
What you need is called #AuthenticationPrincipal.
You can inject it in controller method like this:
#GetMapping("/")
public void get(#AuthinticationPrincipal User user){ ... }
Here is documentation
Alternatively, you can create your own annotation and custom argument resolver, and inject whatever you want.
Solution 1: Principal principal
#RequestMapping(value = {"/", ""})
public String start(Principal principal, Model model) {
String currentUser = principal.getName();
return currentUser;
}
Solution 2: Authentication authentication
#RequestMapping(value = {"/", ""})
public String currentUserName(Authentication authentication) {
return authentication.getName();
}
Solution 3: SecurityContextHolder
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
More Here

Custom authorities with LDAP authentication

I’ve found few Spring XML Configuration examples for logging in with LDAP and configuring the authorities of the logged in user with the help of a custom method and not through LDAP.
Unfortunately, I could not find any Spring Boot example with annotations.
In our case, there is a central LDAP repository in which the usernames and passwords of the users are stored, but the groups of the users are not stored there.
I appreciate any example or reference.
Thank you in advance.
Below you can find our final implementation at the project.
Basic flow is:
a)Check the user id and roles during the authentication. If user is not defined or does not have the roles for the application, do not authenticate the user.
b)if user pass the database check, continue with ldap authentication.
c)Merge the roles coming from database with ldap to be used during the application.
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter implements InitializingBean {
...
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(this.ldapAndDatabaseAuthenticationProvider());
}
#Bean(name="ldapAuthenticationProvider")
public AuthenticationProvider ldapAndDatabaseAuthenticationProvider(){
LdapUserDetailsMapper userDetailsMapper = new LdapUserDetailsMapper();
userDetailsMapper.setRoleAttributes(new String[]{"groupMembership"});
LdapAndDatabaseAuthenticationProvider provider =
new LdapAndDatabaseAuthenticationProvider(this.ldapAuthenticator(), this.ldapAuthoritiesPopulator());
provider.setUserDetailsContextMapper(userDetailsMapper);
return provider;
}
#Bean( name = "ldapAuthoritiesPopulator" )
public LdapAndDatabaseAuthoritiesPopulator ldapAuthoritiesPopulator(){
return new LdapAndDatabaseAuthoritiesPopulator(this.contextSource(), "");
}
#Bean( name = "ldapAuthenticator" )
public LdapAuthenticator ldapAuthenticator() {
BindAuthenticator authenticator = new BindAuthenticator( this.contextSource() );
authenticator.setUserDnPatterns(new String[]{"cn={0},ou=prod,o=COMP"});
return authenticator;
}
#Bean( name = "contextSource" )
public DefaultSpringSecurityContextSource contextSource() {
DefaultSpringSecurityContextSource contextSource =
new DefaultSpringSecurityContextSource( ldapUrl );
return contextSource;
}
Here is how additional roles populator (LdapAndDatabaseAuthoritiesPopulator ) implemented.
public class LdapAndDatabaseAuthoritiesPopulator extends DefaultLdapAuthoritiesPopulator{
public LdapAndDatabaseAuthoritiesPopulator(ContextSource contextSource, String groupSearchBase) {
super(contextSource, groupSearchBase);
}
protected Set<GrantedAuthority> getAdditionalRoles(DirContextOperations user,
String username) {
Set<GrantedAuthority> mappedAuthorities = new HashSet<GrantedAuthority>();
/* Add additional roles from other sources for this user*/
/* below add is just an example of how to add a role */
mappedAuthorities.add(
new GrantedAuthority() {
private static final long serialVersionUID = 3618700057662135367L;
#Override
public String getAuthority() {
return "ROLE_MYAPP_USER"; //this is just a temporary role we are adding as example. get the roles from database.
}
#Override
public String toString(){
return this.getAuthority();
}
});
for (GrantedAuthority granted : mappedAuthorities) {
log.debug("Authority : {}", granted.getAuthority().toString());
}
return mappedAuthorities;
}
}
Below is how the Custom Ldap authentication provider (LdapAndDatabaseAuthenticationProvider) implemented to check if user has required roles defined in the database to access the application. If user is not in the database or roles are missing, authentication will throw account DisabledException.
franDays also suggested to use "Custom Authentication Provider". I would like to give him a credit.
public class LdapAndDatabaseAuthenticationProvider extends LdapAuthenticationProvider{
public LdapAndDatabaseAuthenticationProvider(LdapAuthenticator authenticator, LdapAuthoritiesPopulator authoritiesPopulator) {
super(authenticator, authoritiesPopulator);
}
#Override
protected DirContextOperations doAuthentication(
UsernamePasswordAuthenticationToken authentication) {
log.debug("Checking if user <{}> is defined at database to use this application.", authentication.getName());
// Here is the part we need to check in the database if user has required role to log into the application.
// After check if user has the role, do nothing, otherwise throw exception like below example.
boolean canUserAuthenticate = isActiveUserExist(authentication.getName());
log.debug("canUserAuthenticate: {}", canUserAuthenticate);
if (!canUserAuthenticate)
throw new DisabledException("User does not have access to Application!");
return super.doAuthentication(authentication);
}
private boolean isActiveUserExist(String userId) {
// Do your logic here are return boolean value...
}
You can implement your own AuthenticationProvider. The authenticate method would query an LdapTemplate and upon successful attempt then query the groups from wherever they are stored. It could look like below:
public class CustomAuthenticationProvider implements AuthenticationProvider {
private LdapTemplate ldapTemplate;
private UserRepository userRepository;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = (String) authentication.getPrincipal();
boolean success = ldapTemplate.authenticate(...);
if (!success) {
throw new BadCredentialsException("Wrong username or password");
}
User user = userRepository.findByUsername(username);
if (user == null) {
throw new BadCredentialsException("Username not known by the application");
}
return new CustomAuthentication(username, user.getRoles());
}
}
I omitted the initialization of the LdapTemplate because it depends on the specifics of your case. Same thing for the Authentication object you return for which you would need to implement a class and allow a way to build an instance by passing the username and password.
If you need guidance on how to register your auth provider using java config, this post might help: Custom Authentication provider with Spring Security and Java Config

Spring security - Limiting access to my update profile page

I am using spring security in my application and ran into a problem. My application has an Update Profile page. I have added preAuthorized() with request mapping as
#PreAuthorize("isAuthenticated()")
#RequestMapping (value="/user/{uid}/profile/update", method = GET)
public String updateProfileView(#ModelAttribute("form") UserProfileForm form, #PathVariable ("uid") Integer userId, Model model){
It works fine, and unauthenticated user can not access this page.
But the issue is that every Authenticated User can access this page.
For example : User A logged in into application, he/she will be able to update every one's profile.
My CustomUserDetailService class is
#Service
#Transactional
public class CustomUserDetailsService implements UserDetailsService {
#Resource
UserService userService;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
com.analyst.future.domain.User user = userService.getUser(email);
SimpleGrantedAuthority auth = new SimpleGrantedAuthority("ROLE_USER");
Collection<SimpleGrantedAuthority> authorities = new HashSet<SimpleGrantedAuthority>();
authorities.add(auth);
User userDeatails = new User(user.getEmail(), user.getPassword(), authorities);
return userDeatails;
}
}
I don't think i can restrict it with roles as every authenticated user will have same roles.
Is there any way i can restrict Authenticated user to access only self update profile page.
I am no Spring Security expert, but try reading up on using Expression-Based Access - Link here
There is one tiny little line that matches what you want -
For example, if you wanted a particular method to only allow access to a user whose username matched that of the contact, you could write
#PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);
I think in your case it would be something like
#PreAuthorize("email == authentication.email")
This is method level though, so maybe not what you are looking for? Good news is that there is a way to use the logged in user and match it against the request user. :)
Since all previous answers talk about matching username (which is included in the principal object) but you need to match the userID, this needs a little more work. We first need to return a custom User object that extends UserDetails. Here is how you can do that:
Step 1: Make your 'User' model implement UserDetails
#Entity
public class UserAccount implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonProperty("id")
private int userId;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String email;
private String password;
// other fields and methods
}
You will also have to override some methods from UserDetails, which is easy to figure out.
Step 2: Make you User Service implement UserDetailsService
#Component
public class UserAccountService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String username) {
// todo
}
}
Step 3: Return YOUR user model from the loadUserByUsername method
#Override
public UserDetails loadUserByUsername(String username) {
Optional<UserAccount> userAccount = userAccountDao.findByEmail(username);
if (!userAccount.isPresent()) {
throw new MyException("User account not found for the given Username.", HttpStatus.NOT_FOUND);
}
return userAccount.get();
}
Step 4: Add the #PreAuthorize expression to your resource
#PutMapping(value = "/{userId}")
#PreAuthorize("#userId == authentication.principal.userId or hasAuthority('ADMIN')")
public UserAccount updateUserAccount(#PathVariable("userId") int userId,
#RequestBody UserAccount userAccount) {
return userAccountService.updateUserAccount(userId, userAccount);
}
Important things to notice above are:
We can check if the userId is equal above, only because our custom UserAccount model has a userId. If we had returned a raw UserDetail (like org.springframework.security.core.userdetails.User) object instead, it will not have any 'id' property to match with, so above will fail.
Above #PreAuthorize annotation checks for 2 conditions. It allows the request if the user is the owner of the Account or if the user has ADMIN authority.

Spring custom AuthenticationProvider for Principal with additional data

I'm fairly new to Spring.
My goal is to have some AuthenticationProvider with a custom authenticate Method that takes an Authenticate object as argument with more than just a user name and password. Let's say a realm name (and there can be 2 users with the same user name when they are in different realms). I already looked for answers to my question, but the simple answers to questions about authentication only explain how to extend AbstractUserDetailsAuthenticationProvider, which doesn't cater my needs because the retrieve method only takes a user name as argument while I need the realm name as well to retrieve the user. The complex ones are about extending or implementing all kinds of different Spring classes and interfaces without the context being explained.
So the simple question is:
How do I have to implement / extend an AuthenticationProvider to be able to read custom data out of an Authentication object?
My call would look like this (yes, I want to get an OAuth2 token):
curl -vX POST http://localhost:9001/oauth/token \
-d "client_id=myId&client_secret=secret&grant_type=password&username=myUser&password=1234&realm=realm3"
Notice the realm=realm3 at the end.
The call without the extra data and with my own sub class of AbstractUserDetailsAuthenticationProvider already works when there's just one realm.
Thanks in advance!
How do I have to implement / extend an AuthenticationProvider to be
able to read custom data out of an Authentication object?
RealmAuthenticationProvider
public class RealmAuthenticationProvider implements AuthenticationProvider {
private RUPAuthenticator rupAuthenticator;
public RealmAuthenticationProvider(RUPAuthenticator rupAuthenticator) {
this.rupAuthenticator = rupAuthenticator;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Object principal = authentication.getPrincipal();
Object credentials = authentication.getCredentials();
Object realm = authentication.getDetails();
if (rupAuthenticator.authenticate(principal, credentials, realm)) {
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER")); //use any GrantedAuthorities you need
return new RealmAuthenticationToken(principal, credentials, realm, grantedAuths);
};
return null;
}
#Override
public boolean supports(Class<?> authentication) {
return RealmAuthenticationToken.class.isAssignableFrom(authentication);
}
}
RealmAuthenticationToken
public class RealmAuthenticationToken extends UsernamePasswordAuthenticationToken {
private Object realm;
public RealmAuthenticationToken(Object principal, Object credentials, Object realm, Collection<? extends GrantedAuthority> authorities) {
super(principal,credentials, authorities);
this.realm = realm;
}
}
RUPAuthenticator
public interface RUPAuthenticator {
boolean authenticate(Object username, Object password, Object realm);
}
You would just have to provide an implementation for RUPAuthenticator that states whether the username, password, realm combinations are correct.
And then register the custom AuthenticationProvider (RealmAuthenticationProvider) as a bean.
Below is an example of an authentication provider that accepts requests from a specific user:
#Bean
public AuthenticationManager authenticationManager() {
List<AuthenticationProvider> providers = new ArrayList<AuthenticationProvider>();
providers.add(new RealmAuthenticationProvider(new RUPAuthenticator() {
#Override
public boolean authenticate(Object username, Object password, Object realm) {
return (username.equals("sa") && password.equals("sa") && realm.equals("realm2"));
}
}));
return new ProviderManager(providers);
}
I hope this is what you were looking for.

Categories

Resources