Shiro

资料

概念

基本功能:

在这里插入图片描述
以下内容翻译自官网:https://shiro.apache.org/introduction.html

Shiro的目标是Shiro开发团队所说的“应用程序安全的四个基石” - 身份验证,授权,会话管理和密码学:

  • 认证:有时称为“登录”,这是证明用户是他们所说的身份的行为。

  • 授权:访问控制的过程,即确定“谁”可以访问“什么”。

  • 会话管理:管理特定于用户的会话,即使在非 Web 或 EJB 应用程序中也是如此。

  • 密码学:使用加密算法确保数据安全,同时仍然易于使用。

在不同的应用程序环境中,还有一些其他功能可以支持和加强这些问题,特别是:

  • Web 支持:Shiro 的 Web 支持 API 有助于轻松保护 Web 应用程序。

  • 缓存:缓存是Apache Shiro的API中的一级公民,以确保安全操作保持快速高效。

  • 并发:Apache Shiro支持多线程应用程序及其并发功能。

  • 测试:测试支持可帮助您编写单元测试和集成测试,并确保代码按预期得到保护。

“运行方式”:允许用户假定其他用户的身份(如果允许)的功能,有时在管理方案中很有用。

“记住我”:跨会话记住用户的身份,以便他们只需要在强制要求时登录。

架构原理

以下内容翻译自官网:https://shiro.apache.org/architecture.html

在这里插入图片描述

  • Subject:正如我们在教程中提到的,Subject本质上是当前执行用户的特定于安全的“视图”。“用户”一词通常指的是人,而主体可以是人,但它也可以代表第三方服务、守护帐户、cron作业或任何类似的东西——基本上是当前与软件交互的任何东西。
    主题实例都绑定到(并且需要)SecurityManager。当您与Subject交互时,这些交互将转换为与SecurityManager的特定于主题的交互。

  • SecurityManager: SecurityManager是Shiro架构的核心,充当一种“保护伞”对象,协调内部安全组件,共同形成对象图。然而,一旦为应用程序配置了SecurityManager及其内部对象图,它通常就不会受到影响,应用程序开发人员几乎把所有时间都花在Subject API上。
    我们将在后面详细讨论SecurityManager,但重要的是要认识到,当您与Subject交互时,实际上是幕后的SecurityManager为任何Subject安全操作做了所有繁重的工作。这反映在上面的基本流程图中。

  • Realms: Realms充当Shiro和应用程序安全数据之间的“桥梁”或“连接器”。当需要实际与安全相关数据(如用户帐户)交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从为应用程序配置的一个或多个realm中查找其中的许多内容。
    从这个意义上讲,Realm本质上是一个特定于安全的DAO:它封装了数据源的连接细节,并根据需要将相关数据提供给Shiro。配置Shiro时,必须指定至少一个Realm用于身份验证和/或授权。SecurityManager可以配置多个realm,但至少需要一个。

在这里插入图片描述

  • Subject:当前与软件交互的实体(用户、第三方服务、cron作业等)的特定于安全的“视图”。

  • SecurityManager:如前所述,SecurityManager是Shiro架构的核心。它主要是一个“伞形”对象,用于协调其托管组件,以确保它们顺畅地协同工作。它还管理Shiro对每个应用程序用户的视图,因此它知道如何对每个用户执行安全操作。

  • Authenticator: Authenticator是负责执行和响应用户的身份验证(登录)尝试的组件。当用户尝试登录时,Authenticator将执行该逻辑。验证者知道如何与一个或多个存储相关用户/帐户信息的域进行协调。从这些领域获得的数据用于验证用户的身份,以确保用户确实是他们所说的那个人。

    • Authentication Strategy:如果配置了多个Realm, AuthenticationStrategy将协调各个Realm以确定身份验证尝试成功或失败的条件(例如,如果一个域成功而其他域失败,那么尝试成功吗?所有领域都必须成功吗?只有第一个?)
  • Authorizer :授权器是负责确定应用程序中用户访问控制的组件。它是一种机制,最终决定是否允许用户做某事。与验证者一样,授权者也知道如何与多个后端数据源协调以访问角色和权限信息。授权人使用此信息来确定是否允许用户执行给定的操作。

  • SessionManager: SessionManager知道如何创建和管理用户会话生命周期,为所有环境中的用户提供健壮的会话体验。这是安全框架世界中的一个独特特性——Shiro能够在任何环境中本地管理用户会话,即使没有可用的Web/Servlet或EJB容器。默认情况下,Shiro将使用现有的会话机制(例如Servlet容器),但如果没有,例如在一个独立的应用程序或非web环境中,它将使用其内置的企业会话管理来提供相同的编程体验。SessionDAO的存在是为了允许使用任何数据源持久化会话。

    • SessionDAO代表SessionManager执行会话持久性(CRUD)操作。这允许将任何数据存储插入到Session Management基础设施中。
  • CacheManager (org.apache.shiro.cache.CacheManager)创建并管理其他Shiro组件使用的Cache实例生命周期。由于Shiro可以访问许多后端数据源进行身份验证、授权和会话管理,因此缓存一直是该框架中的一流体系结构特性,可以在使用这些数据源时提高性能。任何现代开源和/或企业缓存产品都可以插入到Shiro中,以提供快速高效的用户体验。

  • Cryptography :是企业安全框架的自然补充。Shiro的加密包包含易于使用和理解的加密密码,哈希(又名摘要)和不同的编解码器实现的表示。这个包中的所有类都经过精心设计,非常易于使用和理解。任何使用过Java原生加密支持的人都知道,它是一种难以驯服的动物。Shiro的加密api简化了复杂的Java机制,使加密技术易于为普通人使用。

  • Realms:如上所述,Realms是Shiro和应用程序安全数据之间的“桥梁”或“连接器”。当需要实际与安全相关数据(如用户帐户)交互以执行身份验证(登录)和授权(访问控制)时,Shiro会从为应用程序配置的一个或多个realm中查找其中的许多内容。您可以根据需要配置任意数量的realm(通常每个数据源一个),Shiro将根据需要与它们协调进行身份验证和授权。

