Initialize repositories protected by Spring authentication - java

I use Spring Boot Data REST to map Hibernate repositories of entities to REST routes. I also use Spring Security's #PreAuthorize annotation to secure certain methods of those repositories. This works fine during production, but when I want to programmatically initialize the repositories with data (e.g. adding a first user to get started and some other sample data), it complains that "An Authentication object was not found in the SecurityContext". I realize that this is because there is no authenticated user in the security context at the time I configure the database (with an autowired component saving objects to repositories). However, I'd like to temporarily circumvent this authorization during configuration, so that I can initialize the database.
Is there any way to achieve this? And if not, how can I manually log in in the initialization method? I've seen some attempts to the second approach, but they all require access to an AuthenticationManager, which I don't know how to obtain.

You can use the following codes to create a fake user who has enough permission and set it to the SecurityContext just before running that protected method:
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
//Setup the permission that the user should have in order to run that method.
//It is the customized value based on your security configuration
grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_SUPER_ADMIN"));
//Ensure this user is enabled , active and has above permission
User user = new User("admin", "password", true, true, true, true, grantedAuthorities);
Authentication auth = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
After executing that protected method , reset the SecurityContext (which is equivalent to logout) :
SecurityContextHolder.clearContext();

An easy solution: Ignore security and focus on an empty repository
#Component
class InitializeDataIfNoDataPresent {
private final UserRepo repository;
public InitializeDataIfNoDataPresent(UserRepo repo) {
this.repository = repo;
}
#EventListener(ApplicationReadyEvent.class)
public void init() {
if(0 == repository.count()) {
setupData();
}
}
#PreAuthorize("hasRole('ADMIN')")
public void setupData() {
// your existing setup
}
}
When the application starts and your repository is empty, setupData() will be called even if there is no user present.
ref: stackoverflow-initialization-secured

Related

Spring boot Basic Authentication and OAuth2 in same project?

Is it possible to use OAuth2 for certain endpoints in my rest application and use basic authentication too for some other endpoints.
It should all work on spring security version 2.0.1.RELEASE. I hope someone can help me further.
Yes, it's possible to use a basic authentication as well as an OAuth2 authentication intertwined, but I doubt you'll be able to set it up easily as HttpSecurity's authenticated() method doesn't allow you to pick which of your authentication method (oauth2Login/formLogin) will work.
However, there's a way to easily bypass that:
You could add a custom authority, let's call it ROLE_BASICAUTH, when an user connects using basic auth, and ROLE_OAUTH2 when an user connects using OAuth2. That way, you can use
.antMatchers("/endpoint-that-requires-basic-auth").hasRole("BASICAUTH")
.antMatchers("/endpoint-that-requires-oauth2").hasRole("OAUTH2")
.anyRequest().authenticated()
When they reach an endpoint that you want basic authentication (and not OAuth2), you check their current authorities, and if it's not BASICAUTH, then you invalidate their session you display a login form without OAuth2 (to force them to use the basic authentication).
The downside to doing that is that you'd need to implement both a custom UserDetailsService as well as a custom OAuth2UserService...
But that's actually not that hard:
#Service
public class UserService extends DefaultOAuth2UserService implements UserDetailsService {
// ...
#Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
OAuth2User user = super.loadUser(oAuth2UserRequest);
Map<String, Object> attributes = user.getAttributes();
Set<GrantedAuthority> authoritySet = new HashSet<>(user.getAuthorities());
String userNameAttributeName = oAuth2UserRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();
authoritySet.add(new SimpleGrantedAuthority("ROLE_OAUTH2"));
return new DefaultOAuth2User(authoritySet, attributes, userNameAttributeName);
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserDetails user = getUserFromDatabase(username); // you'll need to provide that method (where are the username/password stored?)
if (user == null) { // UserDetailsService doesn't allow loadUserByUsername to return null, so throw exception
throw new UsernameNotFoundException("Couldn't find user with username '"+username+"'");
}
// add ROLE_BASICAUTH (you might need a custom UserDetails implementation here, because by defaut, UserDetails.getAuthorities() is immutable (I think, I might be a liar)
return user;
}
}
Note that this is a rough implementation, so you'll have to work it out a bit on your end as well.
You can also use this repository I made https://github.com/TwinProduction/spring-security-oauth2-client-example/tree/master/custom-userservice-sample as a guideline for the custom OAuth2UserService
Good luck.

How to get current user authentication inside UserDetailsService

