Apache Shiro 是 Java 的一個(gè)安全框架德撬。目前银酬,使用 Apache Shiro 的人越來(lái)越多末贾,因?yàn)樗喈?dāng)簡(jiǎn)單揩晴,對(duì)比 Spring Security勋陪,可能沒(méi)有 Spring Security 做的功能強(qiáng)大,但是在實(shí)際工作時(shí)可能并不需要那么復(fù)雜的東西硫兰,所以使用小而簡(jiǎn)單的 Shiro 就足夠了诅愚。
HP-shiro-spring是一個(gè)簡(jiǎn)單的基于Spring實(shí)現(xiàn)shiro的例子,進(jìn)行用戶的身份認(rèn)證劫映,實(shí)現(xiàn)基于role的授權(quán)违孝。該項(xiàng)目是由MyEclipse進(jìn)行構(gòu)建的動(dòng)態(tài)web項(xiàng)目。
項(xiàng)目具體實(shí)現(xiàn)層次結(jié)構(gòu)如下:
- 首先定義shiro.ini泳赋,用來(lái)指定用戶身份和憑據(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文件定義了四個(gè)用戶,格式為“用戶名=密碼,角色”祖今;每個(gè)角色擁有一些權(quán)限校坑。
root擁有所有的權(quán)限,role1擁有filesystem以及system的所有權(quán)限千诬,role2擁有calculator的add和substract權(quán)限耍目。這些權(quán)限在當(dāng)前用戶對(duì)系統(tǒng)資源進(jìn)行訪問(wèn)的時(shí)候要用到。
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">
<!-- 作用:在啟動(dòng)Web容器時(shí)徐绑,自動(dòng)裝配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
當(dāng)我們需要controller返回一個(gè)map的json對(duì)象時(shí)制妄,可以設(shè)定<mvc:annotation-driven />
會(huì)自動(dòng)注冊(cè)DefaultAnnotationHandlerMapping與AnnotationMethodHandlerAdapter
兩個(gè)bean,是spring MVC為@Controllers分發(fā)請(qǐng)求所必須的。并提供了:數(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 />
<!-- 設(shè)置使用注解的類所在的包名 -->
<context:component-scan base-package="com.hp.shiro.simplerbac.controller" />
<!--完成請(qǐng)求和注解pojo的映射振愿。
當(dāng)我們需要controller返回一個(gè)map的json對(duì)象時(shí)捷犹,可以設(shè)定<mvc:annotation-driven />弛饭,
同時(shí)設(shè)定<mvc:message-converters> 標(biāo)簽,設(shè)定字符集和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" />
-->
<!-- 視圖解析器萍歉,對(duì)轉(zhuǎn)向頁(yè)面的路徑解析侣颂。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來(lái)實(shí)現(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()注解來(lái)表示每一個(gè)方法的需要的permission憔晒,沒(méi)有該注解的getGreetingMessage(String name)方法不要求任何權(quán)限。
4.定義兩個(gè)Controller蔑舞,分別是登陸/登出頁(yè)面的controller和成功登陸以后完成訪問(wèn)系統(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樣式文件,具體代碼參見(jiàn)工程源碼HP-shiro-spring
總結(jié):
該項(xiàng)目的主要目的是對(duì)spring的shiro的配置文件進(jìn)行一個(gè)梳理攻询,了解它倆結(jié)合的具體配置方式从撼。
該項(xiàng)目將用戶名和密碼簡(jiǎn)單的存放在文本文件中,而且是明文存儲(chǔ)钧栖,以后需要遷移到數(shù)據(jù)庫(kù)加密存儲(chǔ)的形式低零。
參考開(kāi)濤的博客進(jìn)一步對(duì)shiro的功能進(jìn)行探索。例如加密解密模塊和session管理模塊桐经。
2016/06/22日更新:使用shiro+springmvc+mybatis實(shí)現(xiàn)的小例子毁兆,頁(yè)面沒(méi)有變化,添加了數(shù)據(jù)庫(kù)的支持阴挣。
項(xiàng)目地址:https://github.com/lunabird/shiro-demo.git