How to integrate Apache Shiro with JavaEE6
Tech-Today

How to integrate Apache Shiro with JavaEE6


This tutorial will show the readers, how I was able to integrate Apache Shiro with JavaEE6.

My technology Stack:
JavaEE6, Glassfish3.1.2.2, Apache Shiro1.2

Steps:
1.) Create a maven project and include the following dependency.

<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
<dependency>
<groupId>org.jboss.weld</groupId>
<artifactId>weld-logger</artifactId>
<version>1.0.0-CR2</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-endorsed-api</artifactId>
<version>6.0</version>
</dependency>
<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>1.1.EDR1.2</version>
</dependency>
</dependencies>

2.) Create a the shiro ini file: shiro.ini inside /src/main/resources folder:

#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

# INI configuration is very powerful and flexible, while still remaining succinct.
# Please http://shiro.apache.org/configuration.html and
# http://shiro.apache.org/web.html for more.

[main]
# listener = org.apache.shiro.config.event.LoggingBeanListener

shiro.loginUrl = /login.xhtml

[users]
# format: username = password, role1, role2, ..., roleN
root = secret,admin
guest = guest,guest
presidentskroob = 12345,president
darkhelmet = ludicrousspeed,darklord,schwartz
lonestarr = vespa,goodguy,schwartz

[roles]
# format: roleName = permission1, permission2, ..., permissionN
admin = *
schwartz = lightsaber:*
goodguy = winnebago:drive:eagle5

[urls]
# The /login.jsp is not restricted to authenticated users (otherwise no one could log in!), but
# the 'authc' filter must still be specified for it so it can process that url's
# login submissions. It is 'smart' enough to allow those requests through as specified by the
# shiro.loginUrl above.
/login.xhtml = authc
/logout = logout
/account/** = authc
/remoting/** = authc, roles[b2bClient], perms["remote:invoke:lan,wan"]

3.) In my case I'm only interested with RequiresAuthenticated, RequiresRoles and RequiresPermissions, so I only implement them. How? Create an interceptor:

package com.czetsuya.commons.web.security.shiro;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.authz.permission.WildcardPermission;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author Edward P. Legaspi
* @since Oct 10, 2012
*/
@Secured
@Interceptor
public class SecurityInterceptor {

@Inject
private Subject subject;

private Logger log = LoggerFactory.getLogger(SecurityInterceptor.class);

@AroundInvoke
public Object interceptGet(InvocationContext ctx) throws Exception {
log.info("Securing {}.{}({})", new Object[] { ctx.getClass().getName(),
ctx.getMethod(), ctx.getParameters() });

final Class<? extends Object> runtimeClass = ctx.getTarget().getClass();

// Check if user is authenticated
boolean requiresAuthentication = false;
try { // check method first
ctx.getMethod().getAnnotation(RequiresAuthentication.class);
requiresAuthentication = true;
} catch (NullPointerException e) {
requiresAuthentication = false;
}

if (!requiresAuthentication) { // check class level
try {
runtimeClass.getAnnotation(RequiresAuthentication.class);
requiresAuthentication = true;
} catch (NullPointerException e) {
requiresAuthentication = false;
}
}

if (requiresAuthentication) {
log.debug("[security] checking for authenticated user.");
try {
if (!subject.isAuthenticated()) {
throw new AuthorizationException();
}
} catch (Exception e) {
log.error("Access denied - {}: {}", e.getClass().getName(),
e.getMessage());
throw e;
}
}
/************************************************************/

// check if user has roles
boolean requiresRoles = false;
List<String> listOfRoles = null;

try { // check method first
RequiresRoles roles = ctx.getMethod().getAnnotation(
RequiresRoles.class);
listOfRoles = Arrays.asList(roles.value());
requiresRoles = true;
} catch (NullPointerException e) {
requiresRoles = false;
}

if (!requiresRoles || listOfRoles == null) { // check class
try {
RequiresRoles roles = runtimeClass
.getAnnotation(RequiresRoles.class);
listOfRoles = Arrays.asList(roles.value());
requiresRoles = true;
} catch (NullPointerException e) {
requiresRoles = false;
}
}

if (requiresRoles && listOfRoles != null) {
log.debug("[security] checking for roles.");
try {
boolean[] boolRoles = subject.hasRoles(listOfRoles);
boolean roleVerified = false;
for (boolean b : boolRoles) {
if (b) {
roleVerified = true;
break;
}
}
if (!roleVerified) {
throw new AuthorizationException(
"Access denied. User doesn't have enough privilege Roles:"
+ listOfRoles + " to access this page.");
}
} catch (Exception e) {
log.error("Access denied - {}: {}", e.getClass().getName(),
e.getMessage());
throw e;
}
}
/************************************************************/

// and lastly check for permissions
boolean requiresPermissions = false;
List<String> listOfPermissionsString = null;

try { // check method first
RequiresPermissions permissions = ctx.getMethod().getAnnotation(
RequiresPermissions.class);
listOfPermissionsString = Arrays.asList(permissions.value());
requiresPermissions = true;
} catch (NullPointerException e) {
requiresPermissions = false;
}

if (!requiresPermissions || listOfPermissionsString == null) {
// check class
try {
RequiresPermissions permissions = runtimeClass
.getAnnotation(RequiresPermissions.class);
listOfPermissionsString = Arrays.asList(permissions.value());
requiresPermissions = true;
} catch (NullPointerException e) {
requiresPermissions = false;
}
}

if (requiresPermissions && listOfPermissionsString != null) {
log.debug("[security] checking for permissions.");
List<Permission> listOfPermissions = new ArrayList<Permission>();
for (String p : listOfPermissionsString) {
listOfPermissions.add((Permission) new WildcardPermission(p));
}
try {
boolean[] boolPermissions = subject
.isPermitted(listOfPermissions);
boolean permitted = false;
for (boolean b : boolPermissions) {
if (b) {
permitted = true;
break;
}
}
if (!permitted) {
throw new AuthorizationException(
"Access denied. User doesn't have enough privilege Permissions:"
+ listOfRoles + " to access this page.");
}
} catch (Exception e) {
log.error("Access denied - {}: {}", e.getClass().getName(),
e.getMessage());
throw e;
}
}

return ctx.proceed();
}
}

