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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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); | |
} | |
} |
Then when you submit your form login this route will be called.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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"); | |
} |
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 :
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@Authenticated("my-authenticator") | |
@Route(method = HttpMethod.GET, uri = "/protected") | |
public Result protectedArea() { | |
return ok(render(protectedView, "user", new UserHelper())); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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 !"); | |
} | |
} |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
Have fun with this great framework !