In my application I am trying to unite ActiveDirectory authentication with OAuth2 refresh tokens.
I was able to successfully authenticate via ActiveDirectoryLdapAuthenticationProvider. I have also provided my custom implementation of LdapUserDetailsMapper that populates the UserDetails with some custom attributes taken from ActiveDirectory. Key thing here is that these attributes have a confidentialty flag set on them and are only available to the user itself (i.e. authenticated user could read the values of these attributes for himself but not for the others). These attributes are stored in Authentication object and are used by an application in a context of an authenticated user.
Things get tricky when I try to add refresh tokens to the picture. Refresh tokens require me to implement a UserDetailsService where I have to provide new UserDetails having just a user name. This is not feasible due to confidentialty flag. Even if I have some master account in my application with the ability to browse ActiveDirectory I will not be able to retrieve the confidential attributes.
So I would rather prefer to provide more atomic implementations like the function that checks if the user is still active or the function that provides a renewed set of user authorities. Unfortunately I did not find this level of atomicity in Spring Security. So it looks like for refresh tokens I have to provide an implementation of UserDetailsService.
If I have to provide new user details I would like to have an access to previous user Authentication object. In this case I will check the user and if it is still active I will copy all the confidential information from previous Authentication. The problem is that it does not seem to be available. At the moment when UserDetailsService::loadUserByUsername() is called SecurityContextHolder.getContext() does not contain the user authentication. Authentication is also not available from UserDetailsService API - I only get the user name. At the same time user's Authentication object is present just one stack frame up in UserDetailsByNameServiceWrapper class:
public UserDetails loadUserDetails(T authentication) throws UsernameNotFoundException {
return this.userDetailsService.loadUserByUsername(authentication.getName());
}
The least thing I want to do here is to implement some in-memory storage for all user confidential information to be used whenever I need to provide new UserDetails. I already have all the required information in user authentication managed by Spring and doing this on my end seems to be just surplus.
And here comes question list:
If you feel that I am doing something terribly wrong from the perspective of application security architecture, please tell me
Is there a way to tell Spring during refresh token procedure to use previous UserDetails object so that application could just answer the question if the user is still active and should be issued a new access token (and not provide the UserDetailsService at all)?
Is there a way to get previous user Authentication object during the call to UserDetailsService::loadUserByUsername() so that I could use it as a source of confidential info?
Is there some other approach that I do not see at the moment to add refresh tokens to my application?
Update:
Here I saw a comment that you could implement your own AuthenticationUserDetailsService to work around the problem. This I do not see how to do. It is hardcoded in AuthorizationServerEndpointsConfigurer that it always creates an instance of UserDetailsByNameServiceWrapper so to provide your own implementation you would have to interfere into AuthorizationServerEndpointsConfigurer initialization process.
OK, looks like the answer with Spring Security 4.0 is you can't.
So I had to apply the following hack which works, but I do not like it very much. Since it works I am posting it here. Since it does not solve the original problem, but works around it I will not mark it as accepted by the author.
Switch to JWT tokens
Use custom TokenEnhancer to inject all information that is required to recreate the user (user secret in my case) to the token directly. Of course, the value must be encrypted by the server with symmetrical crypto algorithm before adding it to the token.
Instruct authorization server to use custom AccessTokenConverter. This implementation of AccessTokenConverter would extract the secret value from the token, decrypt it and put it to ThreadLocal field.
Instruct custom UserDetailsService to retrieve the user secret from the ThreadLocal field set in step 3. This is the best way I found so far to deliver the current authorization context to UserDetailsService. And this is the part that I do not like most in my solution.
Use custom security filter to erase the value set in step 3 from ThreadLocal field.
P.S. I still do not see the possibility to implement custom AuthenticationUserDetailsService that was mentioned earlier. If such possibility exists it could have been another way to solve the problem.
Some useful links:
Extending Spring Security OAuth for Multi-Tenant
Detailed explanation of the problem in spring-security-oauth GitHub
I've got the response from Joe Grandja on spring-security-oauth github page.
Posting it here since it actually provides an answer to the original question.
Hi #masm22. To help with question 1 and 2, below is a custom configuration that will allow you to hook into the refresh_token grant and provide your own behaviour or delegate to super to proceed with current behaviour. It will also allow you to access the user Authentication so you can read your custom (confidential) attributes.
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
..... // other config
#Autowired
private ClientDetailsService clientDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenServices(this.customTokenServices());
}
private DefaultTokenServices customTokenServices() {
DefaultTokenServices tokenServices = new CustomTokenServices();
tokenServices.setTokenStore(new InMemoryTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setReuseRefreshToken(true);
tokenServices.setClientDetailsService(this.clientDetailsService);
return tokenServices;
}
private static class CustomTokenServices extends DefaultTokenServices {
private TokenStore tokenStore;
#Override
public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest) throws AuthenticationException {
OAuth2RefreshToken refreshToken = this.tokenStore.readRefreshToken(refreshTokenValue);
OAuth2Authentication authentication = this.tokenStore.readAuthenticationForRefreshToken(refreshToken);
// Check attributes in the authentication and
// decide whether to grant the refresh token
boolean allowRefresh = true;
if (!allowRefresh) {
// throw UnauthorizedClientException or something similar
}
return super.refreshAccessToken(refreshTokenValue, tokenRequest);
}
#Override
public void setTokenStore(TokenStore tokenStore) {
super.setTokenStore(tokenStore);
this.tokenStore = tokenStore;
}
}
}
The other thing I want to point out for your information is in DefaultTokenServices.refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
has the following code:
OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);
if (this.authenticationManager != null && !authentication.isClientOnly()) {
// The client has already been authenticated, but the user authentication might be old now, so give it a
// chance to re-authenticate.
Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());
user = authenticationManager.authenticate(user);
Object details = authentication.getDetails();
authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);
authentication.setDetails(details);
}
The user is being re-authenticated. Possibly something you may want to do in your custom implementation if need be.

