Using Shiro with OAuth and JWT - java

We are using shiro in our application, and the session are saved in the database for scale. And we have our own accounts databse, so far so good.
This is the core security components:
DatabaseRealm
Vvalidate user by UsernameAndPasswordToken and the password in the database, retrieve the permissions fro the database.
DatabaseSessionDao
Extends the CachingSessionDAO, for create,read,delete sessions from the database.
DefaultWebSessionManager
Shiro built in components.
Now we have to make two kinds of improvement:
Integrate the OAuth login
For example, user should be able to login by Google or Facebook or their own accounts registered in our application.
Then I wonder how can we re-use the existed security components like the DatabaseRealm, since the realm will check the AuthenticationInfo's credentials which is not avaiable in the OAuth context:
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
SimpleAuthenticationInfo info = null;
if (token instanceof UsernamePasswordToken) {
UsernamePasswordToken uToken = (UsernamePasswordToken) token;
User user = queryUserByName(uToken.getUsername());
info = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword().toCharArray(), getName());
if (user.getSalt() != null) {
info.setCredentialsSalt(ByteSource.Util.bytes(user.getSalt()));
}
} else if (token instanceof OAuthUserToken) {
OAuthUserToken oToken = (OAuthUserToken) token;
String type = oToken.getOauthType();
String openId = oToken.getOpenID();
//then what should I do to make the `Credentials` check passed?
}
return info;
}
How to fix this?
Using JWT(Json Web Token)
The sessions are saved to the database for cluster deployment at the moment, however we found that it may slow our response, also we need to provide api for the mobile paltform, so we try to use JWT.
While it seems that shior use cookie + session to identify if user have been authenciated or not. I have no idea how to replace that.
Any suggestions?

It may be better to add new Realm / Filter / Login url for redirection.
GoogleRealm
public class GoogleOAuthRealm extends AuthorizingRealm {
...
public GoogleOAuthRealm() {
//OauthToken implements AuthenticationToken to hold code
setAuthenticationTokenClass(OauthToken.class);
}
...
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
OauthToken tk = (OauthToken) token;
String authCode = (String) tk.getPrincipal();
//1. fetch token by posting code to google
//2. validation & parse token
//org.apache.shiro.authz.SimpleAuthorizationInfo
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo();
//set permission manually
return info;
}
...
}
config GoogleRealm in securityManager, and redirect to a new url after google login success.
public class GoogleAuthenticatingFilter extends FormAuthenticationFilter
<property name="filterChainDefinitions">
<value>
...
/login/googleLogin = GoogleAuthenticatingFilter
...
</value>
</property>

As neuo wrote above, use a different realm for OAuth and then do some wiring up on the back end to reconcile the user credentials. For your scaling and performance, I’d suggest using something like Redis as a session cache - that will save you having to persist sessions in the database.

Re-Using Existing Components
Consider the construct in which you are working, you probably do not want to reuse the DatabaseRealm for other authentication schemes. Rather create individual realms for Google, Facebook, etc.
This way you will be able to control what AuthenticationTokenand AuthenticationInfo will be used in each Realm.
JWT/Cookie Session
From what you decribe I think you would likely want to create a RESTful API for your mobile platform. The REST spec requires that the server does not maintain, and more importantly not rely on server-side state.
Fortunately shiro allows you to configure endpoints that do not create a session when a request is received. More details on session management here
Considering the above, session tracking becomes irrelevant, and you could use the JWT token as a form of Bearer token to tacitly authenticate the user on every request. Be sure to consider the security short-falls of the Bearer token, which may be mitigated by always using an encrypted connection.

Related

How can I trigger OAuth/OpenID authentication on a Java Servlet and add my own roles to the user?

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/

oAuth Token for Anonymous User in Spring boot

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

Using JWT with role based authorization

Background
I am working on a web application using Spring Security and am trying to use JSON Web Tokens for authentication for the first time. The app should restrict access to certain URIs based on user roles. It should provide password change option and enable Admin users to change other users' roles.
In a tutorial I've followed, on each HTTP request to the server the database is hit by CustomUserDetailsService to load the current details of the user which seems heavy on performance:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
//...
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Long userId = tokenProvider.getUserIdFromJWT(jwt);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (Exception ex) {
logger.error("Could not set user authentication in security context", ex);
}
filterChain.doFilter(request, response);
}
//...
}
The author of the tutorial suggests another option:
Note that you could also encode the user's username and role inside JWT claims and create the UserDetails object by parsing those claims from the JWT.
However, this comes at the cost of making it difficult to change user's role as we have no way of discarding issued tokens, without keeping track of them.
Possible solution
I've researched the topic of JWT and came up with the following solution:
Let's store username and role inside JWT claims and set a short token expiration time (using exp claim) - after this period, e.g. 15 minutes, we hit the database to check user's details. If the role has changed, we generate the new token with the new role in payload. If the password has been changed, we require the user to re-authenticate, before generating the new token.
An obvious downside of this solution is that any change in user access rights is effective after the period of expiration time.
Question
Are there any other ways of tackling the issue of handling user details change while using JWTs?
We use JWT tokens with Spring Security and an Angular webapp.
I think your Possible Solution idea is valid. We handle this in a similar manner. Our auth flow looks like:
User signs in at a URL and the response header contains the JWT token
JWT tokens have a short timeout (minutes)
The webapp pings a 'refresh token' service at a shorter interval which detects if a token is valid. If so, the server re-issues a new token including any updated roles for the user, and this is then stored by the webapp for inclusion in future requests to the backend.
Due to the 'refresh' service, if a user's roles change, or if they're banned from the system, they will be automatically notice the new role or be 'locked out' no later than the token expiration time.
This has worked well for us for years now. Our users' roles don't frequently change, and if it's ever desired to have their role immediately updated, they can always sign out / sign back in.
Additional potential solution
However, if it's paramount to have the user's role immediately updated in your system, you could have a Filter in Spring check for the JWT header on each request, and have it do the JWT verification, and add a new, refreshed JWT token on every response.
Then your client can expect to get a revised JWT token on each response back from the server, and use that revised JWT token for each subsequent request.
This would work, but it'd also be relatively expensive if you have a lot of traffic.
It all depends on your use case. Like I said, the 'refresh' service has worked well for us.

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.

Dropwizard auth users

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.

Categories