spring-attic / spring-security-oauth

Support for adding OAuth1(a) and OAuth2 features (consumer and provider) for Spring web applications.
http://github.com/spring-projects/spring-security-oauth
Apache License 2.0
4.69k stars 4.05k forks source link

Using JWT authentication without OAuth setup #368

Open marceloverdijk opened 9 years ago

marceloverdijk commented 9 years ago

After discussing with @rwinch and @dsyer on various channel I created this issue to discuss further.

A blog post from Robbert van Waveren on stateless authentication (http://blog.jdriven.com/2014/10/stateless-spring-security-part-2-stateless-authentication/) triggered me to look into JWT authentication again. The described implementation provides basically two filters: 1) a login filter (acting as endpoint) and 2) a token authentication filter.

I personally hesitate to bring in OAuth when I only need JWT authentication. It feels confusing and honestly I do not want the additional complexity to use @EnableResourceServer etc. Maybe it's just a couple of lines of configuration but if feels like overkill.

A little bit of topic, but a good start :-) When using JSON to communicate between client and server also typical application/x-www-form-urlencoded form authentication is awkward. Besides using Basic HTTP authentication a JSON based username/password authentication would be nice to have out-of-the-spring-security-box. It would not be difficult to implement a JsonUsernamePasswordAuthenticationFilter. It would be similar like the already existing UsernamePasswordAuthenticationFilter. Just like the latter the username / password fields should be configurable. This might already help some people.

Extending on this JsonUsernamePasswordAuthenticationFilter we could create a JwtUsernamePasswordAuthenticationFilter serving as endpoint for the authentication. It would just the basis from JsonUsernamePasswordAuthenticationFilter to do the actual authentication, but it should set the JWT token in the response header. Next we would need a JwtTokenAuthenticationFilter which verifies the received token and sets the authentication. Both filters could be based on the implementation worked out in the mentioned blog post.

From @dsyer last blog post 'The internet, and people’s Spring backend projects, are littered with custom token-based authentication solutions.'. I think this is true. Just like the provided implementation in the blog post it's not that difficult to implement it but everybody does it on it's own way. It's a pity that compared to what Spring Security alrady offers in terms of form authentication, HTTP Basic authentication, CAS etc. out of the box, there is not much for JSON based authentication or JWT authentication.

patoi commented 9 years ago

+1

cemo commented 9 years ago

+1

8cells commented 9 years ago

+1

isarantidis commented 9 years ago

+1

dsyer commented 9 years ago

Can someone show me why it's so difficult to set up an OAuth2 provider with JWT tokens? If you want JWT tokens all the code is already here. Why is it so hard to just use it?

marceloverdijk commented 9 years ago

Maybe it's not difficult but 1) it feels unnatural to do so and 2) it can be easier.

Instead of using @EnableResourceServer and other setup I would like something much more easier like:

@Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .jwt()
                    .loginUrl(new AntPathRequestMatcher("/api/login", "POST"))
                    .secret("my-super-duper-secret")
                    .claimsProvider(new MyClaimsProvider)

What you typically want set to for JWT is the login url (can be defaulted to /login), the secret and optionally some claimsProvider implementation. A default implementation should be provided out of the box adding the username and roles to the claims. This way it would be very easy to setup JWT in Spring Security.

marceloverdijk commented 9 years ago

note that @jhipster in latest 2.1.0 release also delivered their custom token authentication yesterday: https://jhipster.github.io/security.html (alongside OAuth2 support btw)

Token-based authentication

Token-based authentication, like OAuth2, is a stateless security mechanism, so it's another good option if you want to scale on several different servers.

This authentication mechanism doesn't exist by default with Spring Security, so it's specific to JHipster. It is easier to use and implement than OAuth2, as it does not require a persistence mechanism, so it works on all SQL and NoSQL options.

This solution uses a custom token, which is a MD5 hash of your user name, the expiration date of the token, your password, and a secret key. This ensures that if someone steals your token, he should not be able to extract your username and password.

The secret key should be configured in your application.yml file, as the "authentication.xauth.secret" property.

Another reason to look into this to provide something out of the box?

javajack commented 9 years ago

Some one has tried to implement the JWT authentication with Spring Security.

https://github.com/lordofthejars/jwt-springsecurity

One Off the track Question ? As it seems JWT can carry additional data in claim set as well, Could we somehow load "grantedauthorities" from JWT after a successful verification ?

This would make our app truly stateless. As we are getting authentication as well as authorization data from same token.

marceloverdijk commented 9 years ago

