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.
Related
We are implementing a microfrontend, microservices architecture.
App1 is the microfrontend app - ui built on React, backend built on spring boot. It handles the authentication and provides the token to it's child app. The token is generated using Jwts as follows:
Jwts.build().setClaims(claims).setSubject(username).setExpiration(expirationDate)...
App2 is a child app of the microfrontend setup. It's ui is built on React, backend built on spring boot. App1 attaches App2 via react-iframe while passing the token as follows:
<Iframe url={`${urlOfApp2}`?token={jwtToken}} ... />
App2 on useEffect checks if window.location.search has the token field and use this to set the Authenticcation in its security context. This is done by calling endpoint /user in App2. The App2 backend will then call an endpoint /validate from App1 to check if the token is valid. If it is valid, App2 parses the token and creates an Authentication object and saves it to its context as follows:
final Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
This will create the JSESSIONID. So everytime an endpoint from App2 is requested, for example /someendpoint, it will check if the request has the required authority as set in the code above. The security config is as follows:
http...
.antMatchers("/user").permitAll()
.anyRequest().hasAuthority("SOME_AUTHORITY_PARSED_FROM_THE_TOKEN")...
This works as the /user is called once to check if token is valid and a session on App2 is initialized. So for succeeding requests, it will check if it has the proper authority.
The problem is, the session on App2 has different expiration compared to that set on the token. How can we sync the expiration on the session on App2 with the token provided by App1?
Several things here;
You are exposing the token in cleartext (the iframe url). Even when HTTPS/TLS is used this URL itself is always cleartext and can be seen by anyone on the network at either end, or a middle party along the physical devices that relays network packets from client to server, anyone can see the token who are joined to the same wifi network as an example but the largest concern with exposing sensitive information in the URL is these URLs are logged everywhere, on the same device in unprotected files that other software can access, on all the devices on the network paths along the way (maybe even routed through countries you're not thinking can read this traffic), and on the server the same logs are created and often not protected either and these logs are almost always also sent of to a logging service like Datadog, SUMO, (hundreds others) who also see this sensitive token in the URL from these logs.
Bottom line, never, ever, send anything sensitive in a URL.
Which JWT scheme are you implementing for the client?
a) Is there a private key generated by the client and the public key sent to the server to form a JWT with encryption? Or;
b) Is it using a HMAC which is essentially a signature accompanying the data to provide an integrity check, which only ensures data was not modified, this type of JWT has no encryption and therefore the JWT is entirely 'readable' by anyone by design
c) There is no actual authentication happening on the client at all, the client is provided the JWT to avoid having to do it.
If (a) then the client generated a key/pair and it needs to send the public key to the server, not the server share anything with the client.
If (b) then the client needs the shared secret to complete the signing, it will generate the JWT 'token' itself new every time it makes a request to the microservices endpoints.
So you don't give a token to a client, the client generates (or rather constructs) it's own signature (the token) using the shared secret. The server also has the shared secret and can also construct and identical signature (token) on the server without the secret ever being sent over the communications channel with the data.
You will need to find an out-of-band method for a client to obtain the shared secret because sending it in the same communication method will defeat the entire purpose if having JWT.
If (c) This is not a use case for JWT. If you do not do actual authentication on the client, then the client-side is not proven to be 'authentic' using a security characteristic that is established to be a form of an authentication mechanism. Passing a string to a client endpoints from a server is only an authentication on the server, when the client later communicates using your method it is claiming it is the 'authentic' server who 'authenticated', which it is not, it is the client..
That's why JWT is not making sense in this situation. If you do not ever what to do the authentication on the client side, then the client must be treated as unauthenticated and don't need authorisation (JWT) because they have no authentic identity established and are indistinguishable from one another.
If you do want client side authorisation, you need client side authentication first. Maybe you are having trouble with implementation of Authentication on a client, you may find OIDC easier
If App2 is the child, then App1 should dictate the auth expiration protocols. Why not just set your session expiration equal to your token expiration? Each time a user logs in to App1, decode the JWT and pass the expiration as a variable to App2.
Or set a timeout equal to moments before expiration and silently pass token refresh requests until both expirations are nearly equal.
Also, don't pass tokens openly in this manner. Instead, pass encrypted headers or cookies. Or hash tokens and save them to a secure database for query or update as needed.
One possible way to sync the JWT token expiration from the container app to the child app's session is to use a session listener on the child app that will invalidate the session when the token expires. A session listener is a class that implements the HttpSessionListener interface and can perform actions when a session is created or destroyed. You can register a session listener in your child app by adding a #WebListener annotation or by declaring it in your web.xml file.
To invalidate the session when the token expires, you can store the token expiration time as a session attribute when you create the session in the /user endpoint. Then, in the sessionDestroyed method of the session listener, you can compare the current time with the stored expiration time and invalidate the session if the token has expired. This way, the child app will not accept any requests from the session after the token expiration and will require a new token from the container app.
Example
Here is an example of a session listener class that invalidates the session when the token expires:
#WebListener
public class TokenExpirationSessionListener implements HttpSessionListener {
#Override
public void sessionCreated(HttpSessionEvent se) {
// do nothing
}
#Override
public void sessionDestroyed(HttpSessionEvent se) {
// get the session and the token expiration time
HttpSession session = se.getSession();
Long tokenExpiration = (Long) session.getAttribute("tokenExpiration");
// check if the token has expired
if (tokenExpiration != null && System.currentTimeMillis() > tokenExpiration) {
// invalidate the session
session.invalidate();
}
}
}
Here is an example of how to store the token expiration time as a session attribute in the /user endpoint:
// get the token from the query parameter
String token = request.getParameter("token");
// validate the token with the container app
boolean isValid = validateToken(token);
// if the token is valid, parse it and create the authentication object
if (isValid) {
// parse the token and get the expiration time
Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
Long expiration = claims.getExpiration().getTime();
// get the username and authorities from the claims
String username = claims.getSubject();
List<GrantedAuthority> authorities = getAuthoritiesFromClaims(claims);
// create the authentication object and set it to the security context
Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
// get the session and store the token expiration time as a session attribute
HttpSession session = request.getSession();
session.setAttribute("tokenExpiration", expiration);
}
I have a use case in which I have to perform authorization part only. JWT token is getting generated by another service. My service will only consume that token which will have data in it's custom claim. In my spring boot application, I just want to verify that token if it's valid or not before allowing users to access any API. In all answers I can see one Authentication object is created from current Security context. Is there any way in spring in which I don't need to write so much unnecessary code in Custom Filter and just write the logic to parse JWT token and if it is valid, then allow user to API or else send unauthorized response?
Auth0 Java JWT provides few methods to verify JWT token. I want to validate and parse using code like below
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).withIssuer(issuer).build();
DecodedJWT decoded = verifier.verify(token);
If you want to work with spring security you have to work with those things (filters, authentication object, context, etc.). You have to set an Authentication to be able to access protected resources. It's not complicated at all, you don't have to write "useless" code but just tell spring security what you want to do.
In your case, you just have to provide a custom filter that matches your criterias.
We can imagine something like :
#Component
public class TokenAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) req;
// Here, I get the token from authorization header, change if you get it from anywhere else
String token = httpRequest.getHeader("Authorization");
// here i'm just doing a dummy check. if my token equals "mysupersecrettoken" the authentication is validated
// change this test by yours (using Auth0 etc.)
if ("mysupersecrettoken".equals(token)) {
// dummy authentication object. You can set a real username / credentials / role based on the claims of your token if you want to.
UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken("username", "credentials",
Arrays.asList(new SimpleGrantedAuthority("ROLE")));
SecurityContextHolder.getContext().setAuthentication(user);
}
// call next filters, if authentication is not valid, no authentication will be set so user will not be authenticated
chain.doFilter(req, res);
}
}
It's not the best way to work with spring security but it's a kind of "minimal" way to achieve your usecase. You'll probably need to set some security context too but it's not a big thing.
Spring Security ships with out of the box support for JWT authentication, so you would not need to write any code aside from configuration. See OAuth 2.0 Resource Server JWT in the reference docs. You can enable it with the Spring Security DSL using:
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
Note that JWTs can be used outside of OAuth 2.0 with this support, though it may not be obvious at first that this is the case. You can check out the JWT Login sample to see how that is accomplished, but the main takeaway is that you need to provide an #Bean of type JwtDecoder that is created with a public key, such as:
#Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
Also see this Tanzu Tuesday talk for a deep dive on this subject.
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/
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.
I am developing a website using Spring Boot 1.5.7 as back-end and Angular 2 as front-end.
I am a newbie on both technologies, and it's the very first time I try to develop a website. So I am a bit confused on many things.
I have implemented user authentication through JWT.
When the user logins through credentials, the backend verifies them and then creates a JWT and returns it to the frontend: the token is added to the header this way:
Authorization - Bearer <jwt token>
In the frontend I check out if that key is in the post response. If it is there, I add it along with the username to the localStorage.
private authUrl = 'http://localhost:8080/login';
private headers = new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json'
});
constructor(private http: Http) { }
login(username: string, password: string): Observable<void> {
let loginRequest = JSON.stringify({ username: username, password: password });
return this.http.post(this.authUrl, loginRequest, { headers: this.headers })
.map((response: Response) => {
let token = response.headers.get('Authorization');
// If token is not null, empty or undefined.
if (token) {
localStorage.setItem('jwt', JSON.stringify({ username: username, token: token }));
}
});
}
When the user is logged in, everytime he accessed a protected resource, a token will be retrieved from the localStorage and sent back to the backend for validation.
The whole thing works. JWT are immune to CSRF, so I can disable that in the back-end,
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
...
but I have read (for instance, here) that there are vulnerabilities when using localStorage.
In particular the localStorage is accessible through JavaScript on the same domain, exposing to XSS attacks.
To resolve it seems I could use a JWT Cookie. As written in the link before, I could set the HttpOnly cookie flag to avoid cookies to be accessed through JavaScript.
But, with cookies I am now vulnerable to CRSF attacks.
Now, here, I have read Angular 2+ provides built-in, enabled by default, anti XSS.
So the question is. I should use localStorage and just use the embedded Angular 2 anti XSS feature, or is that not enough and then I should store JWTs on cookies to get protection against XSS attacks and then implement on top of it some kind of CRSF protection backend side using Spring Boot configuration?
Thank you
EDIT: the website is a sort of shopping cart. The user can view almost all
pages, but to pay he needs to log in.
Aniruddha Das approach is fine but client application will lost the token if user will refresh the browser because DOM will be reloaded with browser refresh and all memory data including token will be lost.
Now come back to your approach-
Local storage - CSRF attack is not possible using this approach and application will be stateless but it is prone to XSS attack. By default Angular do the output encoding to prevent the XSS attack but risk is still there with server side Angular template. To mitigate the XSS attack, you can reduce the token expiry time and encrypt it, if there is some sensitive information.
Cookie approach - HTTP cookie will mitigate the XSS attack but you have to implement the CSRF protection. You have to use the API gateway pattern to make the application stateless.
Both approach have prons/cons and you have to select depending on your application. If your application is related to financial domain then I would suggest cookie based approach.
In angular you can hold your token in service and use it when ever it required. Like pojo in java in angular you can create a angular service with getter and setter to hold the token. Provide that service to the module and it will available in all component and directives.
The token will be in memory while the application is open in the browser and will be be stored in the browser.
I would say use a observable/Subject type variable so that it will wait until the token is extracted from server and use that to do stuffs.