Shiro入门学习二
Apache Shiro是Java的一个安全(权限)框架
Shiro相比于Spring security更加便捷,不仅使用Java Se环境,也可以用在Java EE环境
Shiro的作用:
- 认证 - 例如登录
- 授权 - 权限管理
- 加密 - 例如密码可以用Shiro进行加密
- 会话管理 -
- web继承
- 缓存
Shiro功能简介
- Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
- Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
- Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
- Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
- Web Support:Web支持,可以非常容易的集成到Web环境;
- Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
- Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
- Testing:提供测试支持;
- Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
- Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。
Shiro的架构(对使用者)
- Subject - 代表当前的用户
- Shiro SecurityManager - 安全管理器
- Realm - Shiro从Realm获取安全数据(如用户、角色、权限)
Shiro环境的搭建
JavaSE环境搭建Shiro框架
1.所需要的jar包
- apache-log4j-1.2.15.jar
- shiro-all-1.3.2.jar
- slf4j-log4j12-1.6.1.jar
- slf4j.api-1.6.1.jar
2.配置文件
可以从Shiro源码文件中的sample中拷贝过来
shiro.ini
文件:存储数据,如用户名、密码、角色、权限log4j.properties
shiro.ini
文件内容及说明:
1.shiro中user存储数据格式
用户名=密码, 角色1, 角色2......
一个用户名可以有多个角色,如:
[users]
# user 'root' with password 'secret' and the 'admin' role
# 用户 root 密码 secret 角色 admin
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
# 用户 guest 密码 guest 角色 guest
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
# 用户 presidentskroob 密码 12345 角色 president
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
# 用户 darkhelmet 密码 ludicrousspeed 角色 darklord schwartz
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
# 用户 lonestarr 密码 vespa 角色 goodguy schwartz
lonestarr = vespa, goodguy, schwartz
2.一个角色可能会对个多个权限
[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
# admin 具有所有权限
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with
# license plate 'eagle5' (instance specific id)
goodguy = winnebago:drive:eagle5
3.测试获取session对象
如下的HelloWorld
类:
package com.shiro.bean;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
private static final Logger log = LoggerFactory.getLogger(HelloWorld.class);
public static void main(String[] args) {
log.info("正在测试log4j......");
//1.获取安全管理器
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
//2.设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//3.获取Subject对象,为即将登录的用户
Subject currentUser = SecurityUtils.getSubject();
//4.获取session
Session session = currentUser.getSession();
session.setAttribute("name", "wz");
String value = (String) session.getAttribute("name");
if (value != null ) {
log.info("Shiro获取session会话对象当中指定的值:" + value);
}
}
}
运行后,在控制台正确的输出了结果
Shiro认证过程
Shiro认证过程可参阅官网Authentication Sequence,如下图所示:
1.判断currentUser
是否登录过,currentUser.isAuthenticated() == false
表示没有登录过
2.如果没有登录过,需要给当前的subject设置用户名和密码,使用UsernamePasswordToken
3.登录,可能会抛出如下的异常
UnknownAccountException
- 错误的帐号IncorrectCredentialsException
- 错误的凭证LockedAccountException
- 锁定的帐号DisabledAccountException
- 禁用的帐号ExcessiveAttemptsException
- 登录失败次数过多ExpiredCredentialsException
- 过期的凭证
代码如下:
if (currentUser.isAuthenticated() == false) {
UsernamePasswordToken token = new UsernamePasswordToken("root", "secret");
token.setRememberMe(true);//记住我
try {
currentUser.login(token);
log.info("用户名和密码正确");
} catch (UnknownAccountException e) {
log.info("账户不存在");
}catch (IncorrectCredentialsException e) {
log.info("密码错误");
}catch (LockedAccountException e) {
log.info("用户已经锁死");
}catch (AuthenticationException e) {
log.info("认证异常");
}
}
Shiro角色、权限认证过程
1.判断某个用户拥有指定的角色
if (currentUser.hasRole("admin")) {
log.info("拥有指定的角色");
}else{
log.info("不拥有指定的角色");
}
2.判断某个用户拥有指定的权限
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("拥有指定的权限");
}else{
log.info("不拥有指定的权限");
}
Shiro Web-App 搭建
参考官网Securing Web Applications with Apache Shiro,使用maven来搭建一个Web-App的shiro项目,可以在Github上fork一个tutorial project,看参考pom.xml
中内容
主要步骤是:
1.在src/main/webapp/WEB-INF/shiro.ini
目录下创建shiro.ini
文件
2.在web.xml
中启用shiro
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ShiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<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>
默认过滤器
默认过滤器的介绍参考Default Filters
Shiro Spring、SpringMVC框架搭建
1.在web.xml
中配置shiroFilter
<!-- Shiro Filter is defined in the spring application context: -->
<!-- Filter的代理类 -->
<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>
Shiro提供了与Web集成的支持,其通过一个ShiroFilter
入口来拦截需要安全控制的URL,然后进行相应的控制
ShiroFilter
类似于Strut2
/SpringMVC
这种web框架的前端控制器,是安全控制的入口点,其负责读取配置(如ini配置文件),然后判断URL是否需要登录/权限等工作
ShiroFilter
如何控制哪些web资源是需要登录才能访问,哪些不需要?
ShiroFilter
提供了2中方式:
filterChainDefinitions
过滤链的定义- 如果是没有经过认证,则会自动走到
loginUrl
DelegatingFilterProxy
的作用是自动到Spring容器查找名为shiroFilter
(filter-name)的bean,并把Filter的操作委托给它。
DelegatingFilterProxy
其源码定义如下:
public class DelegatingFilterProxy extends GenericFilterBean {
private String contextAttribute;
private WebApplicationContext webApplicationContext;
private String targetBeanName;
private boolean targetFilterLifecycle = false;
private volatile Filter delegate;
......
其中:
targetBeanName
- 存储即将获取的shiro核心过滤器的id值,如果没有指定这个targetBeanName,则自动获取filter的名字delegate
- 代表获取Spring的shiro的核心过滤器
2.Spring配置文件中shiro的配置
Spring配置文件applicationContext.xml
中shiro的配置,可在shiro的源码文件中的sample例子找到,如下:
这里推荐大家参考在 Web 项目中应用 Apache Shiro
applicationContext.xml
中shiro的配置内容如下:
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 缓存管理器 -->
<property name="cacheManager" ref="cacheManager" />
<!-- 处理数据 -->
<property name="realm" ref="jdbcRealm" />
</bean>
<!-- 可以使用的缓存技术,这里默认使用EHCACH -->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<!-- 缓存的位置 -->
<property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>
<!-- Realm -->
<bean id="jdbcRealm" class="com.shiro.bean.ShiroRealm">
</bean>
<!-- 必须要有这样的实例,用来管理spring容器当中的shiro常见的对象 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<!-- 启用shiro注解 -->
<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="secureRemoteInvocationExecutor" class="org.apache.shiro.spring.remoting.SecureRemoteInvocationExecutor">
<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="/logon.jsp" />
<!-- 认证登录成功后跳转的页面 -->
<property name="successUrl" value="/s/index" />
<!-- 未授权的页面 -->
<property name="unauthorizedUrl" value="/abc.jsp" />
<!-- 过滤链的定义 -->
<property name="filterChainDefinitions">
<value>
/logon.jsp = anon
/** = authc
</value>
</property>
</bean>
一些说明:
1.securityManager
中 realm
属性,配置为我们自己实现的 Realm
2.shiroFilter
这个bean
的id
必须要与web.xml
文件中的filter-name
一致
loginUrl
- 登录页面地址successUrl
- 为登录成功页面地址(如果首先访问受保护 URL 登录成功,则跳转到实际访问页面)unauthorizedUrl
- 认证未通过访问的页面
3.shiroFilter
中 filterChainDefinitions
属性
anon
- 表示匿名访问(不需要认证与授权)authc
- 表示需要认证
这样配置后,假设我现在访问http://localhost:8080/ShiroDemo02/success.jsp
页面,则会自动跳转到http://localhost:8080/ShiroDemo02/logon.jsp
,其响应头为:
其中302
表示重定向
认证资源格式及URL匹配模型
[urls]
部分的配置,其格式是 url=拦截器[参数],拦截器[参数]
如果当前请求的url匹配[urls]
部分的某个url模式,将会执行其配置的拦截器
URL匹配模式
url模式使用Ant风格模式
Ant路劲通配符支持?
、*
、**
,注意通配符匹配不包括目录分隔符”/”
?
- 匹配一个字符,如/admin?
将匹配/admin1
,但不匹配/admin
或/admin/
*
- 匹配零个或多个字符串,如/admin
将匹配/admin
、/admin123
,但不匹配/admin/1
**
- 匹配路劲中的零个或多个路径,如/admin/**
将匹配/admin/a
或/admin/a/b
URL匹配顺序
URL
权限采取第一次匹配优先的方式,即从头开始使用第一个匹配的url模式对应的拦截器链
如:
- /bb/**= filter1
- /bb/aa = filter2
- /** = filter3
如果请求的url是"/bb/aa"
,因为按照声明顺序进行匹配,那么将使用filter1进行拦截
JSP标签
JSP标签参考JSP / GSP Tag Library,也可参阅第九章 JSP标签——《跟我学Shiro》
导入标签库
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
1.principal标签
<shiro: principal/>
显示用户身份信息,默认调用Subject.getPrincipal()
获取,即Primary Principal。
2.guest标签
用户没有身份验证时显示相应信息,即游客访问信息。
3.user标签
用户已经身份验证/记住我登录后显示相应的信息。
4.hasPermission标签
如果当前Subject有权限将显示body体内容。
<shiro:hasPermission name="user:add">
<a href="<%=path %>/user/add.jsp">用户添加</a><br/>
</shiro:hasPermission>
5.hasRole标签
如果当前Subject有角色将显示body体内容。
<shiro:hasRole name="admin">
<a href="<%=path %>/admin/">管理员页面</a><br/>
</shiro:hasRole>