I'm working on a cloud endpoints backend and want to restrict certain operations to admin users.
My current code works like this:
#ApiMethod(httpMethod = "PATCH", name = "item.update", path = "items")
public Item update(Item newObject, User user)
throws UnauthorizedException, OAuthRequestException {
OAuthService oAuthService = OAuthServiceFactory.getOAuthService();
if (!oAuthService.isUserAdmin()) {
throw new UnauthorizedException("Only admin users can modify content.");
}
...
}
I know app engine has a concept of user roles, but I'm curious if Endpoints
do. I've tried using the OAuthService.isUserAdmin() call but that doesn't
seem to be working out very well and the docs have a big old warning saying
Note: You should not confuse Endpoints auth with the auth for
non-Endpoints App Engine web apps described in the article on configuration settings
https://developers.google.com/appengine/articles/auth in the Admin
Console, where you also specify the user login requirement in your
web.xmlhttps://developers.google.com/appengine/docs/java/config/webxml#Security_and_Authentication
file. That approach is not used with Endpoints."
Do I have to create some sort of authorization myself that uses the User object that's passed into the update method? Any thoughts?
I had similar issues. Indeed OAuth user service has nothing to do with AppEngine user service. What I ended up doing was having a dedicated user type entity in my datastore where I store a specific flag (regular/admin) for each user. This flag is updated when I use AppEngine user service (i.e. so that the administrators I specified in the console get the proper admin flag).
In my endpoints API I get the current user authDomain and id, look up in my datastore to check whether it has the admin flag. The key of my user entity is composed of "authDomain:userId" and as I only support google user for now, it looks like (gmail.com:123456789)
This means that an administrator has to login once using the AppEngine UserService (i.e. a dedicated webpage in my case) so that the flag is properly updated
I needed to do the same thing and validate some endpoint to grant access only to admin members listed in the project console and used the same implementation presented above, but the oAuthService.isUserAdmin() accept one or more string parameters, this parameters are scopes that you specify and the Oauth uses to get user informations, in my case i just set this parameter and it works like the code bellow.
OAuthService authService = OAuthServiceFactory.getOAuthService();
User user;
try {
com.google.appengine.api.users.User currentUser =
authService.getCurrentUser(Constants.EMAIL_SCOPE);
if (currentUser != null && authService.isUserAdmin(Constants.EMAIL_SCOPE)) {
user = new User(currentUser.getEmail());
return user;
}
...
The EMAIL_SCOPE constant is defined by
public static final String EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email";
In my case i implemented an authenticator, to pass user information to endpoint only if it's admin user, you can read more about the authenticators if you want.
https://cloud.google.com/appengine/docs/java/endpoints/javadoc/com/google/api/server/spi/config/Authenticator
Related
On a local Wildfly server I have a simple Java servlet like below:
import javax.servlet.annotation.HttpMethodConstraint;
import javax.servlet.annotation.ServletSecurity;
import javax.servlet.http.HttpServlet;
#ServletSecurity(httpMethodConstraints = { #HttpMethodConstraint(value = "GET", rolesAllowed = { "debug" })})
public class DebugServlet extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
try ( PrintWriter out = response.getWriter()) {
/* TODO output your page here. You may use following sample code. */
...
Principal user = request.getUserPrincipal();
out.println("<p>" + (user != null ? user.getName() : "No user") + "</p>");
...
}
}
}
I have set up authentication with Wildfly's Elytron v1.17.2 using OpenID Connect (OIDC) with a third party authentication server. If it matters, it is not a Keycloak server, and changing the auth server isn't an option; using this auth server is a strict business requirement. The auth server has a bunch of users which we want to give access to, but for my particular client_id none of the users have any roles associated with them, and we want to avoid adding roles through the auth server because the process is...tedious at best. Bureaucratic red tape, every time, for every user. Our end goal is to have our own roles in this server's database and we're only going to the 3rd party to handle the login and then verify the login is valid and which user it is.
If I include rolesAllowed = { "debug" } in the HttpMethodConstraint, authentication with the 3rd-party auth server gets triggered and works correctly. But because none of the users have roles associated with them, they don't have the required "debug" role. The processRequest method is never reached and the user gets a "Forbidden" error page.
If I remove or empty the rolesAllowed field of the constraint, authentication with the 3rd-party auth server is not triggered, and there's no UserPrincipal which we can use to identify the user.
I've tried putting a javax.servlet.Filter in to intercept the request before it gets to my Servlet, but if the "debug" role is required then the filter doesn't get reached either. I had hoped to intercept the request after the user authenticates and then add roles to the user, or a session variable, or something which my servlet would then check for permission to do various actions.
I've tried a few other combinations which have not triggered authentication:
#ServletSecurity(httpMethodConstraints = { #HttpMethodConstraint(value = "GET", emptyRoleSemantic = ServletSecurity.EmptyRoleSemantic.PERMIT)})
and
#ServletSecurity(httpMethodConstraints = { #HttpMethodConstraint(value = "GET", transportGuarantee = ServletSecurity.TransportGuarantee.CONFIDENTIAL)})
No luck.
Is there a way I can trigger 3rd-party authentication, and get a UserPrincipal with the authenticated user's username/user id, without requiring a specific role? -OR- Is there a way I can add roles to the authenticated user after authenticating but before being blocked by the HttpMethodConstraint?
The reason your custom filter approach will not work is because the elytron security framework handles this well before your filter chain is invoked. You might try custom role mapper within elytron security as explained here (Section 3.2.9) and here to add some predefined roles.
RoleMapper - After the roles have been decoded for an identity further
mapping can be applied, this could be as simple at normalising the
format of the names through to adding or removing specific role names.
You would obviously need to configure the elytron subsystem to use your custom role mapper. You can take a look at an example configuration for regex-role-mapper here ( in your case you need a custom RoleMapper instead of regex-role-mapper)
I believe elytrons role mapping is the way to go. You could configure a Regex mapper to map every role to one static role. Or use the group-decoder-mapper to convert group names into roles.
See for example: https://wildfly-security.github.io/wildfly-elytron/blog/regex-role-mapper/
I'm trying to implement a custom keycloack Authenticator SPI for authenticating against an external Datasource. Spring boot Rest Service is also available, I can also use that.
Use case I am trying to solve is
User is presented keycloak login screen. Onsubmission User is validated against external Datasource.
Retrieve some attributes from external datasource, map it to keycloak's id and access token.
Also put in a condition of user restriction of same user logging in multiple times at the same time.
I was thinking, it could be solved by retrieving user session information that's available in the keycloak datasource. If i use external datasource, does keycloak still maintain session information?
I followed section 8.3 of the official guide (https://www.keycloak.org/docs/latest/server_development/index.html#_auth_spi_walkthrough), which is very similar to what I need.
Now i skipped and started as per section 11(https://www.keycloak.org/docs/latest/server_development/index.html#_user-storage-spi) seems more apt as well.
What I have done is started with implementing custom authenticator SPI thought it isn't the right way and now implemented UserStorageProvider.
/***
* From UserLookupProvider
*/
public UserModel getUserById(String id, RealmModel realm) {
System.out.println("ID: " + id + ":REALM:" + realm);
StorageId storageId = new StorageId(id);
/**
* StorageId.getExternalId() method is invoked to obtain
* the username embeded in the id parameter
*/
String username = storageId.getExternalId();
System.out.println("Name:" + username);
return getUserByUsername(username, realm);
}
/***
* From UserLookupProvider
* This method is invoked by the Keycloak login page when a user logs in
*/
public UserModel getUserByUsername(String username, RealmModel realm) {
System.out.println("USERNAME: " + username + ":REALM:" + realm);
UserModel userModel = loadedUsers.get(username);
if (userModel == null) {
String password = properties.getProperty(username);
if (password != null) {
userModel = createUserModel(realm, username);
System.out.println("New UserModel:");
System.out.println(userModel.toString());
loadedUsers.put(username, userModel);
}
}
return userModel;
}
protected UserModel createUserModel(RealmModel realm, String username) {
return new AbstractUserAdapter(session, realm, model) {
#Override
public String getUsername() {
return username;
}
};
}
Followed the doc(https://www.keycloak.org/docs/latest/server_development/index.html#packaging-and-deployment-2)
The class files for our provider implementation should be placed in a jar. You also have to declare the provider factory class within the META-INF/services/org.keycloak.storage.UserStorageProviderFactory file.
Question here is: jar I created doesn't have services directory inside "META-INF" folder, do I need to create manually and add it?
org.keycloak.examples.federation.properties.FilePropertiesStorageFactory
Once you create the jar you can deploy it using regular WildFly means: copy the jar into the deploy/ directory or using the JBoss CLI.
After creating jar using maven, copied jar to "keycloak-6.0.1\standalone\deployments" folder. but i don't see my provider in the "User Federation list"
Any suggestion/help would be greatly appreciated!!
Thanks in advance for your suggestions.
Incase if anybody ran into issues like this:
UserStorage SPI wasn't displaying because of META-INF/services folder. It's provided in the documentation but it isn't clear
In src/main/resources, create a folder structure META-INF/services
Create a file called org.keycloak.storage.UserStorageProviderFactory (the whole thing is the filename) in META-INF/services directory. Its contents is the fully qualified class name of your SPI:
com.test.UserSpi
Ok you clarified you need a User Store Provider API. Great
Now as for your 2nd "Problem/Challenge":
Retrieve some attributes from external datasource, map it to keycloak's id and access token. Need to retrieve users unique id and add it as subject id in the jwt. That's the id, rest of the services can use to retrieve the id, when this token is passed to other services.
For this, the best you can do is:
Add those user's unique data as users attributes (see them on the Admin console)
Create a "Client scope" on Keycloak, with a mapper for "user property"
to map those properties you'd like to add (from your user) to your Id-token and access-token. You also need to tie your client with your just created "client scope". This may sound a little bit confusing, but this video is great material and I bilieve it'll help you alot: https://www.youtube.com/watch?v=ZxpY_zZ52kU (arround min 6:30 you'll see how to add extra user info to your tokens)
also check this page out: https://jwt.io/ (when you paste there encoded tokens, you can see their contents), it is a great tool for developers.
When you advance in your solution I'll help with the unique session, or you post that as a different question, as that's a different problem.
Hope it helps.
I'm not exactly sure what do you need. Lets start by differentiating Authentication SPI (federated identity check) vs User Provider SPI (federated users). The first one (section 8 of the doc - is more about focusing on authenticating users against an external service - similar to facebook, or google). Federated user store is more like you have your own users in a legacy system with their legacy "roles structure", and you basically want to manage them via keycloak (either by importing them, or by querying the via some API - this would be section 11 of that documentation). So please decide what is indeed what you need.
2nd, you mention following:
> User is presented keycloak login screen. Onsubmission User is
> validated against external Datasource.
>
> Retrieve some attributes from external datasource, map it to
> keycloak's id and access token.
>
> Also put in a condition of user restriction of same user logging in
> multiple times at the same time.
>
> I was thinking, it could be solved by retrieving user session
> information that's available in the keycloak datasource. If i use
> external datasource, does keycloak still maintain session information?
What do you mean by: Retrieve some attributes from external datasource, map it to keycloak's id and access token. ? Usually you only retrieve user core information, plus maybe roles and other custom attributes (not session information). Keycloak itself as an Authorization Server based on openIDConnect, will generate acces tokens which already contains information about what protected resource can by accessed by whom, so you dont really need to import any session from somewhere else, or concern yourself with the generation of said tokens.
regarding: Also put in a condition of user restriction of same user logging in
multiple times at the same time. what exactly are you trying to accomplish (or avoid?) when you log the 1st time your client receives a Bearer token valid for X amount of time, within that time you wont need to log yourself again, until the token expires or is roveked; again something your Auth server takes care of, not something you implement. Is there something more specific you want?
I was thinking, it could be solved by retrieving user session information that's available in the keycloak datasource. If i use external datasource, does keycloak still maintain session information? This doesn't sound right, what session data do you refer to? or you need access to? your userdata, scopes, roles, etc can be accessed via KEycloak Rest API (https://www.keycloak.org/docs-api/6.0/rest-api/index.html#_overview). Your external datasource is for user related core data (not external sessions), why do you think you need to import an external session?
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.
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.
I want to access google AuditService api from google app engine application.user is already logged in to my app using UserService via google credentials.
Is there a way I can make authenticated calls without the need of user re authenticating my app.
AuditService service = new AuditService("userEmail", "password", "domain name", "application name");
in the above call I don't have userEmail and password,but I have the user object which I got using UserService ,when user first logged in to my application.Any way to use this user object to make authenticated calls ?
It looks like you can do this by storing and providing an authentication token instead of the user's credentials. On the first request, proceed with the username/password authentication as normal, then generate an authorization token using service.getAuthToken(...). Store that token with your user model. For subsequent requests, instead of using the AuditService constructor that takes the user's credentials, use the one that omits them: AuditService(domain, applicationName). Once constructed, call service.setUserToken(token) to provide the stored token. You should then be able to make authenticated requests. Be sure to handle AuthenticationExceptions that will be raised by invalid/expired tokens.