Apache Shiro 是 Java 的一個安全框架。目前,使用 Apache Shiro 的人越來越多,因為它相當簡單,對比 Spring Security,可能沒有 Spring Security 做的功能強大,但是在實際工作時可能并不需要那么復雜的東西,所以使用小而簡單的 Shiro 就足夠了。
HP-shiro-spring是一個簡單的基于Spring實現(xiàn)shiro的例子,進行用戶的身份認證,實現(xiàn)基于role的授權。該項目是由MyEclipse進行構建的動態(tài)web項目。
項目具體實現(xiàn)層次結構如下:

- 首先定義shiro.ini,用來指定用戶身份和憑據(jù)。
[users]
root = secret, root
guest = guest, guest
gandhi = 12345, role1, role2
bose = 67890, role2
[roles]
root = *
role1 = filesystem:*,system:*
role2 = "calculator:add,subtract"
上面的shiro.ini文件定義了四個用戶,格式為“用戶名=密碼,角色”;每個角色擁有一些權限。
root擁有所有的權限,role1擁有filesystem以及system的所有權限,role2擁有calculator的add和substract權限。這些權限在當前用戶對系統(tǒng)資源進行訪問的時候要用到。
2.定義配置文件
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app 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/web-app_2_5.xsd"
version="2.5">
<!-- 作用:在啟動Web容器時,自動裝配Spring applicationContext.xml的配置信息 -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<!-- The filter-name matches name of a 'shiroFilter' bean inside applicationContext.xml -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!-- Make sure any request you want accessible to Shiro is filtered. catches all
requests. Usually this filter mapping is defined first (before all others) to
ensure that Shiro works in subsequent filters in the filter chain: -->
<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>
<servlet>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<!--
<servlet>
<servlet-name>login</servlet-name>
<servlet-class>com.hp.shiro.simplerbac.controller.LoginServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>logout</servlet-name>
<servlet-class>com.hp.shiro.simplerbac.controller.LogoutServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>home</servlet-name>
<servlet-class>com.hp.shiro.simplerbac.controller.HomeServlet</servlet-class>
</servlet>
-->
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!--
<servlet-mapping>
<servlet-name>login</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>logout</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>home</servlet-name>
<url-pattern>/home/*</url-pattern>
</servlet-mapping>
-->
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
springMvc-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<!-- is actually rather pointless. It declares explicit support
for annotation-driven MVC controllers (i.e.@RequestMapping,
@Controller, etc), even though support for those is the default
behaviour
當我們需要controller返回一個map的json對象時,可以設定<mvc:annotation-driven />
會自動注冊DefaultAnnotationHandlerMapping與AnnotationMethodHandlerAdapter
兩個bean,是spring MVC為@Controllers分發(fā)請求所必須的。并提供了:數(shù)據(jù)綁定支持,
@NumberFormatannotation支持,@DateTimeFormat支持,@Valid支持,讀寫XML
的支持(JAXB),讀寫JSON的支持(Jackson)-->
<mvc:annotation-driven />
<!-- 指定靜態(tài)資源的位置,例如js,css和圖片等文件,放到webroot文件夾下 -->
<mvc:resources mapping="/css/**" location="/css/" />
<mvc:default-servlet-handler />
<!-- 啟用spring mvc注解 例如 @Required, @Autowired, @PostConstruct-->
<context:annotation-config />
<!-- 設置使用注解的類所在的包名 -->
<context:component-scan base-package="com.hp.shiro.simplerbac.controller" />
<!--完成請求和注解pojo的映射。
當我們需要controller返回一個map的json對象時,可以設定<mvc:annotation-driven />,
同時設定<mvc:message-converters> 標簽,設定字符集和json處理類 -->
<bean
class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="messageConverters">
<list>
<bean
class="org.springframework.http.converter.StringHttpMessageConverter">
<property name="supportedMediaTypes">
<list>
<value>text/plain;charset=UTF-8</value>
</list>
</property>
</bean>
</list>
</property>
</bean>
<!-- <bean
class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" />
-->
<!-- 視圖解析器,對轉向頁面的路徑解析。prefix:前綴,suffix:后綴 -->
<bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/" />
<property name="suffix" value=".jsp" />
</bean>
<!-- <bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/> -->
</beans>
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="iniRealm" class="org.apache.shiro.realm.text.IniRealm">
<property name="resourcePath" value="classpath:/shiro.ini" />
</bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="iniRealm" />
</bean>
<!--Shiro 生命周期處理器-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" />
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="successUrl" value="/home/" />
<property name="filterChainDefinitions">
<value>
/home/** = authc
</value>
</property>
</bean>
</beans>
3.然后定義ProtectedService.java來實現(xiàn)功能。
package com.hp.shiro.simplerbac.bean;
import java.io.File;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import org.apache.shiro.authz.annotation.RequiresPermissions;
public class ProtectedService {
private static final List<String> USERS = Arrays.asList("root","guest","gandhi","bose");
private static final List<String> ROLES = Arrays.asList("root","guest","role1","role2");
@RequiresPermissions("user-roles:read")
public List<String> getUsers() {
return USERS;
}
@RequiresPermissions("user-roles:read")
public List<String> getRoles() {
return ROLES;
}
@RequiresPermissions("system:read:time")
public Date getSystemTime() {
return Calendar.getInstance().getTime();
}
@RequiresPermissions("calculator:add")
public int sum(int a, int b) {
return a+b;
}
@RequiresPermissions("calculator:subtract")
public int diff(int a, int b) {
return a-b;
}
@RequiresPermissions("filesystem:read:home")
public List<String> getHomeFiles() {
File homeDir = new File(System.getProperty("user.home"));
return Arrays.asList(homeDir.list());
}
public String getGreetingMessage(String name) {
return String.format("Hello %s",name);
}
}
使用了@RequiresPermissions()注解來表示每一個方法的需要的permission,沒有該注解的getGreetingMessage(String name)方法不要求任何權限。
4.定義兩個Controller,分別是登陸/登出頁面的controller和成功登陸以后完成訪問系統(tǒng)資源功能的controller。
package com.hp.shiro.simplerbac.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
@Controller
public class LoginController {
@RequestMapping(value="login", method=RequestMethod.GET)
public String login(HttpServletRequest req){
if (SecurityUtils.getSubject().isAuthenticated()) {
return "redirect:/home";
} else {
return "login";
}
}
@RequestMapping(value="login", method=RequestMethod.POST)
public String login(HttpServletRequest req,RedirectAttributes redirectAttributes,Model model) {
String username = req.getParameter("username");
String password = req.getParameter("password");
System.out.println("access to login");
System.out.println(username+","+password);
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
String errorMessage = null;
try {
SecurityUtils.getSubject().login(token);
} catch (AuthenticationException e) {
errorMessage = "user name doesn't exist or wrong password";
}
if(null == errorMessage) {
redirectAttributes.addAttribute("username", username);
return "redirect:/home";
} else {
System.out.println(errorMessage);
req.setAttribute("errorMessage",errorMessage);
return "login";
}
}
@RequestMapping(value="logout")
public String logout(HttpServletRequest req){
SecurityUtils.getSubject().logout();
return "redirect:/login";
}
}
package com.hp.shiro.simplerbac.controller;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.shiro.SecurityUtils;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.hp.shiro.simplerbac.bean.ProtectedService;
@Controller
public class HomeController {
@RequestMapping(value="home")
public String home(HttpServletRequest req, HttpServletResponse response, Model model){
// System.out.println("access to home controller.");
String username = (String)SecurityUtils.getSubject().getPrincipal();
// System.out.println("username:"+username);
model.addAttribute("username", username);
String method = req.getParameter("method");
// System.out.println("method:"+method);
/*
* method可能的值value包括:
* <input type="hidden" name="method" value="getUsers"/>
* <input type="hidden" name="method" value="getRoles"/>
* <input type="hidden" name="method" value="getSystemTime"/>
* <input type="hidden" name="method" value="sum"/>
* <input type="hidden" name="method" value="diff"/>
* <input type="hidden" name="method" value="getHomeFiles"/>
* <input type="hidden" name="method" value="getGreetingMessage"/>
*/
ProtectedService protectedService = new ProtectedService();
try {
if ("getUsers".equals(method)) {
model.addAttribute("users", protectedService.getUsers());
} else if ("getRoles".equals(method)) {
model.addAttribute("roles", protectedService.getRoles());
} else if ("getSystemTime".equals(method)) {
model.addAttribute("systemTime", protectedService.getSystemTime());
} else if ("sum".equals(method)) {
int a = Integer.parseInt(req.getParameter("a"));
int b = Integer.parseInt(req.getParameter("b"));
model.addAttribute("sum",protectedService.sum(a, b));
} else if ("diff".equals(method)) {
int a = Integer.parseInt(req.getParameter("a"));
int b = Integer.parseInt(req.getParameter("b"));
model.addAttribute("diff",protectedService.diff(a, b));
} else if ("getHomeFiles".equals(method)) {
model.addAttribute("homeFiles",protectedService.getHomeFiles());
} else if ("getGreetingMessage".equals(method)) {
String name = req.getParameter("name");
model.addAttribute("greetingMessage",protectedService.getGreetingMessage(name));
}
} catch(Exception e) {
model.addAttribute("errorMessage", e.getMessage());
}
return "home";
}
}
5.定義jsp文件和css樣式文件,具體代碼參見工程源碼HP-shiro-spring
總結:
該項目的主要目的是對spring的shiro的配置文件進行一個梳理,了解它倆結合的具體配置方式。
該項目將用戶名和密碼簡單的存放在文本文件中,而且是明文存儲,以后需要遷移到數(shù)據(jù)庫加密存儲的形式。
參考開濤的博客進一步對shiro的功能進行探索。例如加密解密模塊和session管理模塊。
2016/06/22日更新:使用shiro+springmvc+mybatis實現(xiàn)的小例子,頁面沒有變化,添加了數(shù)據(jù)庫的支持。
項目地址:https://github.com/lunabird/shiro-demo.git