Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任
何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
Shrio的主要功能:
Shiro的基本架构:
Shiro有三个核心的概念:Subject、SecurityManager和Realms。
Subject:Subject实质上是一个当前执行用户的特定的安全“视图”,开发者所写的应用代码就通过Subject与Shiro框架进行交互。所有Subject实例都必须绑定到一个SecurityManager上,当使用一个Subject实例时,Subject实例会和SecurityManager进行交互,完成相应操作。
SecurityManager:SecurityManager是Shiro的核心部分,作为一种“保护伞”对象来协调内部安全组件共同构成一个对象图。开发人员并不直接操作SecurityManager,而是通过Subject来操作SecurityManager来完成各种安全相关操作。
Realms:Realms担当Shiro和应用程序的安全数据之间的“桥梁”或“连接器”。从本质来讲,Realm是一个特定安全的DAO,Realm中封装了数据操作的模块和用户自定义的认证匹配过程。SecurityManager可能配置多个Realms,但至少要有一个。
注:该示例基于一个Maven管理的SSH项目
1.在Maven中添加Shiro依赖
org.apache.shiro
shiro-all
1.2.3
注:Shiro官方现在不推荐这种用法,因为可能会导致Maven运行错误,详见
2.在web.xml添加核心过滤器,且必须放在Struts2核心过滤器前面
shiroFilter
org.springframework.web.filter.DelegatingFilterProxy
targetFilterLifecycle
true
shiroFilter
/*
3.在Spring配置文件中添加如下代码,且放在事务管理器之前
4.在Spring配置文件中配置Shiro,建议单独出一个applicationContext-shiro.xml进行配置(编写完成后记得在总的配置文件中引入该配置文件)
applicationContext-shiro.xml
/index.jsp* = anon
/home* = anon
/sysadmin/login/login.jsp* = anon
/sysadmin/login/logout.jsp* = anon
/login* = anon
/logout* = anon
/components/** = anon
/css/** = anon
/images/** = anon
/js/** = anon
/make/** = anon
/skin/** = anon
/stat/** = anon
/ufiles/** = anon
/validator/** = anon
/resource/** = anon
/** = authc
/*.* = authc
在这里配置了核心的SecurityManager,SecurityManager中注入了realm和cacheManager,因此需要配置realm和cacheManager,Realm是自定义的,其中注入了userService,用来进行查询数据库数据的相关操作,还注入了credentialsMatcher属性,这个是开发者自定义的比较器。配置CacheManager时,使用了ehcache,在最后也进行了配置,并且编写了ehcache的配置文件,这些操作在下述步骤进行。
在shiroFilter的配置中配置了要过滤的目录,其中一个*号表示匹配当前目录后的参数,两个*号表示匹配该目录及其子目录及参数。等号后面是Shiro各类过滤器的简称,anon表示匿名过滤器,表示可以匿名访问这些资源,authc表示需要验证用户身份才能访问,还有8种过滤器,在此就不再详述了。
5.Ehcache的配置文件
ehcache-shiro.xml
6.编写自定义的credentialsMatcher
DemoCredentialsMatcher.java
public class DemoCredentialsMatcher extends SimpleCredentialsMatcher {
/**
* 密码比较的规则
* token:用户在界面输入的用户名和密码
* info: 从数据库中得到的加密数据
*/
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
// 获取用户输入的密码,并加密
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
String md5Pwd = Encrypt.md5(new String(upToken.getPassword()), upToken.getUsername());
// 获取数据库中的加密密码
String pwd = (String) info.getCredentials();
// 返回比较结果
return this.equals(md5Pwd, pwd);
}
}
Encrypt是一个进行MD5加密的工具类
Encrypt.java
public class Encrypt {
public static String md5(String password, String salt) {
return new Md5Hash(password, salt, 2).toString();
}
}
7.编写自定义的realm
AuthRealm.java
public class AuthRealm extends AuthorizingRealm {
private IUserService userService;
public void setUserService(IUserService userService) {
this.userService = userService;
}
/**
* 授权,当jsp页面遇到shiro标签会执行该方法
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
System.out.println("授权");
User user = (User) pc.fromRealm(this.getName()).iterator().next(); // 根据realm名字找到对应的realm
List permissions = new ArrayList();
Set roles = user.getRoles();
for (Role role : roles) {
Set modules = role.getModules();
for (Module module : modules) {
permissions.add(module.getName());
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissions); // 添加用户的权限
return info;
}
/**
* 认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("认证");
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
List list = userService.find("from User u where u.userName = ?", User.class, new String[]{upToken.getUsername()});
if (list != null && list.size() > 0) {
User user = list.get(0);
AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
return info;
}
return null; // 返回null抛出异常
}
}
8.登录操作
try {
// 1、得到Subject
Subject subject = SecurityUtils.getSubject();
// 2、调用登录方法---AuthRealm#doGetAuthenticationInfo()
subject.login(new UsernamePasswordToken(username, password));
// 3、登录成功
User user = (User) subject.getPrincipal();
// 4、放入session
session.put(SysConstant.CURRENT_USER_INFO, user);
} catch (Exception e) {
e.printStackTrace();
request.put("errorInfo", "用户名或密码错误!");
return "login";
}
return SUCCESS;
在Shiro框架中,如果登陆失败,则会抛出异常,所有在使用Subject的代码外要使用try-catch,当捕获异常,表示用户身份验证失败。