In this step-by-step tutorial i will be explaining how to implement an form based authentication on your hi-framework based application, using apache shiro. You can find a copy of a working project cloning git@github.com:Americo-Chaquisse/Hi-Framework-and-Apache-Shiro.git,
This tutorial should not take more than 20 minutes to complete. After finishing, you will be able to implement a simple form based authentication (username and password) on your app, and secure url routes.
I am assuming that:
What is apache shiro? According with their website, "Apache Shiro⢠is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management".
In this tutorial we are going to build a simple web app that can be a starting point for secure your app. It will demonstrate user login, logout and how to get the user name from session. We will start considering that you already have a hi-framework app running.
Step 1: Enable Shiro
The "Apache Shiro Web" dependency must be added to your pom.xml
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
1a: Add a shiro.ini file to your project
We will create a file where we will store all configuration of the security framework
Create a new blank file on your WEB-INF folder named shiro.ini
1b: Enable Apache Shiro in web.xml
In this step we will be telling the application server to actually load, start a new Shiro environment and make it available to o our web application.
Add this on your WEB-INF/web.xml file:
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ShiroFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
<dispatcher>ERROR</dispatcher>
</filter-mapping>
Let's explain what we just did:
- The declaration starts the shiro environment
- The declaration define the master ShiroFilter. This guy will be filtering all requests and doing identity validation and access control operations before allowing a request to reach the app.
- The ensures that all request types are handled by the ShiroFilter. The request types included are define above, using the ... declaration.
Step 2: The MySQL Database
We will be querying the user data from mysql, so you need to ensure that you have the mysql java connector driver. If you don't, please add this on your pom.xml file:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
2a: Database Structure
Login to your database administrator interface (most of us are using phpmyadmin), select you database and create a table with the following structure:
CREATE TABLE `User` (
`username` varchar(255) NOT NULL PRIMARY KEY,
`email` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL
);
And then we will insert into table our testing user:
INSERT INTO `User` (`username`, `email`, `name`, `password`) VALUES
('admin', 'admin@web.co.mz', 'Administrator', '$shiro1$SHA-256$500000$uWz1xGFmUxrU7cnVFNnoZA==$Cq7FM9uAFbXqH/SuwapeuePUcsGodOrOqQGkbjsEpBQ=');
That '$shiro1$SHA-256$' long and complicated string is the encrypted version of 12345. Shiro will handle the password security for you. Just insert it and relax.
When times arrives that you need to encrypt the password to store to database, you will be able to do calling the encryptPassword method from the PasswordService interface.
Example:
import org.apache.shiro.authc.credential.DefaultPasswordService;
import org.apache.shiro.authc.credential.PasswordService;
public class MySecurityGenerator {
public static String encript(String string){
PasswordService passwordService = new DefaultPasswordService();
String encryptedPassword = passwordService.encryptPassword(string);
//do things like update the user password on database
return encryptedPassword;
}
}
Step 3: Configuring the shiro.ini file
Rebember the file we created on Step 1a? That file will be enough to store all configuration that we will need dealing with shiro.
Please append on shiro.ini the following content:
# this declares the start of the configuration
[main]
# here we will define who will be encrypting and comparing the user password from the stored on database
# We will be querying the password from database based on user name. What we are doing here is telling shiro
# who will encrypt the current password, and compare with the encrupted password on database.
# In this case is our DefaultPasswordService class that implements PasswordService
passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
passwordService = org.apache.shiro.authc.credential.DefaultPasswordService
passwordMatcher.passwordService = $passwordService
# here will be defining and configuring mysql as our datasource
ds = com.mysql.jdbc.jdbc2.optional.MysqlDataSource
ds.serverName = localhost
ds.user = user
ds.password = password
ds.databaseName = my_database
# here will be configuring and setting up our login query
jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.authenticationQuery = SELECT password FROM User WHERE username = ?
# Remember: we need to select only the password on our query, and then the "PasswordMatcher" will compare the result
# of database and the password from the login form
jdbcRealm.credentialsMatcher = $passwordMatcher
jdbcRealm.dataSource = $ds
securityManager.realms = $jdbcRealm
# The route of login form
# In the next step we will be creating a matching controller and action, according to Hi-Framework guidelines.
shiro.loginUrl = /user/login
# Where user will be redirected after successful login
# After redirect, the Hi-framework will redirect the user to the welcome url, defined on your hi.xml configuration file.
shiro.successUrl = /
[urls]
# here will be securing the urls
# Shiro need you to define what are your login and logout routes
/user/login = authc
/user/logout = logout
# allow only authenticated users to access this section. The "private" word correspond to our controller name
# you can put also: /** = authc - if all controllers require user to be authenticated
/private/** = authc
# anyone can access. put this otherwise the app wont work
# we are telling shiro that the webroot path, where we pull all our assets (pictures, fonts, JavaScript and css files) are
# accessible without requiring authentication
/webroot/** = anon
Step 4: Building the login form
First we are going to create the controller
@ApplicationScoped
public class User extends Controller{
public void login() throws MvcException {
this.callView();
}
}
And then we create the view. Here we will put just a simple html form.
webapp/views/user/login.html
<form action="" method="post">
<p>Username: <input type="text" name="username"></p>
<p>Password: <input type="text" name="password"></p>
<p><input type="submit"></p>
</form>
webapp/views/user/login.js
Hi.view(function($scope){
});
Note: We are putting the attribute action="" of the form empty because we already defined the login url on the shiro configuration file. The security framework will be listening all POST requests on that url and try to validate them, assuming that they correspond to an login attempt.
Step 5: The private area and logout button
I defined on my hi.xml configuration file the route private/index as my welcome url. If the user is not authenticated, he will be redirected to our loginUrl defined on shiro.ini configuration file.
So let's create our "Private" controller:
@ApplicationScoped
public class Private extends Controller{
public void index() throws MvcException {
//here we get the username from shiro
String currentUser = (String) SecurityUtils.getSubject().getPrincipal();
//and then we pass it to the view
Map<String, Object> map = new HashMap<String, Object>();
map.put("user", currentUser);
this.callView(map);
}
}
Le'ts create the view
webapp/views/private/index.html
<h1>Hi {{user}}</h1>
<h2>This is a private content. If you can see this, it means that you are authenticated!</h2>
<p><a href="user/logout" ajaxify>logout</a> </p>
webapp/views/private/index.js
Hi.view(function($scope){
});
Step 6: Run the application
If you did everything well until here, you application will be running perfectly.
Congratulations, your app is secured.
If you didn't, you can see the working source code here: https://github.com/Americo-Chaquisse/Hi-Framework-and-Apache-Shiro
Please test the login form using our default credentials:
username-> admin
password-> 12345
Apache Shiro have many uses. It allows you to implement social authentication, integrating facebook, google and others login, just by manipulating the shiro.ini file.
Using the MySQL directly is not the only way, you can build a custom validator and integrate it with JPA, even integrate it with third-party web-services.
What i did here is explain one of the implementations of security with Shiro, because after all, we need to start from somewhere.
See you next time!