Shiro基础
一. Shiro简介
1. Shiro是什么
- Apache Shiro是一个Java的一个安全权限框架。Shiro可以轻松的完成:身份认证,授权,加密,会话管理等功能。
2. 功能简介
- Authentication: 身份认证/登录,验证用户是不是拥有相应的身份。
- Authorization: 授权,即验证权限,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作。如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限。
- Session Manager: 会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的。
- Cryptography: 加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。
- Web Support: Web 支持,可以非常容易的集成到Web 环境。
- Caching: 缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。
- Concurrency: Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去。
- Testing: 提供测试支持。
- Run As: 允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。
- Remember Me: 记住我 ,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
3. 工作流程
-
Shiro的三个核心组件:
Subject
、SecurityManagr
、Realm
-
Subject: 应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject。Subject 代表了当前“用户”, 这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;与Subject的所有交互都会委托给 SecurityManager。
-
SecurityManager: 安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且其管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC中DispatcherServlet 的角色。
-
Realm: Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource。
4. 架构
- Subject: 任何可以与应用交互的“用户”。
- SecurityManager: 相当于SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行证、授权、会话及缓存的管理。
- Authenticator: 负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了。
- Authorizer: 授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控 制着用户能访问应用中的哪些功能。
- Realm: 可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的 Realm。
- SessionManager: 管理 Session 生命周期的组件;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境。
- CacheManager: 缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能。
- Cryptography: 密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密
二. HelloWorld
1. pom文件
<!-- shiro依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<!-- 日志依赖 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.12</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
2. 配置
创建一个shiro.ini文件。
[users]
zhangsan=123,admin
lisi=456,manager,seller
wangwu=789,clerk
-----------------------------------------------------------------------------
角色及其权限信息
预定权限:user:query
user:detail:query
user:update
user:delete
user:insert
order:update
....
[roles]
# admin 拥有所有权限,用*表示
admin=*
# clerk 只有查询权限
clerk=user:query,user:detail:query
# manager 有 user 的所有权限
manager="user:query,insert,update",order:query
3. 代码
- 通过在ini中设置的身份信息、角色、权限、做安全管理
- 1.身份认证:subject.login(token)
- 2.是否认证身份判断:subject.isAuthenticated()
- 3.角色校验:subject.hasRole(String:role)
- 4.权限校验:subject.isPermitted(String:perm)
// 创建 "SecurityFactory",加载ini配置,并通过它创建SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// shiro核心:SecurityManager
SecurityManager securityManager = factory.getInstance();
// 将SecurityManager托管到SecurityUtils工具类中(ops:之后可以不必关心SecurityManager)
SecurityUtils.setSecurityManager(securityManager);
// subject作用:直接由用户使用,调用功能简单,其底层调用SecurityManager的相关流程
Subject currentUser = SecurityUtils.getSubject();
if (!currentUser.isAuthenticated()) {
// 把用户名和密码封装为 UsernamePasswordToken 对象
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
// rememberme
token.setRememberMe(true);
try {
//执行登录.
currentUser.login(token); //如果失败,抛出异常
}
// 若没有指定的账户, 则 shiro 将会抛出 UnknownAccountException 异常.
catch (UnknownAccountException uae) {
System.out.println("----> There is no user with username of " + token.getPrincipal());
return;
}
// 若账户存在, 但密码不匹配, 则 shiro 会抛出 IncorrectCredentialsException 异常。
catch (IncorrectCredentialsException ice) {
System.out.println("----> Password for account " + token.getPrincipal() + " was incorrect!");
return;
}
// 用户被锁定的异常 LockedAccountException
catch (LockedAccountException lae) {
System.out.println("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
// 所有认证时异常的父类.
catch (AuthenticationException ae) {
}
}
//认证成功则用户信息会存入 currentUser(Subject)
System.out.println("----> User [" + currentUser.getPrincipal() + "] logged in successfully.");
//可以进一步进行角色校验和权限校验
if (currentUser.hasRole("admin")) {
System.out.println("hello admin";)
} else {
System.out.println("no role admin");
return;
}
if (currentUser.isPermitted("user:update")) { //权限校验
System.out.println("you can update user";)
} else {
System.out.println("you can not update update";)
}
//用户退出,会清除用户状态
currentUser.logout();
三. 与Web(SpringMVC)集成
ShiroFilter拦截所有请求,对于请求做访问控制
如请求对应的功能是否需要有认证的身份,是否需要某种角色,是否需要某种权限
1> 如果没有做身份认证,则将请求强制跳转到登录页面。
如果没有充分的角色或权限,则将请求跳转到权限不足的页面。
2> 如果校验成功,则执行请求的业务逻辑
1. pom文件
<!-- ============ Servlet ============ -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- ============== SpringMVC ============== -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<!-- shiro依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
2. 代码:Servlet
@Controller
@RequestMapping("/user")
public class UserController {
@PostMapping("/login")
public String loginLogic(User user){
try {
System.out.println("login logic");
//获取subject 调用login
Subject subject = SecurityUtils.getSubject();
// 创建用于登录的令牌
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword());
// 登录失败会抛出异常,则交由异常解析器处理
subject.login(token);
}catch (AuthenticationException e) {
e.printStackTrace();
return "login";
}
return "success";// 登录成功,跳转首页
}
}
3. 配置
3.1 web.xml
<!-- 接受所有请求,已通过请求路径 识别是否需要安全校验,如果需要出发安全校验。做访问校验时,会遍历过滤器链。(链中包换shiro.ini中urls内使用的过滤器)
会通过ThreadContext在当前线程中绑定一个subject和SecurityManager,功请求内使用。因此代码中可以不用使用工厂创建SecurityManager。可以通过SecurityUtils.getSubject()直接获得subject
-->
<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>
</filter-mapping>
<!-- 在项目启动时,默认加载web-info 或 classpath下的 shiro.ini ,并构建WebSecurityManager。
构建所有配置中使用的过滤器链(anon,authc等),ShiroFilter会获取此过滤器链
-->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<!-- 可以自定义文件名称和位置,不加则是默认的文件名称和位置 -->
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:xxxx.ini</param-value>
</context-param>
<!-- SpringMVC的配置 -->
<servlet>
<servlet-name>mvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:mvc.xml</param-value>
</init-param>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>mvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
3.2 shiro.ini
新增两个部分,[main]和[urls]
[users]
zhangsan=123,admin
lisi=456,manager,seller
wangwu=789,clerk
-----------------------------------------------------------------------------
角色及其权限信息
预定权限:user:query
user:detail:query
user:update
user:delete
user:insert
order:update
....
[roles]
# admin 拥有所有权限,用*表示
admin=*
# clerk 只有查询权限
clerk=user:query,user:detail:query
# manager 有 user 的所有权限
manager="user:query,insert,update",order:query
[main]
#没有身份认证时,跳转地址
shiro.loginUrl = /user/login
#角色或权限校验不通过时,跳转地址
shiro.unauthorizedUrl=/user/perms/error
#登出后的跳转地址,回首页
shiro.redirectUrl=/
#声明自定义的Realm
realm04=com.qianfeng.realm.MyRealm
#将自定义的Realm注册给 核心控制者:Securitymanager
securityManager.realms=$realm04
[urls]
/user/all = authc,perms["user:query2"]
/user/logout = logout
#/user/login/page = anon
#/user/login/logic = anon
#/user/query = authc
#/user/update = authc,roles["manager","seller"]
#/user/delete = authc, perms["user:update","user:delete"]
#/user/logout = logout
- 常用的默认过滤器:
- anno: 表示可以匿名使用
- authe: 表示需要认证(登录)才能使用
- logout: 注销登录的时候,完成一定的功能:任何现有的Session都将会失效,而且任何身份都会将失去关联(在Web引用程序中,RememberMe Cookie也将被删除)
- perms: 权限过滤器,相当于isPermitedAll()方法
- roles: 角色过滤器,相当于hasAllRoles()方法
四. Shiro标签
1. 导入Shiro标签库
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
2. 身份认证
<shiro:authenticated>
已登录
<shiro:user>
已登录或记住我
<shiro:guest>
游客(未登录)
<shiro:notAuthenticated>
未登录
<shiro:principal>
获取用户身份信息
五. 自定义Realm
存在的问题,目前所有的用户,角色,权限数据都在ini文件中,不利于管理。实际项目中我们需要把这些数据放入数据库中。
1. Realm的作用
Realm的职责:为Shiro加载用户,角色,权限数据,以供Shiro内部校验
之前定义在ini中的数据,默认有IniRealm去加载
现在库中的数据,需要自定义Realm去加载
2. Realm的相关类
自定义Realm有两个父类可以选择:
- 如果Realm只负责做身份认证,则可以继承:AuthehticatingRealm
- 如果Realm要负责做身份认证和权限校验,则可以继承:AuthorizingRealm
3. 自定义Realm
此时还没有继承Spring,Realm还没有被加载到IOC容器中,因此service下的类使用这种方式获的。
public class MyRealm extends AuthorizingRealm{
/**
* 作用:查询权限信息,并返回即可,不用任何比对
* 何时触发: /user/query = roles["admin"] /user/insert= perms["user:insert"] <shiro:hasRole <shiro:hasPermission
* 查询方式:通过用户名查询,该用户的 权限/角色 信息
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("在realm中 查询权限");
// 获取当前用户的用户名
String username = (String)principals.getPrimaryPrincipal();
// 查询当前用户的所有 权限信息: RoleService: public List<String:RoleName> queryAllRolesByUsername(String username)
// PermissionService: public List<String:PermissionStr> queryAllPermissionsByUsername(String username)
RoleService roleService = ContextLoader.getCurrentWebApplicationContext().getBean("roleServiceImpl", RoleService.class);
PermissionService permissionService = ContextLoader.getCurrentWebApplicationContext().getBean("permissionServiceImpl", PermissionService.class);
// 查询当前用户的权限信息
Set<String> roles = roleService.queryAllRolenameByUsername(username);
Set<String> perms = permissionService.queryAllPermissionByUsername(username);
// 将查询出的信息 封装
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(roles);
simpleAuthorizationInfo.setStringPermissions(perms);
return simpleAuthorizationInfo;
}
/**
* 作用:查询身份信息,并返回即可,不用任何比对
* 查询方式:通过用户名查询用户信息。
* 何时触发:subject.login(token);
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("在Realm中查询身份");
// 获取用户登录时发送来的用户名
String username = (String)token.getPrincipal();
// 查询用户信息: UserService:public User queryUserByUsername(String username);
UserService userService = ContextLoader.getCurrentWebApplicationContext().getBean("userServiceImpl", UserService.class);
//查询到用户信息
User user = userService.queryUserByUsername(username);
//判断用户信息是否为null
if(user==null){//不存在用户名
return null;//在后续流程中会抛出异常 UnknownAccountException
}
// 将用户信息封装在 AuthenticationInfo 中
return new SimpleAuthenticationInfo(user.getUsername(),//数据库中用户名
user.getPassword(), //数据库中的密码
this.getName());// realm的标识
}
}
4. 配置Realm
shiro.ini中配置自定义Realm
注意:[users] [roles]两个部分不需要,因为我们时用数据库中的数据进行比对
[main]
#没有身份认证时,跳转地址
shiro.loginUrl = /user/login
#角色或权限校验不通过时,跳转地址
shiro.unauthorizedUrl=/user/perms/error
#登出后的跳转地址,回首页
shiro.redirectUrl=/
#声明自定义的Realm
realm=com.codinghit.realm.MyRealm
#将自定义的Realm注册给 核心控制者:Securitymanager
securityManager.realms=$realm
5. 此时的项目架构
六. 加密
用户的密码是不予许铭文存储的,因为一旦数据泄露,用户的隐私信息会完全暴露。
密码必须结果加密,生成密文,然后数据库中存储用户的密码的密文信息。
1. 加密代码
注册和加密写进数据库中的密码都是加密后的密码,进行密码比对时也是使用的加密后的密码
@Service
public class UserServiceImpl implements UserService {
//@Resource(name="userDAO2")
@Resource
private UserDAO userDAO;
@Override
public Integer insertUser(User user) {
// 盐值
String salt = user.getUsername();
String s = new Sha256Hash(user.getPassword(), salt, 10000).toBase64(); //迭代10000次
// 设置密文
user.setPassword(s);
// 设置盐
user.setSalt(salt);
return userDAO.insertUser(user);
}
}
2. 密码比对
2.1 指定比对器
修改shiro.ini中配置
[main]
#没有身份认证时,跳转地址
shiro.loginUrl = /user/login
#角色或权限校验不通过时,跳转地址
shiro.unauthorizedUrl=/user/perms/error
#登出后的跳转地址,回首页
shiro.redirectUrl=/
# 声明密码比对器 密文转换
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
# 声明加密算法
credentialsMatcher.hashAlgorithmName=sha-256
# 声明迭代次数
credentialsMatcher.hashIterations=10000
#true=hex格式 false=base64格式
credentialsMatcher.storedCredentialsHexEncoded=false
#声明自定义的Realm
realm04=com.codinghit.realm.MyRealm
# 将密码比对器 注册在Realm中
realm.credentialsMatcher=$credentialsMatcher
#将自定义的Realm注册给 核心控制者:Securitymanager
securityManager.realms=$realm
2.2 修改Realm
doGetAuthenticationInfo方法的返回值需要做修改
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("在Realm中查询身份");
String username = (String)token.getPrincipal();
User user = userService.queryUserByUsername(username);
if(user==null){
return null;
}
//以上逻辑不变
//在最后返回用户认证info时,添加一个属性ByteSource.Util.bytes(user.getSalt() = 盐
//用于密码比对
return new SimpleAuthenticationInfo(user.getUsername(),
user.getPassword(),
ByteSource.Util.bytes(user.getSalt()),
this.getName());
}
七. Spring集成
web项目的核心组件都在spring工厂中管理
shiro的诸多组件也需要有spring统一管理,进而可以更好和其他组件协作
1. pom
<!-- 新增一个依赖 用于在工厂中生产 ShiroFilter-->
<!-- 会传递导入shiro-core 和 shiro-web -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2. applicationContext.xml
将SecurityManager和Realm和ShiroFilter都迁移到applicatinoContext.xml中
建议将如下配置,单独一个配置文件:shiro-spring.xml,然后applicationContext.xml中引入
<import resource="classpath:shiro_applicationContext.xml"/>
<!-- shiro配置 -->
<!-- Realm -->
<bean id="myRealm" class="com.qianfeng.realm.MyRealm">
<property name="userService" ref="userServiceImpl"/>
<property name="roleService" ref="roleServiceImpl"/>
<property name="permissionService" ref="permissionServiceImpl"/>
<!-- 密码比对器 -->
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="SHA-256"/>
<!-- true means hex encoded, false means base64 encoded -->
<property name="storedCredentialsHexEncoded" value="false"/>
<property name="hashIterations" value="10000"/>
</bean>
</property>
</bean>
<!-- 声明SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.webmgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
</bean>
<!-- shiroFilter -->
<!-- 生产SpringShiroFilter
( 持有shiro的过滤相关规则,可进行请求的过滤校验,校验请求是否合法 )
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 注入核心对象 -->
<property name="securityManager" ref="securityManager"/>
<!-- 未登录,没权限时的跳转路径 -->
<property name="loginUrl" value="/user/login"/>
<property name="unauthorizedUrl" value="/user/perms/error"/>
<!-- 过滤器链 -->
<property name="filterChainDefinitions">
<value>
/user/all=authc,roles["banzhang"]
/user/logout=logout
/user/insert=authc,roles["banfu"]
/user/update=authc,perms[""student:update""]
/order/insert=authc,roles["xuewei"]
</value>
</property>
</bean>
3. web.xml
此时shiro.ini文件就可以完全删除了
<!-- 会从spring工厂中获取和它同名的bean,(id="shiroFilter")
接到请求后调用bean的doFilter方法,进行访问控制。
-->
<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>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 此时如下两个配置都可以去掉了 -->
<!--<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>
</filter-mapping>-->
<!--<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>-->
八. 记住我
在登录后,可以将用户名存在cookie中,下次访问时,可以先不登录,就可以识别身份。
在确认需要身份认证时,再要求用户登录即可
注意:记住我与登录验证是不一样的,因此此时有可能别人在使用你的电脑,因此在执行某些操作时还会要求再次输入密码进行验证。
1. 代码
"记住我"起点在登录时刻,subject.login(token);
@PostMapping("/login")
public String loginLogic(User user){
System.out.println("login logic");
//获取subject 调用login
Subject subject = SecurityUtils.getSubject();
// 创建用于登录的令牌
UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword(
// 开启记住我
token.setRememberMe(true);
subject.login(token);
return "index";
2. 自定义
如果需要做自定义,可以明确定义如下两个组件:
SimpleCookie:
封装cookie的相关属性,定制cookie写出逻辑
CookieRememberMeManager:
接受SecurityManager调度,获取用户信息,加密数据,并调度SimpleCookie写出cookie
<!-- 记住我的Cookie -->
<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- rememberMe是cookie值中的key,value时用户名的密文
cookie["rememberMe":"deleteMe"] 此cookie每次登陆后都会写出,用于清除之前的cookie
cookie["rememberMe":username的密文] 此cookie也会在登录后写出,用于记录最新的username
(ops: 如上设计,既能保证每次登陆后重新记录cookie,也能保证切换账号时,记录最新账号)
-->
<property name="name" value="rememberMe04"/>
<!-- cookie只在http请求中可用,那么通过js脚本将无法读取到cookie信息,有效防止cookie被窃取 -->
<property name="httpOnly" value="true"/>
<!-- cookie的生命周期,单位:秒 -->
<property name="maxAge" value="604800"/><!-- 7天 -->
</bean>
<!--记住我管理器-->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<property name="cookie" ref="rememberMeCookie"/>
</bean>
<!-- 声明SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
<!-- 记住我管理器 -->
<property name="rememberMeManager" ref="rememberMeManager"/>
</bean>
九. Session管理
Shiro提供一整套session方案:
- Shiro的session方案和任何容器无关(如Servlet容器)
- 可以方便的扩展定制存储位置(内存,缓存,数据库)
- 提供了全面的session监听机制,和session检测机制,对session可以细粒度操作
1. 核心对象
- SimpleSession
Session的实现类,完成session基本功能- SimpleSessionFactory
生成SimpleSession- SessionDAO
默认的实现类:MemorySessionDAO,由SessionManager创建- DefaultSessionManager
由SecurityManager创建,负责创建、管理SessionFactory和SessionDAO。
2. JavaEE环境
2.1 applicationContext.xml
<!-- 会话Cookie模板 默认可省-->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<!-- cookie的 key="sid" -->
<property name="name" value="JSESSIONID04"/>
<!-- 只允许http请求访问cookie -->
<property name="httpOnly" value="true"/>
<!-- cookie过期时间,-1:存活一个会话 ,单位:秒 ,默认为-1-->
<property name="maxAge" value="-1"/>
</bean>
<bean id="sessionManager"
class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<!-- 默认值和配置中给出的一致,所bean:sessionIdCookie 可以省略 -->
<property name="sessionIdCookie" ref="sessionIdCookie"/>
<!-- session全局超时时间, 单位:毫秒 ,30分钟 默认值为1800000-->
<property name="globalSessionTimeout" value="1800000"/>
<!-- 注册session监听器 -->
<property name="sessionListeners">
<list>
<bean class="com.qianfeng.session.MySessionListener"></bean>
</list>
</property>
<!-- session检测的时间间隔 -->
<property name="sessionValidationInterval" value="15000"></property>
</bean>
<!-- 声明SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
<!-- 记住我管理器 -->
<property name="rememberMeManager" ref="rememberMeManager"/>
<!-- session管理器 -->
<property name="sessionManager" ref="sessionManager"/>
</bean>
2.2 Session监听
Session有三个核心过程:创建、过期、停止
public class MySessionListener extends SessionListenerAdapter{
/**
* session创建时触发
* @param session
*/
@Override
public void onStart(Session session) {
System.out.println("session create~~~");
}
/**
* session 停止时触发 subject.logout() session.stop()
* @param session
*/
@Override
public void onStop(Session session) {
System.out.println("session stop~~~");
}
/**
* session过期时触发,静默时间查过了过期时间
* @param session
*/
@Override
public void onExpiration(Session session) {
System.out.println("session expire~~~");
}
}
2.3 Session检测
用户如果没有主动退出登录,只是关闭浏览器,则session是否过期无法获知,也就不能停止session。
为此,Shiro提供了session的检测机制,可以定时发起检测,识别session过期并停止session。定时的检测session,并及时移除无效session,释放资源。
<!-- session检测的时间间隔 -->
<property name="sessionValidationInterval" value="15000"></property>
十. 注解开发
1. pom.xml
<!-- 注解加载Controller中,原理是会对Controller做增强,切入访问控制逻辑,需要如下依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
2. 配置mvc.xml
<!-- shiro -->
<!-- 调用工厂中 Initializable类型的组件的 init方法 -->
<bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- springMVC工厂启动时,如果发现了此配置,会在当前工厂中 多创建一个Bean(后处理器),用来定制代理 -->
<aop:config></aop:config>
<!-- 在此bean的构建过程中,初始化了一些额外功能和piontcut
interceptors.add(new RoleAnnotationMethodInterceptor(resolver));
@RequiresRoles
interceptors.add(new PermissionAnnotationMethodInterceptor(resolver));
@RequiresPermissions
interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver));
@RequiresAuthentication
interceptors.add(new UserAnnotationMethodInterceptor(resolver));
@RequiresUser
interceptors.add(new GuestAnnotationMethodInterceptor(resolver));
@RequiresGuest
-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"
<property name="securityManager" ref="securityManager"/>
</bean>
3. 注解使用
@Controller
@RequestMapping("/order")
public class OrderController {
@RequiresUser
@RequestMapping("/query")
public String queryOrder(){
System.out.println("query Order");
return "index";
}
@RequiresAuthentication
@RequiresRoles(value = {"banzhang","student"},logical = Logical.OR)
@RequestMapping("/save")
public String saveOrder(){
System.out.println("save Order");
return "index";
}
// @RequiresPermissions("user:delete")
@RequiresAuthentication
@RequiresPermissions(value={"user:delete","user:query"},logical = Logical.AND)
@RequestMapping("/delete")
public String deleteOrder(){
System.out.println("delete Order");
return "index";
}
@RequiresAuthentication
@RequestMapping("/update")
public String updateOrder(){
System.out.println("update Order");
return "index";
}
}
4. 配置修改
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 注入核心对象 -->
<property name="securityManager" ref="securityManager"/>
<!-- 不再需要,此时如果身份或权限不通过,会抛出异常,需要异常解析器处理 -->
<!--<property name="loginUrl" value="/user/login"/>-->
<!--<property name="unauthorizedUrl" value="/user/perms/error"/>-->
<!--<property name="filterChainDefinitions">-->
<!--<value>-->
<!-- 如下不再需要,登出可以保留 -->
<!--/user/all=authc,roles["banzhang"]-->
<!--/user/logout=logout-->
<!--/user/insert=authc,roles["banfu"]-->
<!--/user/update=authc,perms[""student:update""]-->
<!--/order/insert=authc,roles["xuewei"]-->
<!--</value>-->
<!--</property>-->
</bean>
5. 异常处理
public class MyExceptionResolver implements HandlerExceptionResolver{
@Override
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println(ex.getClass());
ex.printStackTrace();//开发时必需
ModelAndView mv = new ModelAndView();
if(ex instanceof IncorrectCredentialsException || ex instanceof UnknownAccountException){
//跳转登录页面,重新登录
//mv.setViewName("redirect:/user/login");
try {
response.getWriter().print(JSON.toJSONString(R.error("用户名或密码错误")));
response.getWriter().flush();
response.getWriter().close();
} catch (IOException e) {
e.printStackTrace();
}
mv=null;
}else if(ex instanceof UnauthorizedException){// 角色不足 权限不足
//跳转权限不足的页面
mv.setViewName("redirect:/user/perms/error");
}else if(ex instanceof UnauthenticatedException){//没有登录 没有合法身份
//跳转登录页面,重新登录
mv.setViewName("redirect:/user/login");
}
return mv;
}
}
ex.printStackTrace();//开发时必需
ModelAndView mv = new ModelAndView();
if(ex instanceof IncorrectCredentialsException || ex instanceof UnknownAccountException){
//跳转登录页面,重新登录
//mv.setViewName("redirect:/user/login");
try {
response.getWriter().print(JSON.toJSONString(R.error("用户名或密码错误")));
response.getWriter().flush();
response.getWriter().close();
} catch (IOException e) {
e.printStackTrace();
}
mv=null;
}else if(ex instanceof UnauthorizedException){// 角色不足 权限不足
//跳转权限不足的页面
mv.setViewName("redirect:/user/perms/error");
}else if(ex instanceof UnauthenticatedException){//没有登录 没有合法身份
//跳转登录页面,重新登录
mv.setViewName("redirect:/user/login");
}
return mv;
}
}