登录认证

概念

(1)身份验证:一般需要提供如身份ID等一些标识信息来表明登录者的身份,如提供email,用户名/密码来证明。

(2)在shiro中,用户需要提供principals(身份)和credentials (证明
给shiro,从而应用能验证用户身份

(3) principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary’ principals,一般是用户名/邮箱/手机号。

(4) credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

(5)最常见的principals和crqdentials组合就是用户名/密码

流程

在这里插入图片描述
在这里插入图片描述

角色授权

概念

(1)授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面
编辑数据/页面澡作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)

(2)主体(Subject):访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。

(3)资源(Resource):在应用中用户可以访问的· URL,比如访问JSP页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。

(4)权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权权限控制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许。

(5)Shiro支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权艮,即实例级别的)

(6)角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程币等都是角色,不同的角色拥有一组不同的权限

流程

在这里插入图片描述

在这里插入图片描述

代码

大致流程

shiro配置解读(ShiroConfig)

1、自定义realm:

  • MyRealm是一个自定义类,继承AuthorizingRealm类
    在这里插入图片描述

2、创建安全管理器

  • 这里需要用到刚刚创建的realm
    在这里插入图片描述

3、ShiroFilter过滤器

  • 需要用到第2步的安全管理器
  • 下图中map的value可取值:
    • anon:无需认证就可以访问
    • authc:必须认证了才能让问
    • user:必须拥行记住我功能才能用
    • perms:圳有对某个资源的权限才能访问;
    • role:拥有某个角色权限才能访问
    • logout:设置登出的路径
      在这里插入图片描述

登录/认证:

我们在配置类配置了登录页的地址
在这里插入图片描述
当访问登录页,或访问某个需要认证才能访问的资源时,就会跳转到登录页,然后进入登录认证流程,大致流程为:

1、填写登录表单(login.html)
在这里插入图片描述
2、来到controller:(UserController )

  • 在这里,我们需要获取到加密后的密码,并执行subject.login方法
    在这里插入图片描述

3、来到自定义Realm:(MyRealm)

  • 自定义Realm实现AuthorizingRealm
  • 认证对应于doGetAuthenticationInfo方法,另一个是授权用的
  • 在这里,我们只需告诉shiro真实的密码应该是数目即可。
  • SimpleAuthenticationInfo的参数:
    • 第一个,类似于身份标识符,在这存什么,在其他类就能取到什么
    • 第二个,用户对应的密码
    • 第三个,空串即可
  • 如果这里传的密码,和第2步controller里面那传的密码相同就认证成功(shiro来完成)
    在这里插入图片描述

授权

1、配置了需要某个权限才能访问的资源:(两种方式)

  • 在配置类中配置:(ShiroConfig )
    在这里插入图片描述
  • 使用注解配置:(UserController)
    在这里插入图片描述

2、当访问对应资源时,将会来到自定义Realm:(MyRealm )

  • 对应的方法是:doGetAuthorizationInfo
  • 我们只用告诉shiro该用户有哪些权限即可,其他的由shiro来完成
    在这里插入图片描述

详细代码:

pom

  • (spring-boot我用的2.2.6.RELEASE)
        <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring-boot-starter -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>1.10.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/user/login" method="post">
    提示:用户名任意,密码是1234即可,root用户有user:*权限<br>
    username:<input name="username"><br>
    username:<input name="password"><br>
    <input type="submit">
</form>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>首页</h1>
<hr>
<a href="/user/add">添加用户</a>|
<a href="/user/delete">删除用户</a>|
<a href="/user/logout">退出登录</a>
</body>
</html>

UserController

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@Slf4j
public class UserController {
    /**
     * 去填写登录表单
     *
     * @return
     */
    @GetMapping({"/user/login", "/"})
    public String login() {
        return "redirect:/login.html";
    }

