How to integrate Apache Shiro with JavaEE
This tutorial will show the readers, how I was able to integrate Apache Shiro with JavaEE6. My technology Stack: JavaEE6, Glassfish3.1.2....
https://www.czetsuyatech.com/2012/10/java-apache-shiro-integration.html
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.
2.) Create a the shiro ini file: shiro.ini inside /src/main/resources folder:
3.) In my case I'm only interested with RequiresAuthenticated, RequiresRoles and RequiresPermissions, so I only implement them. How? Create an interceptor:
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).
4.) We need an interface for interceptor binding:
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.
6.) Sample Usage
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
3 comments
This is great!!!! Thanks so much for sharing - this is something many in the Shiro community have been waiting for. Thanks!
could provide a link to the source code.
I have trouble using the pages.
Feel free to download the code from:
http://code.google.com/p/czetsuya/
Note that I've used the Security Interceptor from Balus.
Post a Comment