@javajack yes the granted authorities should be part of JWT token indeed. How I see it a default implementation would just add the username, the granted authorities and configurable an expiry date. This would be a good out-of-the-box start using a DefaultClaimsProider implementation. If you then need to add other claims this should done via a custom ClaimsProvider implementation.

marceloverdijk commented 9 years ago

@dsyer is there a simple JWT sample (based on spring security oauth) with just having 1 server/backend which serves both the authentication endpoint, resources and html pages? Basically what I need is simple JWT token with username, authorities and a 1d expiry. If there is no sample like that available I will try to make one myself and publish it on github. Just to see what it takes to setup JWT with spring security oauth. (I have the feeling more people are looking for a simple setup like this). ^M

dsyer commented 9 years ago

@marceloverdijk this sample is pretty minimal: JWT Sample. Given that you only need one client (which could be autoconfigured in Spring Boot) for your use case, it would only be a few lines of code.

dsyer commented 9 years ago

@javajack as Marcel pointed out, a JWT can contain whatever you need it to. I noticed that the JHipster custom tokens require a UserDetailsService in addition to the token though (the token only provides the username), which is limiting, but sensible as well for a custom token implementation because it gets round the problem of what to do if you want to revoke a token. With OAuth2 there is a "refresh token", so you put the onus on the client to keep the access token live, and the authorization server can check the user account every time it is refreshed. If you start worrying about that kind of problem (which you should) then you will end up implementing something that is getting pretty close to OAuth2, at which point you might say "why didn't we just use OAuth2 in the first place?" Do you see my point?

marceloverdijk commented 9 years ago

Thanks @dsyer I will have a look at the sample this evening (after work).

marceloverdijk commented 9 years ago