Spring Security: how to map Active Directory users to application users?

I'm integrating Active Directory authentication in an existing Spring web app.
At the moment user details are stored in a DB table and I implemented a custom UserDetailsService to authenticate users.
Following several tutorials I implemented Active Directory support via ActiveDirectoryLdapAuthenticationProvider in my application, and it basically works, but I need something more specific.
Instead of mapping AD groups to application roles, I need to map AD users to existing users in my web app. In other words I want to enable AD authentication only for users in my Users table.
A user can access via AD credentials ONLY IF is already registered in application DB.
Authorization info for each user are stored in DB. This way, each user can be authenticated both via DB user+password or via AD.
Is it possible to achieve this with Spring Security? and how?
NOTE I'm using Spring v 3.2.9 and Spring Security v 3.2.3
As a workaround I implementend a custom AuthenticationProvider and a custom UserDetailsContextMapper.
Becouse ActiveDirectoryLdapAuthenticationProvider is a final class I implemented the ADCustomAuthenticationProvider via composition, this way:
public class ADCustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private ActiveDirectoryLdapAuthenticationProvider adAuthProvider;
#Autowired
private UserDao uDao;
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
String principal = authentication.getPrincipal().toString();
String username = principal.split("#")[0];
User utente = uDao.findByUsername(username);
if (utente == null) {
throw new ADUnregisteredUserAuthenticationException("user ["
+ principal + "] is not registered");
}
return adAuthProvider.authenticate(authentication);
}
#Override
public boolean supports(Class<?> authentication) {
return adAuthProvider.supports(authentication);
}
}
In the mapper I extended LdapUserDetailsMapper implementing only mapUserFromContext method.
public class ADCustomUserDetailsContextMapper extends LdapUserDetailsMapper {
#Autowired
private UserDetailsService userDetailsService; // ... the service used for DB authentication
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx,
String username, Collection<? extends GrantedAuthority> authorities) {
return userDetailsService.loadUserByUsername(username);
}
}
(I'll probably need to implement mapUserToContext method beacuse I'm using a custom UserDetails implementation that not extends LdapUserDetails, so the reverse convertion process could throw an exception...)
NOTE This way I'm repeating the same query (to Users table) two times... I'd like to find a way to make a single query and share the result among AuthenticationProvider and UserDetailsContextMapper.e result among AuthenticationProvider and UserDetailsContextMapper.
If the user is not the user database, it should be assigned "no access" in the authorization process. So it doesn't really matter if he's authenticated or not, the application won't allow him to access because he's not authorized to the application.

Add user to session and get it in an entity using spring

