Shiro详解(超全面)

一、简介

  • Shiro安全框架是Apache提供的一个强大灵活的安全框架
  • Shiro安全框架提供了认证、授权、企业会话管理、加密、缓存管理相关的功能,使用Shiro可以非常方便的完成项目的权限管理模块开发
  • 简单灵活、可脱离spring

二、Shiro的整体架构

在这里插入图片描述

1、Subject

​ Subject即主体(可以把当前用户理解为主体),外部应用与Subject进行交互,Subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject在Shiro中是一个接口,接口中定义了很多认证授权相关的方法,外部程序通过Subject进行认证授,而Subject是通过SecurityManager安全管理器进行认证授权

2、Security Manager

​ SecurityManager即安全管理器,对全部的Subject进行安全管理,它是Shiro的核心,负责对所有的Subject进行安全管理。通过SecurityManager可以完成Subject的认证、授权等,实质上SecurityManager是通过**Authenticator进行认证,通过Authorizer**进行授权,通过SessionManager进行会话管理等。

​ SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。

3、Cryptography

​ Cryptography即密码管理,Shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。

4、Authenticator

​ Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,Shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

5、Authorizer

​ Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

6、realm

​ Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库,那么realm就需要从数据库获取用户身份信息。

注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。

7、sessionManager

​ sessionManager即会话管理,Shiro框架定义了一套会话管理,它不依赖web容器的session,所以Shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。

8、SessionDAO

​ SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。

9、CacheManager

​ CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。

三、入门案例

​ 该案例是基于SSM框架整合之后的基础上实现的

  1. 导入依赖
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.3.2</version>
</dependency>
  1. 创建表
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '权限id,主键',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名称',
  `url` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限访问的url资源',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES (1, '用户管理', '/sys');
INSERT INTO `permission` VALUES (2, '教师管理', '/teacher');
INSERT INTO `permission` VALUES (3, '学生管理', '/student');

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '角色id',
  `role_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色名',
  `state` tinyint(4) NULL DEFAULT 1 COMMENT '状态,1可用,0不可用',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, '管理员', 1);
INSERT INTO `role` VALUES (2, '普通用户', 1);

-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `role_id` int(11) NOT NULL COMMENT '关联role表的角色id',
  `permission_id` int(11) NOT NULL COMMENT '关联permission表的权限id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 1, 1);
INSERT INTO `role_permission` VALUES (2, 1, 2);
INSERT INTO `role_permission` VALUES (3, 1, 3);

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户Id,主键',
  `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户账号',
  `password` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户密码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'admin', '111');
INSERT INTO `user` VALUES (2, 'zhangsan', '111');

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `user_id` int(11) NOT NULL COMMENT '关联user表的用户id',
  `role_id` int(11) NOT NULL COMMENT '关联role表的角色id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 1, 2);
INSERT INTO `user_role` VALUES (3, 2, 2);

SET FOREIGN_KEY_CHECKS = 1;
  1. 创建实体类User和Role
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    
    
    private int id;
    private String username;
    private String password;
    private List<Role> roles; //该用户所具有的所有角色
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Role {
    
    
    private int id; //角色表的主键
    private String roleName; //角色的名称
    private int state; //角色的状态
}
  1. UserMapper接口
public interface UserMapper {
    
    
    //根据用户账号查询用户信息
    User queryByUsername(String username);
}
  1. UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hqyj.cl.mapper.UserMapper">
    <!-- 配置映射 -->
    <resultMap id="userMap" type="user">
        <id column="id" property="id" />
        <result column="username" property="username" />
        <result column="password" property="password" />
        <collection property="roles" ofType="role">
            <id column="id" property="id" />
            <result column="role_name" property="roleName" />
            <result column="state" property="state" />
        </collection>
    </resultMap>
    <!-- 根据用户账号查询用户信息-->
    <select id="queryByUsername" resultMap="userMap">
         SELECT
            u.id,
            u.username,
            u.password,
            r.id,
            r.role_name,
            r.state
        FROM
            USER u
            LEFT JOIN user_role ur ON u.id = ur.user_id
            LEFT JOIN role r ON r.id = ur.role_id
        WHERE
            u.username = #{username}
    </select>
</mapper>
  1. UserService接口