@dsyer I created this repo (https://github.com/marceloverdijk/spring-security-jwt-sample) with a spring-security-jwt-sample based on the JWT Sample you mentioned. (PS it's not related to the other repo we discussed on gitter https://github.com/marceloverdijk/jwt-security).

Adding the @EnableResourceServer and AuthorizationServerConfigurerAdapter is indeed not much work. But honestly after this I'm completely lost, and have not a good idea what I really enabled now. Basically what I would expect from a JWT based authentication setup is:

dsyer commented 9 years ago

I have no idea in the example what the REST url for logging is, or how to change it

/oauth/token using the password grant probably, if you have a trusted client. You can change the path by setting a prefix in the AuthorizationServerEndpointsConfigurer.

After login I would expect the JWT token back

That's what the /token endpoint does.

In next requests I should add the JWT token again; which header name to use?

It's an OAuth2 bearer token (so Authorization: Bearer <token>). The documentation and the spec are quite clear and easy to find - that is one of the benefits of using OAuth2 over a crummy custom implementation.

primIl commented 9 years ago

Isn't the use case described in this issue conceptually different from the OAuth2 case? Here we have a password as an input and JWT token as an output, and JWT token is then used for accessing the resources. The JWT profile for OAuth 2 spec specifies a different case, where a JWT token is an input to the token service and the access token is an output, and access token is then used for accessing the resources.

greyby commented 9 years ago

I have the same requirement with @marceloverdijk . Just wanna login to get a JWT and every request add JWT in the header to authenticate. I think this is nothing related whit oauth . Using spring-security-oauth2 seems a bit weird.

@marceloverdijk I enter the url http://localhost:8080/oauth/token this is no login page but an exception <oauth><error_description>Full authentication is required to access this resource</error_description><error>unauthorized</error></oauth> response. I use postman post the username and password and got the same error message in json response. Did you login successful and get the JWT response ?

marceloverdijk commented 9 years ago

Honestly, I never get the oauth way to work... I ended up writing a custom Spring Security filter. I have an item on my TODO list to work this out details and create a github project for it. I just need to find the time.

greyby commented 9 years ago

@marceloverdijk Thank you for the feedback. I readed Robbert van Waveren's blog and I think his sample project is simple and clearly. I'll customer that with jjwt today.

dsyer commented 9 years ago

I don't really think Marcel tried very hard to use the existing support in Sprig OAuth. It might not be perfect but I would start here and make improvements rather than re-invent it all (my opinion only, please feel free to differ).

StealthyDev commented 9 years ago

I vote to have a simple JWT from spring security

StealthyDev commented 9 years ago

Thanks dsyer and https://github.com/royclarkson/spring-rest-service-oauth

mailuser41 commented 9 years ago

for me jwt and spring ouath2 together is bit confusing. Should be simple as Marcel pointed....

paweln1986 commented 9 years ago

+1 It will be good to have just simple JWT token base authentication without OAuth which is sometimes to complicated for small projects.

EdwardsBean commented 9 years ago

+1 I vote to have a simple JWT from spring security

eastwood commented 9 years ago

+1 This would make things a lot nicer. I've just recently got onto the JWT bandwagon and I can say that the community documentation and configuration around setting up an OAuth2 resource is polluted and difficult to comprehend as a relatively new Spring developer (even with Boot). Though, the resources of @dsyer are remarkably helpful and in-depth, there is definitely room for a simpler approach to implementing a client/server JWT service. @greyby, this article (http://technicalrex.com/2015/02/20/stateless-authentication-with-spring-security-and-jwt/) is very helpful :)

alchemistgo87 commented 9 years ago

Has anybody able to implement a clean solution of JWT Token with refresh Token? I have taken inspiration from this: http://blog.jdriven.com/2014/10/stateless-spring-security-part-2-stateless-authentication/
But not yet able to implement refresh token properly. Can anybody help please.

lpborges commented 9 years ago

+1

alegueleres commented 9 years ago

+1

gustavosoave commented 9 years ago

+1

brunosimioni commented 9 years ago

+1

osiegmar commented 9 years ago

I'm new to this JWT / OAuth topic, so I'm not an expert here. I've played around a bit with these technologies/frameworks for the last days and I think, that this token-refresh-thingy is a real though one. :-)

Here are my thoughts - maybe they'll help further discussing this topic.

@alchemistgo87 I think there's no clean refresh token implementation available, because there's no proper way to implement them in web applications (like AngularJS).

First of all, having a separate refresh token (in addition to a regular authentication token) makes no sense in web applications (like AngularJS) IMHO - the refresh token can be steal as easy/hard as the authentication token (using XSS). So I'd prefer to use only one token - with a reasonable expiration timestamp defined (either explicitly via the exp claim or implicitly via the iat claim).

A very simple implementation to refresh tokens would be to pass a (still valid) token to the server and get back a new one. Problem here is, that if someone gets your token, he can also refresh it endlessly. One solution to prevent this, that is often discussed, is a token blacklist where all tokens (their jti claim - an UUID for example) are blacklisted, so that they aren't valid anymore. These are typically tokens where the user has logged out or refreshed his token and thus got a new one. Tokens on this blacklist can be cleaned up as soon as their expiration timestamp is in the past. But this "solution" would imply that the token thief could refresh the stolen token, gets a new one and the original owner's token isn't valid any more (and the owner will be logged out). Now, the thief has his own "token thread" that can be refreshed endlessly - nobody would notice. To prevent this, you could save the ID (jti) of the most recent token in the database (user table) and overwrite this value on login, but this would cause that a user cannot be logged in from different clients (desktop, tablet, phone, ...) at the same time.

After thinking about all these issues, I think the best solution for a web application would be to combine the token authentication stuff with the concept of remember-me authentication. On logon, the user would get two tokens - an authentication token and a remember-me token. The authentication token has an expiration timestamp set to 1 day, the remember-me token to 14 days for example. There is no way to refresh these tokens (at least not the authentication token) and nothing special happens on logout (no blacklist). The authentication token could be stored in browsers sessionStorage, the remember-me token in browsers localStorage. So after 1 day (expiration time for the authentication token) or after browser restart (sessionStorage), the authentication token isn't valid anymore and the remember-me token has to be used. The user will be granted to access with limited permissions only (isAuthenticated() but not isAuthenticatedFully()). If he wants to gain access to more restricted areas of the application (isAuthenticatedFully()) he has to provide his credentials again and thus will get a new authentication token as well as a new remember-me token. A token thief still could steal an authentication token and/or the remember-me token. But the authentication token can only be used for 1 day (in this example) and the remember-me token only grants for limited access. Problems solved!?

Am I missing something here? Does OAuth already provide a ready-to-use solution for this and I haven't found it so far? :-)

vsbatista commented 9 years ago

+1

felixbarny commented 9 years ago

+1

kakawait commented 8 years ago

+1 and will help integration with other SSO technologies like CAS (supported by spring security). CAS inherits scaling issue from session oriented technologies. Providing oauth-less JWT mechanism can really help to turn CAS to stateless auth.

apuravchauhan commented 8 years ago

+1

anwfr commented 8 years ago

+1

Nasruddin commented 8 years ago

+1

diegofalcao commented 8 years ago

+1

fran0x commented 8 years ago

+1

codeamatic commented 8 years ago

+1

heimax commented 8 years ago

+1

sura2k commented 8 years ago

+1

dsyer commented 8 years ago

Locked because there are too many +1s (which add nothing to the debate). If you have a more constructive comment please use a back channel (e.g. email) and we can unlock if necessary.