shiro 基于java的安全框架

1. shiro

在使用shiro之前,我们需要考虑一下我们项目中依旧存在的一些问题
1.项目中的密码是否可以明文存储?
2.是否任意方可,无论是否登录都可以访问任何功能?
3.项目中的各种操作,是否所有用户都可以随意使用、
以上的这些项目安全以及身份权限问题有待我们解决,这也就引出了为什么我们要使用shiro

1.1 shiro是什么(重点)

shiro是Java的一个安全(权限)框架
•功能丰富,可以完成:身份认证,授权,加密,会话管理等功能
• Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE 环境,也可以用在 JavaEE 环境。
• 功能强大且易用,可以快速轻松地保护任何应用程序 ( 从最小的移动应用程序到最大的Web和企业应用程序。)
• 方便的与Web 集成和搭建缓存。
(主要他还是免费的,还强,你说气不气)

2.jpg

• Authentication(重点)
身份认证/登录,验证用户是不是拥有相应的身份;
• Authorization(重点)
授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作
如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
• Session Manager
会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有
信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的;
• Cryptography
加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
• Web Support
Web 支持,可以非常容易的集成到Web 环境;
• Caching
缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
• Remember Me
记住我,这个是非常常见的功能,即一次登录后,下次再来的话可以立即知道你是哪个用户
。。。。

2.Shiro架构

shiro 运行流程中,3个核心的组件:
Subject、SecutiryManager、Realm 重点重点重点(重要的事情说三遍)

3.jpg

• Subject
安全校验中,最常见的问题是"当前用户是谁?" "当前用户是否有权做x事?",所以考虑安全校验过程最自
然的方式就是基于当前用户。Subject 代表了当前“用户”,
应用代码直接交互的对象是 Subject,只要得到Subject对象马上可以做绝大多数的shiro操作。
也就是说 Shiro 的对外API 核心就是 Subject。
Subject 会将所有交互都会委托给 SecurityManager。
==Subject是安全管理中直接操作的对象==
• SecurityManager
安全管理器;即所有与安全有关的操作都会与SecurityManager 交互;
且其管理着所有 Subject;它是 Shiro的核心,
它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中DispatcherServlet 的角色
• Realm
Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager 要验证用户身份,那么
它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;
也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;
可以把 Realm 看成 DAO,( 数据访问入口 )

2.2 RBAC模型(重点)

Role Base Access Controll 基于角色的访问控制
shiro采用安全管理模型

模型中3个主体:用户、角色、权限
每个角色可以有多个权限,每个权限可以分配给多个角色
每个用户可以有多个角色,每个角色可以分配给多个用户
两个多对多

rbac1.jpg

权限访问控制
**1. 身份校验:判断是否为合法用户
2.权限检验:用户要做某一项操作或者使用某些资源必须要拥有某角色,或者必须拥有某权限
用户:1.张三 2.李四
角色 张三是老师 李四是学生
权限 老师可以讲课,可以学习 学生只可以学习

2.3Shiro整体架构
4.jpg

• Subject
任何可以与应用交互的“用户”(不干实事,传话筒)
• SecurityManager
相当于SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏
所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证、授权、会话及缓存的管理。
• Authenticator
负责 Subject 身份认证,是一个扩展点,可以自定义实现;可以使用认证
策略(Authentication Strategy),即什么情况下算用户认证通过了;
• Authorizer
授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
• Realm
可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体
的;可以是JDBC 实现,也可以是内存实现等等;由用户提供;所以一般在应用中都需要实现自己的 Realm;(作用类似于DAO)
• SessionManager
管理 Session 生命周期的组件;而 Shiro 并不仅仅可以用在 Web环境,也可以用在如普通的 JavaSE 环境
• CacheManager
缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少改变,
放到缓存中后可以提高访问的性能
• Cryptography
密码模块,Shiro 提供了一些常见的加密组件用于如密码加密/解密。

4.自定义Realm(重点)

存在的问题:目前所有的 用户、角色、权限数据都在ini文件中,不利于管理。
实际项目开发中这些信息,应该在数据库中。所以需要为这3类信息建表

4.2 自定义Realm

Realm的职责是,为shiro加载 用户,角色,权限数据,以供shiro内部校验。之前定义在ini中的数据,默认有IniRealm去加载。
现在库中的数据,需要自定义Realm去加载。

