-
Notifications
You must be signed in to change notification settings - Fork 5
Design Patterns for Authentication
Apply design patterns to how your application handles logins so that the authentication mechanism can change without affecting your code.
To do this exercise, create a branch or fork of your eXceedVote project. This is an experiment, so don't work on the master branch.
Most ExceedVote projects have a route to handle logins (from a view) something like this:
GET /login controllers.Application.login()
POST /authenticate controllers.Application.authenticate()
When the user submits (POSTs) the login view it invokes Application.authenticate() which does this:
public static Result authenticate( ) {
Form<Login> loginForm = Form.form(Login.class);
loginForm = loginForm.bindFromRequest();
if (loginForm.hasErrors()) {
return badRequest(views.html.login.render(loginForm));
}
Login data = loginForm.get();
// authenticate the username using password
// if this fails it returns null
User user = User.authenticate( data.username, data.password);
if (user == null) {
flash("error", "Username or password incorrect.");
return badRequest(views.login.render(loginForm));
}
// success! Create a session for this user.
. . .
}In this code, the responsibility for authenticating users is handled by the User class.
Since its in the User class, its difficult to change without modifying the code for User.
- To enable the implementation of authentication to change,
let's reassign the responsibility to a new class named
Authenticator:
public class Authenticator {
public User authenticate(String username, String password);
. . .
}QUESTION: What design principles (GRASP or other) are being used here?
- Just reassigning the job to
Authenticatordoesn't help much. We need a way to vary the authenticator. If we usenewto create an Authenticator, such as:
// in Application.authenticate (controller)...
Authenticator auth = new Authenticator();
User user = auth.authenticate(username, password);
if (user == null) ...then we'd have to modify the code in the authenticate method in order to change authentication.
A good mechanism to manage object creation is the Factory Method. The usage in our Application controller will look something like this:
User user = Authenticator.getInstance().authenticate( username, password);In this case Authenticator.getInstance() is the factory method. It doesn't necessarily create an Authenticator, but can create a subclass of Authenticator instead.
For design practice, let's define 2 Authenticator subclasses:
DatabaseAuthenticator - authenticate using username and password from database table.
ImapAuthenticator - authenticate using IMAP (such as KU Mail server)
EXERCISE:
- Draw a class diagram of this.
- We only need one Authenticator in the application. How would you ensure this?
QUESTION:
Calling Authenticator.getInstance().authenticate(name,passwd) adds an extra layer of method calls,
compared to User.authenticate(name,passwd). What GRASP Principle is this?
- Write the code for Authenticator and DatabaseAuthenticator. In Authenticator you can always create a Database authenticator (we'll fix that later):
public abstract class Authenticator {
public static Authenticator getInstance() {
//TODO make this a singleton
return new DatabaseAuthenticator();
}
/** authenticate user */
public abstract User authenticate(String username, String password);
}- This code always creates a DatabaseAuthenticator, which is not very flexible. How can you apply Protected Variations so you can easily change the type of concrete Authenticator to something else? One solution is to use a properties file to specify the name of the actual Authenticator class, and dynamically load this class. This is very common in Java applications. To change the app, you only need to change the properties file.
In Play, the conf/application.conf file is a properties file. You can add your own properties there.
For safety, its a good idea to prefix your property names with something specific to your application.
For example:
# this is conf/application.conf
exceed.authenticator=util.DatabaseAuthentictor
5. To read a string-valued property in Play, use:
`String value = play.Play.application().configuration().getString("key name");`
In this case:
`String authclass = play.Play.application().configuration().getString("exceed.authenticator");
Now you can dynamically load and create an instance of this class:
```java
Authenticator auth = (Authentictor) Class.forname( authclass ).newInstance( );
you should catch exceptions, log them, and then return some default authenticator.
- Test this by defining a
NoPasswordAuthenticatorthat checks only the username, not the password.
To access a configuration proerty value from your application conf/application.conf file, you can write:
String value = play.Play.application().configuration().getString("key name");For example, if you define a property:
# in application.conf
exceed.authenticator=util.DatabaseAuthenticator
then you can write:
String authclass = play.Play.application().configuration().getString("exceed.authenticator");