I am using spring & jersey2 to serve some rest-requests like:
#GET
#Path("/someservice")
#Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public String getSomeStuff(...) {
login(...);
// ...
}
During a rest-request, I get an authorized user of the rest-request.
Now I need this user while updating or creating entities like:
#MappedSuperclass
public abstract class PersistentObject {
#PrePersist
#PreUpdate
public void onSaveOrUpdate() {
setCreationUser(...); // How to get the user of this session?
}
// ...
}
How can I get the current user of the rest-request there?
You can try to perform your login operation (for appropriate resource methods) in a ContainerRequestFilter and set SecurityContext:
#Provider
public class SecurityFilter implements ContainerRequestFilter {
#Override
public void filter(final ContainerRequestContext context) throws IOException {
final Principal user = login(...);
context.setSecurityContext(new SecurityContext() {
public Principal getUserPrincipal() {
return user;
}
// Other methods omitted.
});
}
}
Make sure you have jersey-spring3 module on your class-path and the Jersey-Spring integration allows you to inject SecurityContext into a Spring service:
#Service
public MySpringService implements MyService {
#Context
private SecurityContext context;
public String doStuff() {
final Principal user = context.getUserPrincipal();
// ...
}
}
You can't do this if the service, you want to use user principal in, is neither managed by Jersey nor Spring.
Spring Security might be useful to you in two ways:
It can manage authentication, (you would not need to do that login(...) call yourself, it would be done automatically by Spring Security filter chain. But you can still do it manually if you want.
Once a request has been authenticated, as long as the request is alive you can access the authenticated user from anywhere just by doing:
Authentication auth = SecurityContextHolder.getSecurityContext().getAuthentication();
// auth is an object that holds the authenticated user's data
I think you need some sort of authentication by the fact that you make a login(...) and you want to audit the user afterwards. You might not nedd an authentication form, but you do need authentication. Spring Security is not only for interactive applications, you can set up an authentication filter that does authentication based on cookies, request parameters, client certificates or whatever, all of that without user interaction.
Furthermore, Spring Security is very extensible, if you have your authentication method already implemented, integrating with Spring Security is easy. And it is also flexible: you don't need to use the security filter chain if it is too heavyweight for your use case. You can do most things manually and use just a little bit of Spring Security if you want.
I really suggest you take a deeper look at Spring docs about:
Spring Security core components overview and Spring Security authentication overview
I think with just that you will be able to get something working.

Spring security openId support and user deauthentication

I am trying to handle a situation when after an successful authentication with openId provider I discover that there is no account in my db associated with user openId identifier.
Can you tell me how should I handle the situation. Now, I am displaying register form and ask a user for creating an account. However, I have a problem with user authentication status, he is now being seen as authenticated by spring SecurityContext class.
How do I deauthenticate user in my controller action before redirecting to ''register new user page''? Is this approach a good one or should I do it in some other way?
Ok, so separating authentication from authorization as was mentioned in Samuel's post was really helpful. However there are still many gotchas and I found deauthentication still a must because there is no easy way in spring to add to user new roles. So the easiest way is to force user to login again and let spring handle role assignment during login.
In order to deauthenticate user in spring security you have to invoke:
SecurityContextHolder.clearContext();
as an alternative you can throw an exception in your UserDetailsService implementation (see below). It has the downside that you would deauthenticate user and lose user context data so it would be impossible to match new user accout with openid account during process of creating new local account. And you have to match those account after user login with traditional username and password. My solution was to deauthenticate user just after creating new account.
In order to grant user roles(privileges) you have to override UserDetailsService, in case someone find this useful here is my implementation:
public final class MyUserDetailsService implements UserDetailsService {
private final UsersDao usersDao;
#Autowired
public UserDetailsServiceImpl(final UsersDao usersDao) {
this.usersDao = usersDao;
}
#Override
public UserDetails loadUserByUsername(final String username) {
UserEntity user = usersDao.getUserByOpenIdIdentifier(username);
if (user == null) {
// there is no such user in our db, we could here throw
// an Exception instead then the user would also be deuthenticated
return new User(username, "", new ArrayList<GrantedAuthority>());
}
//here we are granting to users roles based on values from db
final Collection<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority(user.getUserType().toString()));
final UserDetails result = new User(username, "", authorities);
return result;
}
}
I think that you might be mixing two concepts: authentication and authorization. Authentication is knowing who the user is, authorization is the right to use access a resource of a feature.
In spring security, this two concepts are implemented by the authentication-manager and the access-decision-manager.
The fact that a user does not exist in your database is not a reason to deny him is identity: no deauthentication! But beeing authenticated can be a criterion in the access decision management. Example: the AuthenticatedVoter.
You should not touch at the authentication, but customize the access-decision-manager to apply the following rules:
A user who exists in your database has access to everything except account creation feature
A user who doesn't exist in your database has access only to the account creation feature.
This is all about access management, not authentication.
Read more at http://static.springsource.org/spring-security/site/docs/3.0.x/reference/ns-config.html#ns-access-manager
PS: The documentation is not exhaustive in spring security, but the source code is very readable. My advice is to check it out and look at the implementation of the elements you need to customize.

Categories