Apache Shiro 是 Java 的一个安全框架。目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。
HP-shiro-spring是一个简单的基于Spring实现shiro的例子,进行用户的身份认证,实现基于role的授权。该项目是由MyEclipse进行构建的动态web项目。
项目具体实现层次结构如下:
- 首先定义shiro.ini,用来指定用户身份和凭据。
[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权限。这些权限在当前用户对系统资源进行访问的时候要用到。
2.定义配置文件
web.xml
org.springframework.web.context.ContextLoaderListener
org.apache.shiro.web.env.EnvironmentLoaderListener
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
REQUEST
FORWARD
INCLUDE
ERROR
springMvc
org.springframework.web.servlet.DispatcherServlet
1
springMvc
/
index.jsp
springMvc-servlet.xml
text/plain;charset=UTF-8
applicationContext.xml
/home/** = authc
3.然后定义ProtectedService.java来实现功能。
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 USERS = Arrays.asList("root","guest","gandhi","bose");
private static final List ROLES = Arrays.asList("root","guest","role1","role2");
@RequiresPermissions("user-roles:read")
public List getUsers() {
return USERS;
}
@RequiresPermissions("user-roles:read")
public List 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 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和成功登陆以后完成访问系统资源功能的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包括:
*
*
*
*
*
*
*
*/
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的配置文件进行一个梳理,了解它俩结合的具体配置方式。
该项目将用户名和密码简单的存放在文本文件中,而且是明文存储,以后需要迁移到数据库加密存储的形式。
参考开涛的博客进一步对shiro的功能进行探索。例如加密解密模块和session管理模块。
2016/06/22日更新:使用shiro+springmvc+mybatis实现的小例子,页面没有变化,添加了数据库的支持。
项目地址:https://github.com/lunabird/shiro-demo.git