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.
Related
Team
Using Spring boot I am able to accomplish workflows, where oAuth server can generate the token for the logged-in user. However, how does the oAuth server generate a unique token for each anonymous user?
For e.g. if 2 concurrent users are trying to access the resource server from their respective browsers, then I would like the resource server to identify the 2 different users in each subsequent requests that they make. For that, I would like to generate different tokens for each anonymous user. Is that possible and if yes then how?
It is simple thing, you are making it complex.
I would like to generate different tokens for each anonymous user. Is that possible
Once you generate valid token, user will become authenticated user.
You might think about validating token and setting ROLE='ROLE_ANONYMOUS'. As I told it will be like making simple thing to complex. Making authenticated user to Anonymous user is not good approach. You can create a new role like ROLE_SEMIANONYMOUS and grant authorities which were open for ANONYMOUS user.
(but this approach doesn't make any sense for me so explaining alternative to achieve your requirement of identifying AnonymousUser)
As "Anonymous User = UnAuthenticated User".
For anonymous user if you print principal
String principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
principal = "anonymousUser"
You can create a session for anonymous user for identifying him as given below
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
if(principal instanceof String && ((String)principal).equalsIgnoreCase("anonymousUser"))
{
if(request.getSession(false) == null)
{
HttpSession session = request.getSession();
session.setMaxInactiveInterval(300); // As per Your requirement
// store session id from session and ip address from request in to DB or cache if required and use it
}
else
{
//identify anonymous user against session id and stored details
}
}
You can achieve this by registering a filter
In convetional spring order of filter should be after springSecurityFilterChain.
In spring boot you can achieve it by FilterRegistrationBean
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.
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.
How can I easily create spring user sessions based on information found in a mySQL database connected with JPA ?
As a simple example lets say I have 2 tables:
users
INT id
VARCHAR(30) username
VARCHAR(20) password
bookmark
INT id
VARCHAR(20) name
TEXT desctription
int user (FK)
Users will be able to view their bookmarks by requesting the following URL:
http://localhost:8080/bookmarks
In this manner I need separate user sessions (authentication not my main priority in this case) to be able to show bookmarks specific to the user.
Another way to go is by accessing bookmark information through:
http://localhost:8080/{userId}/bookmarks
In this case how can I prevent users from accessing another users' bookmark information ? (Such as preventing user id 1 from accessing user id 2s' bookmarks by using the url http://localhost:8080/2/bookmarks
If you would use Spring Security to secure your app, you can create custom UserDetailsService to read user data for each request from DB. Something like this:
#Component
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
protected UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email)
throws UsernameNotFoundException {
User user = userRepository.findByEmail(email);
if (user == null) {
throw new UsernameNotFoundException(String.format("User with email=%s was not found", email));
}
return user;
}
}
Of course assuming that you have JPA entity called User implementing UserDetails interface.
With this mechanism you can inject User instance into MVC controllers:
#GetMapping("/bookmarks")
public List<Bookmark> readBookmarks(Principal principal) {
User user = (User) principal;
// read bookmarks code
}
You can also read in anywhere in the app via:
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
User user = (User) principal;
REACTION ON COMMENT:
It is broad topic with a lot of decisions to make. e.g. you may consider using JWT, OAUTH2 or token based based authentication. If you are starting with Spring Security, I recommend to look at their Guides section. Especially Spting MVC guide is relevant for you.
I was doing some research about drop-wizard security and authentication. Here is the link that I used http://howtodoinjava.com/dropwizard/dropwizard-basic-auth-security-example/.
My question is how to actually create new users, since VALID_USERS is a static final and can't be changed. I was thinking about creating a database, and that would consist of user object that contains the username and role ex. admin. (I don't need a password) But I am confused what I would return. In their example they returned Optional.of(new User(credentials.getUsername(), VALID_USERS.get(credentials.getUsername())));
Would I return a user object?
Essentially, I want to authenticate a user by the username and give them a role of authorization ex. admin, basic. But I guess I am confused how to generate a list of users and their roles. I was thinking of making a database, but I am not sure how exactly I would implement that.
public class AppBasicAuthenticator implements Authenticator<BasicCredentials, User>
{
private static final Map<String, Set<String>> VALID_USERS = ImmutableMap.of(
"guest", ImmutableSet.of(),
"user", ImmutableSet.of("USER"),
"admin", ImmutableSet.of("ADMIN", "USER")
);
#Override
public Optional<User> authenticate(BasicCredentials credentials) throws AuthenticationException
{
if (VALID_USERS.containsKey(credentials.getUsername()) && "password".equals(credentials.getPassword()))
{
return Optional.of(new User(credentials.getUsername(), VALID_USERS.get(credentials.getUsername())));
}
return Optional.empty();
}
}
in the latest version of DropWizard you can find it possible both to do Authentication and Authorization. The former, in a nutshell, instructs DropWizard to ask a user for credentials if you use basic authentication when she tries to access a resource or provide some other identity check. The latter allows one to grant a user access to various resources based on user's roles.
The are various possibilities how you can store user data and roles. Examples include a database which you mentioned, a LDAP server and a third-party identity management system.
If you are interested in Basic Authentication, you can take a look at my example here. A database is used to store user's credentials. Also, here is my little bit dated tutorial on DropWizard authentication. The code for the latest version is in the aforementioned example application.
To implement authentication only, you can skip adding roles and registering an Authorizer. To add authorization you can add a roles collection to your User entity and use annotations such as #RolesAllowed and #PermitAll along with Authorizer implementation to grant/deny access to your resources.
A link to DropWizard authentication docs is here.
Please feel free to ask more questions in comments and good luck.