public interface UserService {
    
    
    //使用用户名密码登录
    Map<String, Object> login(String username, String password);
    //将已登录的用户登出
    Map<String, Object> logout();
}
  1. UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
    
    
    //使用用户名密码登录
    @Override
    public Map<String, Object> login(String username, String password) {
    
    
        Map<String, Object> result = new HashMap<>();
        //获取主体, 代表当前用户的对象
        Subject subject = SecurityUtils.getSubject();
        //判断当前用户是否已经登录过
        if(!subject.isAuthenticated()){
    
    
            //没认证用户才登录
            //使用待认证的用户名和密码创建安全令牌对象
            AuthenticationToken token = new UsernamePasswordToken(username, password);
            //让shiro框架检查令牌,执行登录
            try{
    
    
                subject.login(token);
            }catch (UnknownAccountException e){
    
    
                //用户不存在
                result.put("code", -1);
                result.put("message", username + "不存在");
                return result;
            }catch (IncorrectCredentialsException e){
    
    
                //用户名和密码不匹配
                result.put("code", -2);
                result.put("message", username + "密码错误");
                return result;
            }catch (AuthenticationException e){
    
    
                //其他任何异常
                e.printStackTrace();
                result.put("code", -3);
                result.put("message", username + "认证失败");
                return result;
            }
        }
        //已经通过认证
        System.out.println(username + "认证通过了....");
        //从shiro提供session对象中获取已经认证成功的用户信息
        Object user = subject.getSession().getAttribute("user");
        result.put("code", 200);
        result.put("message", username +"登录成功");
        result.put("loginUser", user);
        return result;
    }

    // 将已登录的用户登出
    @Override
    public Map<String, Object> logout() {
    
    
        //获取当前用户
        Subject subject = SecurityUtils.getSubject();
        Object username = subject.getPrincipal();
        //执行登出 , 即删除所有已经登录的相关信息
        subject.logout();
        Map<String, Object> result = new HashMap<>();
        result.put("code", 0);
        result.put("message", username + "用户登出成功");
        return result;
    }
}
  1. UserController
@Controller
@RequestMapping("/user")
public class UserController {
    
    
    @Autowired
    private UserService userService;

    //用户登录
    @RequestMapping("/login")
    @ResponseBody //将返回的Map对象转换成json格式字符串,直接放在响应体中
    public Map<String, Object> login(User user){
    
    
        //1、对请求参数做判空
        if(user == null){
    
    
            Map<String, Object> result = new HashMap<>();
            result.put("code",-20);
            result.put("message","请求参数异常");
            return result;
        }
        //2、调用业务层执行业务,获取结果,然后直接返回数据
        return userService.login(user.getUsername(), user.getPassword());
    }

    //用户登出
    @RequestMapping("/logout")
    @ResponseBody
    public Map<String, Object> logout(){
    
    
        //调用业务层实现业务
        return userService.logout();
    }

}
  1. MyShiroRealm
public class MyShiroRealm extends AuthorizingRealm {
    
    

    @Autowired
    private UserMapper userMapper;
        
    //获取授权信息,由开发者提供shiro框架已认证过用户的权限信息
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    
    
        return null;
    }
    //获取认证信息
    //由开发者来编写,实现从数据库中查询待认证的用户的用户信息。以提供給shiro框架进行密码匹配工作
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
        //使用token对象获取待登录用户的用户名
        String username = (String)token.getPrincipal();
        //从数据库中查询该username的用户的信息
        User user = userMapper.queryByUsername(username);
        //判断待登录用户是否存在
        if(user == null){
    
    
            throw new UnknownAccountException(username + "不存在");
        }
        //保存用户信息
        //获取shiro提供的当前用户的会话对象
        Session session = SecurityUtils.getSubject().getSession();
        //在shiro提供的会话对象中共享数据
        session.setAttribute("user", user);
        //创建一个SimpleAuthenticationInfo对象
        //三个参数: 1、用户名,2、数据库中用户的密码 3、realm的名称
        //返回AuthenticationInfo对象
        return new SimpleAuthenticationInfo(
                user.getUsername(),user.getPassword(),getName());
    }
}
  1. shiro配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 配置我们自定义realm类 bean-->
    <bean id="myShiroRealm" class="com.hqyj.cl.utils.MyShiroRealm">
    </bean>
    
    <!-- 配置shiro的核心对象 安全管理器-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!--依赖注入我们自已定义realm bean  -->
        <property name="realm" ref="myShiroRealm" />
    </bean>
    
    <!-- 配置shiro的过滤器bean 执行授权检查相关功能,这里得先配上 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
    </bean>
