perwendel / spark

A simple expressive web framework for java. Spark has a kotlin DSL https://github.com/perwendel/spark-kotlin
Apache License 2.0
9.65k stars 1.56k forks source link

Best User Authentication Method? #225

Closed AlexAbraham1 closed 9 years ago

AlexAbraham1 commented 9 years ago

Hey! I am new to SparkJava and so far I am loving it!

I was wondering what the best way to add users to my site would be. I would want to authenticate them at every protected page (using a filter probably), and I would also want to store the user passwords in a SQL database using some form of encryption. I have a rudimentary version of user account control in my project so far but I am not sure where to go from here.

https://github.com/AlexAbraham1/SparkJavaTest

Any help would be greatly appreciated!

Thanks :-)

Serranya commented 9 years ago

You should use Hashing instead of Encryption. You can find good informations at: https://crackstation.net/hashing-security.htm the Author also provides a Java class to help with Hashing.

You then store the loginname and the password hash + salt in the DB. The login name must be unique.

You create an filter like this to only allow users which are logged in.

before("/protected/*", (request, response) -> {
     if (request.session(true).attribute("user") == null)
            halt(401, "Go Away!");
})

To login create an route like this

post("/login.html", (req, res) -> {
    User user = null;
    if ((user = login(requerst.queryMap().get("user", "login").value(), requerst.queryMap().get("user", "password").value())) != null) {
         request.session(true).attribute("user", user);
     }
});

In the login method you check the loginName and password against the database, and return an user object if the login attempt is successfull.

You can use the PasswordHash class from the link i posted.

PasswordHash.createHash("secr3t"); // To create an string containing Hash, salt, and iteration count. Store this string in the DB along with the login name.

PasswordHash.validatePassword("secr3t", "hashValueFromDB"); // To validate the entered password.
AlexAbraham1 commented 9 years ago

This is really helpful! Thanks!

AlexAbraham1 commented 9 years ago

Hey is there a way to put an array of routes as the first parameter of the before() method? I don't necessarily want to have /protected before every protected route i might want /profile, /account, and /orders to all be protected and it seems a bit much to create three separate filters that all do the same thing.

Thanks!

Serranya commented 9 years ago

There is no direct way to do so.

But you can do something like this

    setupProtectedFilters() {
        String[] protectedRoutes = new String[] {"/profile*", "/account*", "/orders*"};
        Filter f = (req, res) -> {
            User user = null;
            if ((user = login(requerst.queryMap().get("user", "login").value(), requerst.queryMap().get("user", "password").value())) != null) {
                request.session(true).attribute("user", user);
            }
        };

        for (String route : protectedRoutes) {
           before(route, f);
        }
    }
dessalines commented 9 years ago

For user authentication, read some articles about cookie-based authentication. I used this for my spark site, and its what the majority of websites use.

Basically it goes like this, once a user has sent a POST to your login page(and you authenticated their password), you generate a random string, called a session ID, as their cookie. use spark response.setcookie to do this. Make sure you set the expiration on the cookie to what is reasonable(maybe 1 hour to 1 day).

BTW, never store passwords, your password authentication should use a good crypto library, like jasypt.

Then, keep a database or in memory map of this sessionID string, to a username. Then when a request is made to spark, you can take this sessionID(using req.cookies) , and associate it internally with the correct user.

IMO, generating a completely random string for each login is the best security.

Here's a more detailed description of this: http://stackoverflow.com/questions/6631834/authenticate-system-without-sessions-only-cookies-is-this-reasonably-secure?rq=1

Serranya commented 9 years ago

Jetty generates an manages a sessionId cookie by itself. You solution should be used for a "keep me logged in" system.

AlexAbraham1 commented 9 years ago

Should there be a separate table in the MySQL database called "tokens" which contains a random string (the sessionID string) and the userID associated with it? Or should the sessionID be another field in the "users" table?

dessalines commented 9 years ago

The first. Think about it, if you tried to put this in the user table, how would you put potentially hundreds of session IDs in one user row? Make sure you study up on your SQL one-to-many relationships.

Also, here's how you encrypt and check a password with jasypt. Again, NEVER STORE USERS PASSWORDS. You'd run the .checkPassword when a user does a login POST.

StrongPasswordEncryptor passwordEncryptor = new StrongPasswordEncryptor();
String encryptedPassword = passwordEncryptor.encryptPassword(userPassword);
...
if (passwordEncryptor.checkPassword(inputPassword, encryptedPassword)) {
  // correct!
} else {
  // bad login!
}