3.2) We need JavaEE6 to see the interceptor by including it in beans.xml (which normally resides in /src/main/resources/META-INF of the project where you define the interceptor class).

<beans xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<interceptors>
<class>com.czetsuya.commons.web.security.shiro.SecurityInterceptor</class>
</interceptors>
</beans>

4.) We need an interface for interceptor binding:

package com.czetsuya.commons.web.security.shiro;

import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import javax.interceptor.InterceptorBinding;

/**
* @author Edward P. Legaspi
* @since Oct 10, 2012
*/
@Inherited
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@InterceptorBinding
public @interface Secured {
}

5.) Then we need to create a Singleton class where we will instantiate the SecurityManager by reading the shiro.ini file and produce the Subject and SecurityManager, so that we can inject them later if we want.

package com.czetsuya.commons.web.security.shiro;

import javax.annotation.PostConstruct;
import javax.enterprise.inject.Produces;
import javax.inject.Named;
import javax.inject.Singleton;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* @author Edward P. Legaspi
* @since Oct 10, 2012 Produces an instance of Shiro's subject so that it can be
* injected.
*/
@Singleton
public class SecurityProducer {
Logger logger = LoggerFactory.getLogger(SecurityProducer.class);
private SecurityManager securityManager;

@PostConstruct
public void init() {
final String iniFile = "classpath:shiro.ini";
logger.info("Initializing Shiro INI SecurityManager using " + iniFile);
securityManager = new IniSecurityManagerFactory(iniFile).getInstance();
SecurityUtils.setSecurityManager(securityManager);
}

@Produces
@Named("securityManager")
public SecurityManager getSecurityManager() {
return securityManager;
}

@Produces
public Subject getSubject() {
return SecurityUtils.getSubject();
}
}