</beans>
  1. 把shiro配置文件导入applicationContext中,让spring管理
<import resource="spring-shiro.xml"/>
  1. 配置shiro过滤器 web.xml
<!-- 配置代理过滤器, 由spring提供实现,代理的目标对象是在ioc容器中的真实过滤器bean
 filter-name标签指定被代理的bean,这里的shiroFilter就是在ioc容器中的shiro过滤器bean的id
-->
<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
  1. login.jsp
<%
    String path = request.getContextPath();
    String basePath = request.getScheme() + "://" +
            request.getServerName() + ":" + request.getServerPort() + path + "/";
%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>后台登录</title>
    <meta name="renderer" content="webkit|ie-comp|ie-stand">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width,user-scalable=yes, minimum-scale=0.4, initial-scale=0.8,target-densitydpi=low-dpi" />
    <meta http-equiv="Cache-Control" content="no-siteapp" />
    <link rel="shortcut icon" href="<%=basePath%>static/favicon.ico" type="image/x-icon" />
    <link rel="stylesheet" href="<%=basePath%>static/css/font.css">
    <link rel="stylesheet" href="<%=basePath%>static/css/xadmin.css">
    <script type="text/javascript" src="<%=basePath%>static/js/jquery-2.1.1.min.js"></script>
    <script src="<%=basePath%>static/lib/layui/layui.js" charset="utf-8"></script>
    <script type="text/javascript" src="<%=basePath%>static/js/xadmin.js"></script>
</head>
<body class="login-bg">
<div class="login">
    <div class="message">用户登录</div>
    <div id="darkbannerwrap"></div>
    <hr class="hr15">
    <form action="" method="post" class="layui-form">
        <input name="username" placeholder="用户名" id="username"  type="text" class="layui-input" >
        <hr class="hr15">
        <input name="password" placeholder="密码" id="password"  type="password" class="layui-input">
        <hr class="hr15">
        <input value="登录" style="width:100%;" type="button" οnclick="login()">
        <hr class="hr20" >
    </form>
    <script>
        function login() {
            console.log()
            $.ajax({
                url:"../user/login",
                dataType:"json",
                type:"post",
                data:{
                    "username":$("#username").val(),
                    "password":$("#password").val(),
                },success:function (data) {
                    console.log(data)
                },error:function () {
                    alert("服务器错误");
                }
            })
        }
    </script>
</div>
</body>
</html>

四、认证流程

1、认证流程

shiro认证流程图
  1. 创建token令牌,token中有用户提交的认证信息即账号和密码;

  2. 执行subject.login(token),最终由securityManager通过Authenticator进行认证;

  3. Authenticator的实现ModularRealmAuthenticator调用MyShiroRealm以获取系统中用户真实的账号和密码;

  4. MyShiroRealm先根据token中的账号去系统中找该账号,如果找不到则抛出UnknownAccountException异常。如果找到则返回AuthenticationInfo(含真实密码);

  5. Authenticator根据realm返回的AuthenticationInfo匹配密码,失败则抛出IncorrectCredentialsException异常,成功则认证通过。

2、常见异常

  1. UnknownAccountException 账号不存在异常
org.apache.shiro.authc.UnknownAccountException: No account found for user...
  1. IncorrectCredentialsException 输入密码错误异常
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhangsan, rememberMe=false] did not match the expected credentials.
  1. AuthenticationException 认证异常
    1. DisabledAccountException 帐号被禁用
    2. LockedAccountException 帐号被锁定
    3. ExcessiveAttemptsException 登录失败次数过多
    4. ExpiredCredentialsException 凭证过期

五、授权流程

1、授权流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eY7hr3cg-1682513591073)(G:\桌面\备课\上课\ssm\课件图\shiro授权流程.png)]

  1. 创建Security Manager
  2. 主体subject授权
  3. 主体授权是交给Security Manager授权
  4. Security Manager调用授权器Authorizer授权
  5. 通过Realm在数据库或者缓存中来获取授权的数据(角色数据和权限数据)