ops : 没必要在Realm中定义大量的查询数据的代码,可以为Realm定义好查询数据的DAO和Service。

4.2.1 父类

如下是Realm接口的所有子类,其中IniRealm是默认的Realm,负责加载shiro.ini中的[users]和[roles]信息,当shiro需要用户,角色,权限信息时,会通过IniRealm获得
自定义realm有两个父类可以选择:
1> 如果realm只负责做身份认证 ,则可以继承:AuthenticatingRealm**
2> 如果realm要负责身份认证和权限校验,则可以继承:AuthorizingRealm**

realm_sub_sup.jpg

4.2.2 定义Realm
public class MyRealm extends AuthorizingRealm {
    /**
     * 是否支持某种token
     * @param token
     * @return
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        System.out.println("is support in realm1");
        if(token instanceof UsernamePasswordToken){
            return true;
        }
        return false;
    }

    /**
     * 当subject.login()时,shiro会调用Realm的此方法做用户信息的查询,然后做校验
     * 职责:通过用户传递来的用户名查询用户表,获得用户信息
     * 返回值:将查到的用户信息(用户名+密码)封装在AuthenticationInfo对象中返回
     * 异常:如果没有查到用户可抛出用户不存在异常;如果用户被锁定可抛出用户被锁异常;或其它自定义异常.
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //获得用户名
        String username = (String) token.getPrincipal();
        System.out.println("user:"+username+" is authenticating~~");
        UserService userService = 
            (UserService)ContextLoader.getCurrentWebApplicationContext().getBean("userService");
        //身份认证
        User user = userService.queryUser(username);
        System.out.println("user:"+user);
        /**
        如果查询结果为空,直接返回null即可,
        shiro的后续流程有null判断逻辑,为null时会抛出UnknownAccountException
        **/
        if(user == null){
            return null;
        }
        // 将 当前用户的认证信息存入 SimpleAuthenticationInfo 并返回
        // 注意此方法的本职工作就是查询用户的信息,所以查到后不用比对密码是否正确,那是shiro后续流程的职责。
        // 如果密码错误,shiro的后续流程中会抛出异常IncorrectCredentialsException
        return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(),getName());
        /** 
        补充: 可以在user表中增加一列,用于存储用户是否被锁定,则查询的User对象中会有是否锁定的属性
             如果发现锁定则可以在此方法中抛出异常:LockedAccountException,
        **/
    }

    /**
     * 当触发权限或角色校验时:subject.isPermitted() / subject.checkPermission();
     *                       subject.hasRole() / subject.checkRole() 等。
     * 此时需要数据库中的 权限和角色数据,shiro会调用Realm的此方法来查询
     * 角色和权限信息存入SimpleAuthorizationInfo对象
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //获得username
        String username  = (String)principals.getPrimaryPrincipal();
        //新建SimpleAuthorizationInfo对象
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //查询当前用户的所有 "角色" 和 "权限"
        UserService userService = 
            (UserService)ContextLoader.getCurrentWebApplicationContext().getBean("userService");
        Set<String> roles = userService.queryRolesByUsername(username);
        Set<String> perms = userService.queryPermissionsByUsername(username);
        //“角色” 和 “权限” 存入 SimpleAuthorizationInfo对象
        info.setRoles(roles);
        info.setStringPermissions(perms);
        //返回SimpleAuthorizationInfo
        return info;
    }
}
4.3 配置Realm

shiro.ini中 配置自定义Realm
注意:[users] [roles] 两个部分不再需要

[main]
#注意:此处实在安装自定义Realm 指定realm
#声明Realm 名称 = Realm类路径
realm1 = com.zhj.realm.MyRealm
realm2 = com.zhj.realm.MyRealm2
#安装Reaml 关联到SecurityManager
securityManager.realms=$realm1,$realm2

五、与Web 集成(重点)

与web项目集成后,shiro的工作模式如下:

shiroFilter.jpg

如上:ShiroFilter拦截所有请求,对于请求做访问控制
如请求对应的功能是否需要有 认证的身份,是否需要某种角色,是否需要某种权限。(一个庞大的过滤器链)

1> 如果没有做 身份认证,则将请求强制跳转到登录页面。
如果没有充分的角色或权限,则将请求跳转到权限不足的页面。
2> 如果校验成功,则执行请求的业务逻辑

5.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>

<!-- ============ log ============ -->
<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>
5.2 代码:Controller

定义一些Servlet 或 springMVC的controller,发送请求,验证shiro的验证
如下代码,除了登录中要用到 Shiro的Subject,其余没改动

@Controller
@RequestMapping("/user")
public class ShiroController {
    @RequestMapping("/delete")
    public String deleteUser(){//访问此删除功能时要先经过shiro的安全校验
        System.out.println("delete User");
        return "forward:/xx.jsp";
    }
    @RequestMapping("/update")
    public String updateUser(){//访问此更新功能时要先经过shiro的安全校验
        System.out.println("update User");
        return "forward:/xx.jsp";
    }
    @RequestMapping("/insert")
    public String insertUser(){//访问此增加功能时要先经过shiro的安全校验
        System.out.println("insert User");
        return "forward:/xx.jsp";
    }
    @RequestMapping("/login/page")
    public String login(String username,String password){
        System.out.println("goto login.jsp");
        return "forward:/login.jsp";
    }
    @RequestMapping("/login/logic")
    public String login(String username,String password){//登录功能不能被shiro校验,否则永不能登录
        try{
            Subject subject = SecurityUtils.getSubject();
            subject.login(new UsernamePasswordToken(username,password));
            String uname = (String)subject.getPrincipal();
            System.out.println("uname:"+uname);
        }catch (AuthenticationException e){
            e.printStackTrace();
            return "redirect:/login.jsp";
        }
        return "forward:/success.jsp";
    }
}

5.3 配置

5.3.1 web.xml

安装 ShiroFilter!!!!(接受请求并移交)

<!-- 接收所有请求,以通过请求路径 识别是否需要 安全校验,如果需要则触发安全校验
     做访问校验时,会遍历过滤器链。(链中包含shiro.ini中urls内使用的过滤器)
     
     会通过ThreadContext在当前线程中绑定一个subject和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>
<!-- 自定义ini文件名称和位置
<context-param>
    <param-name>shiroConfigLocations</param-name>
    <param-value>classpath:shiro9.ini</param-value>
</context-param>-->
<!-- springMVC的配置照旧,此处省略...-->
5.3.2 shiro.ini

新增部分(section) [urls]
如下定义shiro的Realm、访问控制细节

[main]
#没有身份认证时,跳转地址
shiro.loginUrl = /user/login/page
#角色或权限校验不通过时,跳转地址
shiro.unauthorizedUrl=/author/error
#登出后的跳转地址,回首页
shiro.redirectUrl=/
#注意:此处实在安装自定义Realm 指定realm
#声明Realm 名称 = Realm类路径
realm1 = com.zhj.realm.MyRealm
realm2 = com.zhj.realm.MyRealm2
#安装Reaml 关联到SecurityManager
securityManager.realms=$realm1,$realm2
[urls]
# 如下格式:"访问路径 = 过滤器"
#【1.ant路径:? *  ** 细节如下】
# /user/login/page , /user/login/logic 是普通路径
# /user/* 代表/user后还有一级任意路径 : /user/a , /user/b , /user/c , /user/xxxxxxxxxxx
# /user/** 代表/user后还有任意多级任意路径: /user/a , /user/a/b/c , /user/xxxx/xxxxxx/xxxxx
# /user/hello? 代表hello后还有一个任意字符: /user/helloa , /user/hellob , /user/hellox

#【2.过滤器,细节如下】
# anon => 不需要身份认证
# authc => 指定路径的访问,会验证是否已经认证身份,如果没有则会强制转发到 最上面配置的loginUrl上
#         ( ops:登录逻辑本身千万不要被认证拦截,否则无法登录 )
# logout => 访问指定的路径,可以登出,不用定义handler。
# roles["manager","seller"] => 指定路径的访问需要subject有这两个角色
# perms["user:update","user:delete"] => 指定路径的访问需要subject有这两个权限
/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
#其余路径都需要身份认证【用此路径需谨慎】
/** = authc
#【3.注意】
# url的匹配,是从上到下匹配,一旦找到可以匹配的则停止,所以,通配范围大的url要往后放,
# 如"/user/delete" 和 "/user/**"
5.3.3 其他默认过滤器
7.jpg
5.5 总结

通过ShiroFilter和定义在shiro.ini中的配置信息,即可在项目接收用户访问时,进行身份,角色,权限进行访问控制啦!!!
如今的项目架构

shiro_spring架构.jpg

六、Spring集成(重点)

web项目的核心组件都在spring工厂中管理,利用IOC和AOP,组建了关系松散,稳健的系统。
shiro的诸多组件也需要由spring统一管理,进而可以更好的和其他组件协作。

之前的Realm中一直有如下代码:

//由于Realm还未进入spring工厂,所以无法直接注入工厂内部的DAO组件
UserService userService =(UserService)ContextLoader.getCurrentWebApplicationContext().getBean("xx");

ops:shiro的组件都是pojo组件,非常容易用spring管理,可以方便的从ini迁移到spring

6.1 pom

<!-- 其他依赖和web集成中 一致 ,此处省略-->
<!-- 新增一个依赖 用于在工厂中生产 ShiroFilter-->
<!-- 会传递导入shiro-core 和 shiro-web -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
6.2 aplicationContext.xml

将SecurityManager和Realm和ShiroFilter 都迁移到applicationContext.xml中
建议将如下配置,单独定义在一个配置文件:shiro-spring.xml,然后在applicationContext.xml中引入:
<import resource="classpath:shiro-spring.xml"/>

<!-- 连接池,SqlSessionFactory,MapperScannerConfigurer,事务控制等 配置不变 -->
<!-- 添加如下shiro配置 -->
<!-- shiro -->
<!-- 声明realm -->
<bean id="realm1" class="com.zhj.realm.MyRealm">
    <property name="userService" ref="userService"/>
</bean>
<!-- 声明SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!-- 注入Realm -->
    <property name="realm" ref="realm1"/>
</bean>
<!-- 生产SpringShiroFilter
     ( 持有shiro的过滤相关规则,可进行请求的过滤校验,校验请求是否合法 ) 
-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <property name="loginUrl" value="/user/login/page"/>
    <property name="unauthorizedUrl" value="/error.jsp"/>
    <property name="filterChainDefinitions">
        <value>
            /user/query=anon
            /user/insert=authc,roles["banfu"]
            /user/update=authc,perms["student:update"]
            /order/insert=authc,roles["xuewei"]
            /user/logout=logout
        </value>
    </property>
</bean>
6.3 web.xml
<!-- 会从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>
<!-- EnvironmentLoaderListener不再需要,因为shiro环境已由spring初始化 
     springMVC,spring配置不变 -->

7.RememberMe(记住我--一个诡异强大的懒虫制造机)

在登录后,可以将用户名存在cookie中,下次访问时,可以先不登录,就可以识别身份。
在确实需要身份认证时,比如购买,支付或其他一些重要操作时,再要求用户登录即可,用户体验好。
由于可以保持用户信息,系统后台也可以更好的监控、记录用户行为,积累数据。

7.1 代码

”记住我“ 起点在登录时刻:Subject.login(UsernameAndPasswordToken)
而是否确定要“记住我”,由登录时的token控制开关:token.setRememberMe(true);

Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//如果需要记住我的话,需要在token中设置
token.setRememberMe(true);//shiro默认支持”记住我“,只要有此设置则自动运作。
subject.login(token);
7.2 效果

登录之后的效果

rememberme.jpg

8、shiro标签

shiro提供了很多标签,用于在jsp中做安全校验。
完成对页面元素的访问控制

8.1 导入shiro标签库
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
<html>
    ....
</html>
8.2 身份认证

1<shiro:authenticated> 已登陆
2 <shiro:user> 已登录或记住我
3<shiro:guest> 游客(未登录 且 未记住我)
4<shiro:notAuthenticated> 未登陆
5<shiro:principal> 获取用户身份信息

<shiro:authenticated>
    欢迎您,<shiro:principal/>
</shiro:authenticated>

<shiro:user> <!-- 常用,包含已登录 且配合记住我,用户体验好 -->
    欢迎您,<shiro:principal/>
</shiro:user>

<shiro:guest>
    欢迎您,未登录,请<a href="<c:url value="/user/login/page"/>">登录</a>
</shiro:guest>

<shiro:notAuthenticated>
    您尚未登录(记住我也算在未登录中)
</shiro:notAuthenticated>

8.3 角色校验

1<shiro:hasAnyRoles name="admin,manager">是其中任何一种角色
2<shiro:hasRole name="admin"> 是指定角色
3<shiro:lacksRole name="admin">不是指定角色

<table>
    <tr>
        <td>id</td>
        <td>name</td>
        <td>operation</td>
    </tr>
    <tr>
        <td>001</td>
        <td>张三</td>
        <td>
            <shiro:hasAnyRoles name="admin,manager">
                <a href="#" style="text-decoration:none">详情</a>
            </shiro:hasAnyRoles>
            <shiro:hasRole name="admin">
                <a href="#" style="text-decoration: none">删除</a>
            </shiro:hasRole>
            <shiro:lacksRole name="admin">
                <a href="#" style="text-decoration: none">点击升级</a>
            </shiro:lacksRole>
        </td>
    </tr>
</table>
8.4 权限校验

1<shiro:hasPermission name="user:delete"> 有指定权限
2<shiro:lacksPermission name="user:delete">缺失指定权限

...
<td>
    <a href="#" style="text-decoration:none">查看详情</a>
    <shiro:hasPermission name="user:delete">
        <a href="#" style="text-decoration: none">删除</a>
    </shiro:hasPermission>
    <shiro:lacksPermission name="user:delete">
        <a href="#" style="text-decoration: none" >无权删除</a>
    </shiro:lacksPermission>
</td>
...

9.Session管理

shiro作为一款安全管理框架,对状态保持有很强的需要。
比如最常用的用户认证,就必需状态的保持,以及其他的一些功能实现的需要。
【shiro需要:认证中的 记住我中的用户名 正式登陆的用户名
【 开发者需要:其他功能中需要存入session的值 】

shiro提供了一整套session管理方案.
1. shiro的session方案和任何容器无关(如servlet容器);
2. javaSE也可以使用;相关组件都是pojo对ioc极其友好(方便的管理对象和满足依赖关系,定制参数)
3. 可以方便的扩展定制存储位置(内存,缓存,数据库等)
4. 对web透明支持:用了shiro的session后,项目中关于session的代码完全不用任何改动
5. 提供了全面的session监听机制,和session检测机制,对session可以细粒度操作
即,使用了shiro后,采用shiro的session方案是最优的方案。

9.1 配置
<!-- 会话Cookie模板 默认可省-->
<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <!-- cookie的 key="sid" -->
    <property name="name" value="JSESSIONID"/>
    <!-- 只允许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"/>
</bean>

<!-- 将sessionManager关联到SecurityManager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    ...
    <!-- 增加配置sessionManager -->
    <property name="sessionManager" ref="sessionManager"/>
</bean>

9.2 Session监听

session有三个核心过程:创建、过期、停止
过期:session的默认过期时间为30分钟。通过比对最近一次使用时间和当前使用时间判断
session不会自动报告过期,需检测器检测时,或再次访问时,才可以识别是否过期并移除。
停止:用户主动logout;主动调用session.stop(); 两种情况会将session标志为停止状态。

// 定义监听类 exentends SessionListenerAdapter
public class MySessionListener extends SessionListenerAdapter{
    //当有session创建时 触发
    @Override
    public void onStart(Session session) {
        System.out.println("session:"+session.getId()+" start");
    }
    //当有session停止时 触发
    @Override
    public void onStop(Session session) {
        System.out.println("session:"+session.getId()+" stop");
    }
    //当有session过期时 触发
    // 但不会主动触发,需要再次访问时,即又要使用session时才会发现session过期,并触发。
    @Override
    public void onExpiration(Session session) {
        System.out.println("session:"+session.getId()+" expired");
    }
}

配置监听类,关联给SessionManager

<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    ...
    <property name="sessionListeners">
        <list>
            <bean class="com.zhj.listener.MySessionListener"></bean>
        </list>
    </property>
    ...
</bean>
9.3 Session检测

用户如果没有主动退出登录,只是关闭浏览器,则session是否过期无法获知,也就不能停止session。
为此,shiro提供了session的检测机制,可以定时发起检测,识别session过期 并停止session

<!-- sessionManager默认开启session检测机制 -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    ...
    <!-- 开启检测器,默认开启 -->
    <property name="sessionValidationSchedulerEnabled" value="true"/>
    <!--- 检测器运行间隔,单位:毫秒  默认1小时
            //检测到过期后,会直接将session删除
            protected void afterExpired(Session session) {
                if (isDeleteInvalidSessions()) {
                    delete(session);
                }
            }
    -->
    <property name="sessionValidationInterval" value="3600000"/>
    ...
</bean>

如上,通过检测器,定时的检测session,并及时移除无效session,释放资源。

10.加密(重点)

用户的密码是不允许明文存储的,因为一旦数据泄露,用户的隐私信息会完全暴露。
密码必须结果加密,生成密文,然后数据库中只存储用户的密码的密文。

jiami.jpg

在加密过程中需要使用到一些"不可逆加密",如 md5,sha
所谓不可逆是指:
加密函数A, 明文 “abc”, A("abc") = "密文",不能通过 "密文" 反推出 "abc",即使密文泄露密码仍然安全。

10.1 加密介绍

shiro支持hash(散列)加密,常见的如 md5, sha等

  • 基本加密过程
    md5(明文),sha(明文) 得到明文的密文,但明文可能比较简单导致密文容易被破解。
  • 加盐加密过程
    系统生成一个随机salt="xxxxxx", md5(明文+salt) ,sha(明文+salt),则提升了密文的复杂度。
  • 加盐多次迭代加密过程
    如果迭代次数为2,则加密2次: md5(明文+salt)=密文a , md5(密文a+salt)=最终密文
    sha(明文+salt)=密文a , sha(密文a+salt)=最终密文
    则进一步提升了密文的复杂度,和被破解的难度。
    加密过程中建议使用salt,并指定迭代次数,迭代次数的建议值100000+

10.2 加密

增加用户,或修改用户密码时,涉及到密码的加密

在注册用户的业务中,对用户提交的密码加密即可。

注意:之前的用户表,并未考虑存储加密相关信息,所以此时需要对用户表做出改进,
加一列【 salt varchar(50) 】,用于存储每个用户的盐。

user_salt.jpg

class UserServiceImpl implements UserService{
    @Autowired
    private UserDAO userDAO;
    public void createUser(User user){
        user.setSalt(UUID.randomUUID().toString());//设置随机盐
        //设置加密属性:sha256算法,随机盐,迭代1000次
        Sha256Hash sha256Hash = new Sha256Hash(user.getPassword(),user.getSalt(),1000);
        //将用户信息 (包括密码的密文 和 盐) 存入数据库
        user.setPassword(sha256Hash.toBase64());//密文采用base64格式化        
        userDAO.createUser(user);
    }
}

10.3 密码比对

登录认证身份时,涉及到密码 比对 过程
注意,加密过程中使用的加密属性,和此处使用的加密属性 必须一致:
sha256,迭代100000次,使用base64格式化密文

10.3.1 指定比对器
<!-- 声明realm -->
<bean id="realm1" class="com.zhj.realm.MyRealm">
    <property name="userService" ref="userService"/>
    <!-- 此属性如果通过注解注入,则需要将注解加载set方法上,不能用在属性上。
         此属性是父类属性,所以只有在set方法上注入,才能覆盖父类属性值。
    -->
    <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="1000"/>
        </bean>
    </property>
</bean>
10.3.2 修改Realm

doGetAuthenticationInfo方法的返回值中需要添加salt
ByteSource.Util.bytes(user.getSalt())

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    String username = (String) token.getPrincipal();
    UserService userService = 
        (UserService)ContextLoader.getCurrentWebApplicationContext().getBean("userService");
    User user = userService.queryUser(username);
    System.out.println("user:"+user);
    if(user==null){
        System.out.println("用户不存在");
        throw new UnknownAccountException("username:"+username+"不存在");
    }
    //以上逻辑不变
    //在最后返回用户认证info时,添加一个属性:ByteSource.Util.bytes(user.getSalt()) = 盐
    //用于密码比对
    return new SimpleAuthenticationInfo(user.getUsername(),
                                        user.getPassword(), 
                                        ByteSource.Util.bytes(user.getSalt()),
                                        getName());
}

至此,可以进行注册,注册中已经会加密密码。
然后登陆认证身份,认证时realm会调用比对器比对密文。

Shiro介绍完啦,我们的项目也逐渐变得更加完善,明天可期,加油!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容