    /**
     * 去首页(需要先登录,具体的请看:ShiroConfig)
     *
     * @return
     */
    @GetMapping("/user/index")
    public String index() {
        return "redirect:/index.html";
    }

    /**
     * 添加用户(需要user:add权限),现假设有权限就返回添加成功。使用配置类ShiroConfig实现。
     * 相关代码:map.put("/user/add","perms[user:add]");
     */
    @GetMapping("/user/add")
    @ResponseBody
    public String add() {
        return "添加成功...";
    }
    /**
     * 删除用户(需要user:delete权限),现假设有权限就返回删除成功。使用注解实现。
     * 相关注解:@Requires*
     */
    @GetMapping("/user/delete")
    @ResponseBody
    @RequiresPermissions("user:delete")
    public String delete() {
        return "删除成功...";
    }

    /**
     * 登录(认证),具体逻辑在:MyRealm
     *
     * @param username
     * @param password
     * @return
     */
    @PostMapping("/user/login")
    public String login(String username, String password) {
        try {
            //获取加密后的密码
            password = new Md5Hash(password, "salt", 3).toHex();
            //获取主题对象
            Subject subject = SecurityUtils.getSubject();
            //登录(第三个参数,是否记住我)
            subject.login(new UsernamePasswordToken(username, password));
            System.out.println(username + ",登录成功!!!");
            return "redirect:/index.html";
        }
        //以下是可能出现的异常...
        catch (UnknownAccountException uae) {
            System.out.println("用户名不存在");
        } catch (IncorrectCredentialsException ice) {
            System.out.println("密码不正确");
        } catch (LockedAccountException lae) {
            System.out.println("帐户锁定异常");
        } catch (AuthenticationException ae) {
            System.out.println("其他登录异常...");
        }
        return "redirect:/login.html";
    }
}

ShiroConfig

import com.ljy.realm.MyRealm;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    /**
     * ShiroFilter过滤所有请求
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //给ShiroFilter配置安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        //配置系统公共资源、受限资源
        Map<String, String> map = new HashMap<>();
        /**
         * anon:无需认证就可以访问
         * authc:必须认证了才能让问
         * user:必须拥行记住我功能才能用
         * perms:圳有对某个资源的权限才能访问;
         * role:拥有某个角色权限才能访问
         * logout:设置登出的路径
         */
        //无需认证
        map.put("/user/login","anon");
        //需要认证(登录)
        map.put("/user/index","authc");
        //需要有某个权限
        map.put("/user/add","perms[user:add]");
        //设置登出的路径
        map.put("/user/logout","logout");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        // 设置认证(登录)界面路径
        shiroFilterFactoryBean.setLoginUrl("/user/login");

        return shiroFilterFactoryBean;
    }

    /**
     * 创建安全管理器
     */
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm,也可以通过setRealms设置多个
        securityManager.setRealm(realm);

        //设置认证策略示例:
        ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
        //有三种策略:
        //  AtLeastOneSuccessfulStrategy(至少有一个realm成功)
        //  FirstSuccessfulStrategy(有一个成功即可,后面的将被忽略)
        //  AllSuccessfulStrategy(所有的都要成功)
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());

        return securityManager;
    }

    /**
     * 创建自定义Realm
     */
    @Bean
    public Realm realm() {
        return new MyRealm();
    }
}

MyRealm

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定义Realm
 */
public class MyRealm extends AuthorizingRealm {
    /**
     * 授权
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //获取用户名
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        //根据用户名查权限
        List<String> perms = getPermsByUsername(username);
        if (perms != null && !perms.isEmpty()){
            info.addStringPermissions(perms);
        }

        return info;
    }

    /**
     * 认证
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();

        //根据用户名获取对应的密码
        String password = getPasswordByUsername(username);

        //剩下的事,交给shiro
        //第一个参数,是一个对象(例如,可以使用Admin、User等),在其它类中可以使用SecurityUtils.getSubject().getPrincipal()获取。
        // 这里为了简单,将用户名作为主题
        return new SimpleAuthenticationInfo(username, password, "");
    }

    /**
     * 根据用户名查密码。一般通过Service、Dao查询数据库进行实现。
     * 这里为了简单,将密码同意设置为1234(md5加密)
     *
     * @param username
     * @return
     */
    public String getPasswordByUsername(String username) {
        //加盐,盐值是“salt”,迭代三次
        return new Md5Hash("1234", "salt", 3).toHex();
    }

    /**
     * 根据用户名查询用户的权限。一般通过Service、Dao查询数据库进行实现。
     * 这里为了简单,就给root用户user:*权限
     * @param principal
     * @return
     */
    public List<String> getPermsByUsername(String principal) {
        if (principal.equals("root")){
            ArrayList<String> list = new ArrayList<>();
            list.add("user:*");
            return list;
        }
        return null;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值