6.) Sample Usage
package com.czetsuya.mbeans;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;

import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;

import com.czetsuya.commons.web.security.shiro.Secured;

/**
* @author Edward P. Legaspi
* @since Oct 10, 2012
*/
@LocalBean
@Named
@Stateless
public class LoginBean {
@Inject
private Subject subject;

@Inject
private Logger log;

private String username;
private String password;

public String login() {
if (subject.isAuthenticated()) {
log.debug("[czetsuya-ejbs] active subject={}, user={}", subject,
subject.getPrincipal());
return redirect();
} else {
log.debug(
"[czetsuya-ejbs] login to the system with user={}, password={}",
getUsername(), getPassword());
AuthenticationToken token = new UsernamePasswordToken(
getUsername(), getPassword());
try {
subject.login(token);

return redirect();
} catch (Exception e) {
log.error("[czetsuya-ejbs] error login {}", e);
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage("Error login"));
}
}

return "home.xhtml";
}

public String logout() {
log.debug("[czetsuya-ejbs] logout");
if (subject.isAuthenticated()) {
subject.logout();
}

FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage("Logout Ok"));

return "login.xhtml";
}

private String redirect() {
log.debug("[czetsuya-ejbs] redirect");
if (subject.hasRole("admin")) {
return "admin.xhtml";
} else if (subject.hasRole("schwartz")) {
return "schwartz.xhtml";
} else if (subject.hasRole("goodguy")) {
return "goodguy.xhtml";
}
return "home.xhtml";
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

@Secured
@RequiresAuthentication
public String requiresAuthentication() {
return "";
}

@Secured
@RequiresRoles({ "admin" })
public void requiresRolesMerchant() {
log.debug("admin");
}

@Secured
@RequiresRoles({ "schwartz" })
public void requiresRolesSupport() {
log.debug("schwartz");
}

@Secured
@RequiresPermissions({ "lightsaber" })
public void requiresPermissionlightsaber() {
log.debug("lightsaber.action");
}

@Secured
@RequiresPermissions({ "winnebago" })
public void requiresPermissionSupport() {
log.debug("winnebago.action");
}

@Secured
@RequiresPermissions({ "winnebago", "lightsaber" })
public void requiresPermissionlightsaberOrwinnebago() {
log.debug("winnebago+lightsaber.action");
}
}
Related:
Seam Security: http://czetsuya-tech.blogspot.com/2013/06/how-to-setup-seam3-security-in-jboss-7.html




- Social Login Using Rest Fb
This is an implementation tutorial on how we can use REST FB to enable facebook social login on our web application. Basically, it's a project created from javaee7-war template. To run this app you need to set up a Facebook application with callback...

- Jax-rs 2.0 Security Tutorial In Javaee And Jboss
This tutorial will summarize how the author was able to call a secured rest webservice using resteasy. We will not go into detail on how we build the entire project since the code is already pushed at github. Basically we will just note down the most...

- How To Setup Seam3-security In Jboss 7
Recently, I've done some research on several Java Security Framework that can perform authentication, authorization and cryptography. I've worked with Apache Shiro, it's really good and complete but I've found several problems like there's...

- Using Shiro's Native And The Default Http Session
Currently I've been working on a project that uses shiro for authentication and authorization. I can say that aside from the fact that it doesn't support jsf, it's a very useful tool. This page contains codes that will help you in configuring...

- How To Load Property File From Glassfish Config's Folder
In seam you can define a component class in components.xml that will load the properties from the JBOSS_CONFIG folder. Add the ff lines: <component name="paramBean" class="com.ipil.PropertyBean" scope="application" auto-create="true" startup="true">...



Tech-Today








.