2、自定义realm实现授权

​ 上述入门案例中,我们实现了使用自定义realm实现认证过程,接下来,我们完善使用自定义realm实现授权

  1. 修改shiro的配置文件
<!-- 配置shiro的过滤器bean 执行授权检查相关功能,这里得先配上 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager"/>
    <!-- 配置shiro的过滤器bean 执行授权检查,
        敏感资源: web项目中就是一系列能访问操作数据库的url
        浏览器访问敏感资源,shiro过滤器拦截这次请求,执行授权检查,
        通过授权域realm对象来获取当前用户所具有的权限。
        哪些是敏感资源以及需要的身份,需要在此处定义 -->
    <!-- loginUrl配置:如果没有认证的用户访问敏感资源,将被shiro过滤器强制跳转到指定url资源 -->
    <property name="loginUrl" value="/sys/goLogin"/>
    <!-- 已认证的用户访问需要特定权限的敏感资源时,如果没有该特定权限时,将被shiro过滤器强制跳转到指定url资源 -->
    <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
    <!-- 敏感资源及用户身份的定义-->
    <property name="filterChainDefinitions">
        <value>
            <!-- 过滤链的语法: 敏感资源的url = 身份     (一行写一个配置)
                      身份:    anon  匿名用户(未认证的用户)
                               authc 已通过认证的用户
                               user  曾经认证过的用户
                               roles[角色名]  拥有某个角色的用户
                               perms[权限名]  拥有某个权限的用户
                -->
            /sys/goLogin = anon     <!-- 匿名用户才能访问 -->
            /user/login = anon
            /user/logout = authc  <!-- 已认证的用户可以访问-->
            /sys/goMyself = roles[普通用户] <!-- 普通用户才能访问 -->
            /sys/goIndex = roles[管理员]  <!-- 管理员才能访问 -->
            /static/** = anon
            /* = authc       <!-- /** 根路径下的所有url包括子路径  /* 根路径下的一级中所有的url -->
            <!-- 授权时shiro过滤器会从上往下搜索过滤器链,找到一个url匹配就结束。
                   统配的资源一般写在靠下面位置
                -->
        </value>
    </property>
</bean>
  1. 修改MyShiroRealm中授权方法
//获取授权信息,由开发者提供shiro框架已认证过用户的权限信息
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    
    
    System.out.println("shiro来找我们获取授权信息了");
    //获取当前已认证(待授权用户)的用户的账号
    String username = (String)principals.getPrimaryPrincipal();
    // 查询该用户的角色或权限
    User user = userMapper.queryByUsername(username);
    Set<String> roles = new HashSet<>();
    if(user.getRoles() != null){
    
    
        //遍历用户的角色信息
        for(Role one:user.getRoles()){
    
    
            //角色名添加到role集合中
            roles.add(one.getRoleName());
        }
    }
    //查询用户的权限信息,留给同学们后续实现
    Set<String> perms = new HashSet<>();
    //创建授权信息对象
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //设置角色信息
    info.setRoles(roles);
    //设置权限信息
    info.setStringPermissions(perms);
    //返回授权信息对象
    return info;
}

六、shiro标签(JSP)

​ shiro提供一系列jsp标签,使用该标签可以非常方便在jsp页面中根据用户的授权信息来控制菜单、按钮等ui元素的显示。

1、标签

标签名称 标签条件(均是显示标签内容)
<shiro:authenticated> 认证通过用户
<shiro:notAuthenticated> 未认证通过用户
<shiro:guest> 用户未认证时
<shiro:user> 认证通过或已记住的用户时
<shiro:hasAnyRoles name=“abc,123” > 在有abc或者123角色时
<shiro:hasRole name=“abc”> 拥有角色abc
<shiro:lacksRole name=“abc”> 没有角色abc
<shiro:hasPermission name=“abc”> 拥有权限资源abc
<shiro:lacksPermission name=“abc”> 没有abc权限资源
<shiro:principal property=“username”> 显示用户身份名称

1.1 案例

  1. 创建shiroTag.jsp页面
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>shiroTag.jsp</title>
</head>
<body>
<!-- 根据当前用户身份,来决定是否展示标签体中的内容 -->
<shiro:guest>匿名用户可以看到</shiro:guest> <br>
<shiro:authenticated>认证过的用户可以看到</shiro:authenticated>
<shiro:hasAnyRoles name="管理员,普通用户">管理员或普通用户可以看到</shiro:hasAnyRoles>
<shiro:hasRole name="管理员">管理员才能看到</shiro:hasRole>
<shiro:hasRole name="普通用户">普通用户才能看到</shiro:hasRole>
</body>
</html>
  1. shiro配置文件中,过滤链放行shiroTag.jsp页面
/jsp/shiroTag.jsp = anon

七、加密

1、散列算法

​ 散列算法一般用于生成一段文本的摘要信息,散列算法不可逆,将内容可以生成摘要,无法将摘要转成原始内容。散列算法常用于对密码进行散列,常用的散列算法有MD5、SHA。一般散列算法需要提供一个salt(盐)与原始内容生成摘要信息,这样做的目的是为了安全性,比如:111111的md5值是:96e79218965eb72c92a549dd5a330112,拿着“96e79218965eb72c92a549dd5a330112”去md5破解网站很容易进行破解,如果要是对111111和salt(盐,一个随机数)进行散列,这样虽然密码都是111111加不同的盐会生成不同的散列值。

2、加密工具类

  1. MD5Util加密工具类
public class MD5Util {
    
    
    public static String md5(String password, String salt){
    
    
        /*
            algorithmName代表进行加密的算法名称、
            source代表需要加密的元数据,如密码、
            salt代表盐,需要加进一起加密的数据、
            hashIterations代表hash迭代次数。
        * */
        return new SimpleHash("MD5", password, salt,1024).toString();
    }
}
  1. 测试
