jeudi 24 juillet 2014

Wisdom framework with apache shiro

Today, we'll see how to use Wisdom framework with apache shiro to manage users authentication.

First, what is Wisdom ?

Wisdom is a young but promising Java web framework, to develope quickcly modular and dynamic web applications. You can check Wisdom website for more informations.

Unfortunaly it does not include yet users management. That's why I decided to try to embed apache shiro inside my project.

The goal is to manage multiple users with different roles and to be able to authenticate them and display different template according to their roles.

In this example I create some users during shiro initialisation, but in the next article I'll use a real database.

The sample project is available here

Now let's take a look at the code.

First the ShiroActivator class used to initialize shiro

@Component
@Instantiate
public class ShiroActivator {
/**
* The famous {@link org.slf4j.Logger}
*/
private static final Logger logger = LoggerFactory.getLogger(ShiroActivator.class);
/**
* initialize shiro, by adding some roles and users
*/
@Validate
private void start() {
Ini ini = new Ini();
Ini.Section usr = ini.addSection(IniRealm.USERS_SECTION_NAME);
Ini.Section roles = ini.addSection(IniRealm.ROLES_SECTION_NAME);
roles.put("ADMIN", "*");
roles.put("GEST", "*");
usr.put("admin", "admin, ADMIN");
usr.put("guest", "guest, GUEST");
org.apache.shiro.mgt.SecurityManager securityManager = new DefaultSecurityManager(new IniRealm(ini));
SecurityUtils.setSecurityManager(securityManager);
}
@Invalidate
private void stop() {
SecurityUtils.setSecurityManager(null);
}
}
This is just basic stuff here, it create 2 roles and 2 users. Just with this, you're done ! you can use shiro now.

Then when you submit your form login this route will be called.

@Route(method = HttpMethod.POST, uri = "/login")
public Result login(@Body User user) {
Subject currentUser = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
try {
currentUser.login(token);
} catch (UnknownAccountException uae) {
context().flash().error("Unknown account");
return loginForm();
} catch (IncorrectCredentialsException ice) {
context().flash().error("Wrong password");
return loginForm();
} catch (LockedAccountException lae) {
context().flash().error("Account locked");
return loginForm();
} catch (AuthenticationException ae) {
context().flash().error("some error");
return loginForm();
}
return redirect("protected");
}
As you can see it's really simple, in 3 lines of codes you're authenticated. The exceptions here will display a flash message on the view in case of error like wrong password.

Once you're logged you'll be redirected to the protected page which displays different content whether you are admin or not.

But how is the route protected if you're not logged ?
Here I do a combination of wisdom and shiro features. This is my protected route :

@Authenticated("my-authenticator")
@Route(method = HttpMethod.GET, uri = "/protected")
public Result protectedArea() {
return ok(render(protectedView, "user", new UserHelper()));
}
As you can see there is an annotation @Authenticated. It comes from wisdom. You have to specify the name of your authenticator implementation. This is mine

@Component
@Provides
@Instantiate
public class MyAuthenticator implements Authenticator {
/**
* The famous {@link org.slf4j.Logger}
*/
private static final Logger logger = LoggerFactory.getLogger(MyAuthenticator.class);
@Override
public String getName() {
return "my-authenticator";
}
/**
* just check if the current user is logged in
* @param context
* @return
*/
@Override
public String getUserName(Context context) {
Subject currentUser = SecurityUtils.getSubject();
if (currentUser.isAuthenticated()) {
return currentUser.toString();
}
return null;
}
@Override
public Result onUnauthorized(Context context) {
return Results.ok("Your are not authenticated !");
}
}
So in the getUsername() method, I simply use shiro to retrieve the current user, if it returns null, so you're not logged and the onUnauthorized method will be called. Otherwise the controller route will be executed correctly.

Once you're logged, you'll have a different message if you're admin or guest. To achieve this I add some if statements in my template.

As you can see on the ProtectedController, on method return we have a new UserHelper in our parameters. that mean you will be able to call methods of this class on our template to check the role of the current user.
So your template will know which statement to use depending of the user role.
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8"/>
<title>Protected area</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
</head>
<body>
<p th:if="${user.isAdmin()}">I'm the admin so be nice with me</p>
<p th:if="${user.isGuest()}">I'm just a guest</p>
<form style="display: inline" action="#" method="get"
th:action="${#routes.route('sample.LoginController', 'logout')}">
<button class="btn btn-lg btn-primary btn-block" type="submit">Logout</button>
</form>
</body>
</html>
This is everything you need. I let you check by yourself on the sample project the logout method cause it's not really complicated.

Have fun with this great framework !