@Test
public void applyMd5(){
    
    
    String password = "111";
    String slat = "admin";
    String hashedPassword = MD5Util.md5(password, slat);
    System.out.println(hashedPassword);
}
// 输出607a79936b28c5ddfe26940cf5980585

3、认证加密

  1. 修改shiro配置文件
<!-- 配置密码匹配器  散列的凭证匹配器 -->
<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
    <!-- 指定散列算法的细节  这里定义的算法和迭代次数需要和加密工具类定义的一致-->
    <property name="hashAlgorithmName" value="MD5"/>
    <property name="hashIterations" value="1024"/>
</bean>

<!-- 配置我们自定义realm类 bean-->
<bean id="myShiroRealm" class="com.hqyj.cl.utils.MyShiroRealm">
    <!-- 依赖注入我们定义的凭证匹配器,bean-->
    <property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>
  1. 修改MyShiroRealm认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
    //使用token对象获取待登录用户的用户名
    String username = (String)token.getPrincipal();
    //从数据库中查询该username的用户的信息
    User user = userMapper.queryByUsername(username);
    System.out.println(user + "#AuthenticationInfo");
    //判断待登录用户是否存在
    if(user == null){
    
    
        throw new UnknownAccountException(username + "不存在");
    }
    //保存用户信息
    //获取shiro提供的当前用户的会话对象
    Session session = SecurityUtils.getSubject().getSession();
    //在shiro提供的会话对象中共享数据
    session.setAttribute("user", user);
    //创建一个SimpleAuthenticationInfo对象
    //三个参数: 1、用户名,2、数据库中用户的密码 3、realm的名称
    /* 使用4参数构造方法构造SimpleAuthenticationInfo对象
            SimpleAuthenticationInfo(Object principal,
             Object hashedCredentials,
             ByteSource credentialsSalt,
             String realmName)
         其中第二个参数:Object hashedCredentials 为散列过的用户密码
         其中第三个参数:ByteSource credentialsSalt 即为对用户密码散列时用到的salt,
         这里作为盐的字符串采用用户的账号名,与加密时使用的salt要一致才能认证成功
        */
    ByteSource salt = ByteSource.Util.bytes(user.getUsername());
    //返回AuthenticationInfo对象
    return new SimpleAuthenticationInfo(user.getUsername(),
                                        user.getPassword(), salt, getName());
}

猜你喜欢

转载自blog.csdn.net/ailaohuyou211/article/details/130394419