乐趣区

关于shiro:Shiro学习笔记一-基本概念与使用

Shiro 能帮忙咱们干什么?

Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications. -《Apache Shiro 官网》

Apache Shiro 是 Java 畛域内的一款简略易用而又弱小的一款平安框架,次要用于登录验证、受权、加密、会话治理。Shiro 领有简略利用的 API,您能够快送的应用它来爱护您的利用,不论是小到手机利用还是到大的 Web 企业级利用。

从下面的一段话咱们能够提取到以下信息:

  • Shiro 简略易用而又弱小
  • 次要利用于登录验证、受权、加密、会话治理。

第一点须要在中应用中缓缓领会,第二点是咱们次要关注的,这里咱们一点一点的讲。

登录验证 authentication 与 会话治理

这里咱们回顾一下 HTTP 协定和 Servlet, 晚期的 HTTP 协定是无状态的, 这个无状态咱们能够这么了解,你一分钟前拜访和当初拜访一个网站,服务端并不意识你是谁,然而对于 Web 服务端开发者来说, 这便无奈实现访问控制,某些信息只能登录用户能力看,各自看各自的。这着实的有点限制住了 Web 的倒退,为了让 HTTP 协定有状态,RFC-6235 提案被取得批准,这个提案引入了 Cookie, 是服务器发送到用户浏览器并保留在本地的一小块数据,它会再浏览器下次向同一服务器再发动申请时被携带并发送到服务器上,服务端就能够实现“辨认”用户了。

在原生的 Servlet 场景中如下:

代码示例:

/**
 * 拦挡所有的申请
 */
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {

    /**
     * 不重写 init 办法, 过滤器无奈起作用
     * @param filterConfig
     * @throws ServletException
     */
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {System.out.println("----login check filter init-----");
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest servletRequest = (HttpServletRequest) request;
        HttpSession session = servletRequest.getSession();
        // 这里采取了临时写死, 目前只放行登录申请的 URL
        String requestUri = servletRequest.getRequestURI();
        if ("/login".equals(requestUri)){
            // 放行, 此申请进入下一个过滤器
            chain.doFilter(request,response);
        }else {Object attribute = session.getAttribute("currentUser");
            if (Objects.nonNull(attribute)){chain.doFilter(request,response);
            }else {request.getRequestDispatcher("/login.jsp").forward(request,response);
            }
        }
    }
}
@WebFilter(urlPatterns = "/login")
public class LoginFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {System.out.println("------login filter init-----");
    }

    /**
     * 由登录过滤器来执行登录验证操作
     * 要求用户名和明码不为空的状况下才进行下一步操纵
     * 此处省略判空操作
     * 只做模仿登录
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest servletRequest = (HttpServletRequest) request;
        String userName = (String) servletRequest.getAttribute("userName");
        String password = (String)servletRequest.getAttribute("password");
        // 这里伪装去数据库去查账号和明码
        HttpSession session = servletRequest.getSession();
        // 生成 session,
        session.setAttribute("currentUser",userName + password);
        // 放到下一个过滤器, 如果这是最初一个过滤器, 那么这个申请会被放行到对应的 Servlet 中
        chain.doFilter(request,response);
    }
}
@WebServlet(value = "/hello")
public class HttpServletDemo extends HttpServlet {
   
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {System.out.println("hello world");
        super.doGet(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {super.doPost(req, resp);
    }
}

后面提到, 为了实现让 HTTP 协定有“状态”, 浏览器引入了 Cookie, 寄存服务端发送给客户端的数据, 为了使服务端辨别不同的用户, 服务端程序引入了 Session 这个概念,浏览器首次申请服务端,服务端会在 HTTP 协定中通知客户端,须要再 Cooke 外面记录一个 SessionID,当前每次申请把这个 SessionId 发送到服务器,这样服务器就能辨别不同的客户端了。

有了这样的对应关系,咱们就能够在 Session 中保留以后用户的信息。其实咱们这里只做了登录,还没有做登出,登出应该将对应的 Session 中革除掉。这套逻辑 Shiro 帮咱们做好了,那天然是极好的,但仅仅是如此的话,还不足以让咱们应用 Shiro, 咱们接着翻官网文档。

一个小插曲

因为很久没写 Servlet 了,这次去下载 Tomcat,下了一个 10.0 的, 然而发现 10.0 版本跟 JDK 8 不太兼容,折腾了许久, 申请都没方法达到 Servlet,要不就是报 Servlet 初始化失败,最高只好退回 8.5 版本,然而还是发现申请达到不了我写的 Servlet 中, 我是基于注解的模式配置的映射,然而 web.xml 的有个属性我遗记了,那就是 metadata-complete, 此属性为 true, 不会扫描基于注解的 Servlet,此注解为 false, 才会启用此 Servlet。

Shiro 的个性

Authentication 和 Session Management: Shiro 帮咱们做好了, 那天然是极好的。

Cryptography: 加密, 其实 Java 规范库也提供了实现,那 Shiro 能提供一套更简略易用的 API 更好。

Authorization:受权, 这个值得咱们留神一下,写到这里我想起我第一个构建的 Web 零碎,过后只思考了一般的用户,没有思考资源管制的问题,过后曾经快到预约的工夫了,说来惭愧, 只好做了一个十分毛糙的权限管制,为用户增加了一个权限标识字段,1 代表什么角色,2 代表什么角色能看哪些页面,也就是说资源是固定的,相当的生硬。前面才理解到在这方面有一套比拟成熟而天然的 RBAC(Role-Based Access Controle 基于角色的访问控制) 模型,即角色 - 用户 - 权限 (资源),也就是说一个用户领有若干角色,每一个角色领有若干权限和资源,这样咱们就实现了权限和用户的解耦合。

基本概念咱们大抵阐述完之后,咱们来进一步深刻的看这些个性:

  • Authentication 登录

    • Subject Based – Almost everything you do in Shiro is based on the currently executing user, called a Subject.
      And you can easily retrieve the Subject anywhere in your code. This makes it easier for you to understand and work with Shiro in your applications.

      基于主体,在 Shiro 中做的任何事件都基于以后正在流动的用户,在 Shiro 中称之为主体,你能够在代码的任何中央取到以后的主体。

      这将让你在应用和了解 shiro 变的轻松起来。

    • Single Method call – The authentication process is a single method call.
      Needing only one method call keeps the API simple and your application code clean, saving you time and effort.

      简略的办法调用,非常简单,节省时间和精力。

    • Rich Exception Hierarchy – Shiro offers a rich exception hierarchy to offered detailed explanations for why a login failed.
      The hierarchy can help you more easily diagnose code bugs or customer services issues related to authentication. In addition, the richness can help you create more complex authentication functionality if needed.

      丰盛的异样体系,Shiro 提供了齐备的异样体系来解释登录为什么失败。这个异样体系能够帮忙诊断定制服务的相干 bug 和 issue。除此之外,还能够帮忙创立性能更加丰盛的利用。

    • ‘Remember Me’built in – Standard in the Shiro API is the ability to remember your users if they return to your application.
      You can offer a better user experience to them with minimal development effort.

      记住我,规范的 Shiro API 提供了记录明码的性能,只需大量的配置,就能给用户提供更佳的体验。

    • Pluggable data sources – Shiro uses pluggable data access objects (DAOs), called Realms, to connect to security data sources like LDAP and Active Directory.
      To help you avoid building and maintaining integrations yourself, Shiro provides out-of-the-box realms for popular data sources like LDAP, Active Directory, and JDBC. If needed, you can also create your own realms to support specific functionality not included in the basic realms.

      可插拔的数据源,Shiro 提供了一个可插拔的数据权限对象,在 shiro 中咱们称之为 Realms,咱们用这个去平安的连贯像 LDAP、Active Directory 的数据源。

    为了防止开发者做反复的工作,Shiro 提供了开箱即用的连贯指定数据源的 Realm 是,像 LDAP、Active Directory、JDBC。如果你须要你也能够创立自定义的 Realms。

    • Login with one or more realms – Using Shiro, you can easily authenticate a user against one or more realms and return one unified view of their identity.
      In addition, you can customize the authentication process with Shiro’s notion of an authentication strategy. The strategies can be setup in configuration files so changes don’t require source code modifications – reducing complexity and maintenance effort.

      反对一个和多个 Realm 登录,应用 Shiro 你能够轻松的实现用户应用多个 Realm 登录,并且形式对立。除此之外,你也能够能够应用 Shiro 的身份验证策略自定义登录过程,验证策略反对写在配置文件中,所以当验证策略扭转的时候,不须要更改代码。

看来下面的个性概述,你会发现原来登录须要思考这么多,原先咱们的视角可能只在数据库的数据源,事实上对于 WEB 零碎来说还能够引入其余数据源,然而你不必放心,这么多须要思考的货色,原先你本人来写登录,可能还会思考脱漏的中央,然而 Shiro 都帮你写好。咱们还有什么理由不学它呢。这里还是须要重点讲一下 Shiro 的 Realms 概念,咱们回顾一下 JDBC,能够让咱们 Java 程序员写一套 API 就能做到跨数据库,那多个数据源呢,咱们是否也形象出一个接口,做到登录的时候跨数据源呢?其实 Realms 就是这个思路,也形象出了一个接口,对接不同的数据源来实现登录认证:

实质上是一个特定的平安 DAO(Data Access Object), 封装与数据源连贯的细节,失去 Shiro 所须要的相干数据。在配置 Shiro 的时候,咱们必须指定至多 Realm 来实现认证 (authentication) 和受权 (authorization).

咱们只重点介绍一个个性来领会 Shiro 的弱小,其余个性咱们只简略介绍转瞬的点:

  • Cryptography:Shiro 指出 Java 的明码体系比拟难用 (The Java Cryptography Extension (JCE) can be complicated and difficult to use unless you’re a cryptography expert, Java 的扩大十分难用,当然你要是个明码专家就当我没说 ),Shiro 设计的密码学的 API 更简略易用。

    PS: 其实还指出了 Java 密码学扩大的一些问题,算是对 Java 密码学相干库的吐槽了。有兴致能够去看看,咱们这里不做过多介绍。

  • SessionManagement

    能够被用于 SSO,获取用户登录和退出都相当不便。SSO 是单点登录,不便的和各种利用零碎做集成。

  • Authorization

    上面是受权的几个经典问题:

    这个用户是否能够编辑这个账号

    这个用户是否有权限看这页面

    这个用户是否有权限应用这个按钮

    Shiro 答复了以上问题,并且非常灵活、简略、容易应用。

    Shiro 帮咱们做了这么多,而且简略,咱们能够省了很多工作,这就是学习 Shiro 的理由。

Shiro 外围概念概述

在 Shiro 中的架构中有三个次要概念:

  • subject(以后主体, 能够了解为以后登录用户,)

    在 Shiro 中能够应用上面代码来获取以后登录用户:

  Subject currentUser = SecurityUtils.getSubject();
  • SecurityManager

为所有用户提供平安爱护,内嵌了很多平安组件,那么如何设置它呢?也取决于不同的环境, Web 程序中通常是在 Web.xml 中指定 Shiro 的 Servlet 过滤器,这就实现了一个 SecurityManager 实例,其余类型的应用程序咱们也有其余选项。

  • Realms

Realm 事实上是 Shiro 和你利用平安数据的桥梁或连接器。

三者之间的关系:

用起来

首先引入 maven 依赖:

  <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.9.0</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>1.7.21</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>jcl-over-slf4j</artifactId>
      <version>1.7.21</version>
      <scope>test</scope>
    </dependency>

当然是从 Hello World 开始

    public static void testAuthentication(){
        // 设置 SecurityManager 
        // SecurityManager 负责将用户提交过去的 username、password 和 realm 中的进行比照
        // 判断是否能够登录
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
    
        // 这是一个简略的 Realm, 间接在代码外面存储账号和明码
        SimpleAccountRealm simpleAccountRealm = new SimpleAccountRealm();
        simpleAccountRealm.addAccount("hello world","hello world");
        // 将 realm 纳入到 DefaultManager 的管辖之下
        defaultSecurityManager.setRealm(simpleAccountRealm);
        // 通过此办法设置 SecurityManager
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        
        // 用户登录和明码凭证
        UsernamePasswordToken token = new UsernamePasswordToken("hello world", "hello world");
        
        // 获取 subject
        Subject subject = SecurityUtils.getSubject();
        
        // 由 subject 将 token 提交给 SecurityManager
        subject.login(token);
        // 登录胜利会返回 true
        System.out.println("login status:"+subject.isAuthenticated());
        // 退出
        subject.logout();
        // 退出之后是 false
        System.out.println("login status:"+subject.isAuthenticated());
    }

加密示例

Cryptography is the process of hiding or obfuscating data so prying eyes can’t understand it. Shiro’s goal in cryptography is to simplify and make usable the JDK’s cryptography support.

密码学是暗藏或混同数据的过程,避免数据被窃取。Shiro 在密码学方面的指标是简化 JDK 规范密码学库的应用

接下来让咱们下用 Shiro 的密码学相干的 API 有多简略易用.

  • MD5 JDK 规范库的实现
 private static void testMD5JDK() {
        try {
            String code = "hello world";
            // MD5 是 MessageDigest 的第五个版本
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] targetBytes = md.digest(code.getBytes());
            // 输入的是 MD5 的十六进制模式
            System.out.println(targetBytes);
        } catch (NoSuchAlgorithmException e) {e.printStackTrace();
        }
    }
  • Shiro 的实现:
private static void testMD5Shiro() {String hex = new Md5Hash("hello world").toHex();
    System.out.println(hex.getBytes());
}

这么一看 Shiro 的实现的确简略一些,更直观。

受权示例

在 Shiro 中将受权分成以下两种:

  • Permission Defined

Permissions are the most atomic level of a security policy and they are statements of functionality. Permissions represent what can be done in your application. A well formed permission describes a resource types and what actions are possible when you interact with those resources. Can you open a door? Can you read a file? Can you delete a customer record? Can you push a button?

Common actions for data-related resources are create, read, update, and delete, commonly referred to as CRUD.

It is important to understand that permissions do not have knowledge of who can perform the actions– they are just statements of what actions can be performed.

准许是安全策略的原子级别,体现为性能的申明。准许的意思是你能够在这个零碎中做什么。模式良好的准许形容了资源类型以及能够对这些资源进行的操作。比方: 你是否能够关上这扇门?你是否能够读一个文件? 你是否能够删除一个客户记录?你是否能点击按钮?

一般来说对资源的操作有新增、读取、更新、删除,这些操作通常被称作为 CRUD。

肯定要了解,准许是不晓得是谁能够操作这些资源的,它们只是阐明这些资源能够执行哪些操作。

​ 权限的粒度:

  1. Resource Level – This is the broadest and easiest to build. A user can edit customer records or open doors. The resource is specified but not a specific instance of that resource.

资源级别: 这是最宽泛的而且是最容易构建的。用户能够编辑客户记录或者关上一扇门。资源指定了,然而没有指定到具体的人祸角色上。

  1. Instance Level – The permission specifies the instance of a resource. A user can edit the customer record for IBM or open the kitchen door.

实例级别: 某个准许指定了具体的人或者角色, 某个用户能够编辑 IBM 的用户记录或关上厨房的门。

  1. Attribute Level – The permission now specifies an attribute of an instance or resource. A user can edit the address on the IBM customer record.

属性级别: 某人被容许编辑资源的某个属性,某个用户能够编辑 IVM 用户记录的地址。

  • Role Defined

In the context of Authorization, Roles are effectively a collection of permissions used to simplify the management of permissions and users. So users can be assigned roles instead of being assigned permissions directly, which can get complicated with larger user bases and more complex applications. So, for example, a bank application might have an administrator role or a bank teller role.

在受权的高低门,角色实际上是权限的汇合,简化权限和用户的治理,因而用户能够被调配角色,而不是间接被调配权限,因为间接调配权限这对于更大的用户群和更简单的应用程序来说会比较复杂。

Shiro 反对如下两种角色:

  • Implicit Roles 隐式角色

大多数人的角色在咱们眼中属于是隐式角色,隐含了一组权限,通常的说,如果你具备管理员的角色,你能够查看患者数据。如你具备“银行出纳员”的角色,那么你能够创立账号。

  • Explicit Roles 显式角色

显式角色就是零碎显著调配的权限,如果你能够查看患者数据,那是因为你被调配到了“管理员角色”的“查看患者数据”权限。

小小总结一下

下面是我翻译的 Shiro 对角色和权限的阐述,权限的话能够了解为对某个资源的 CRUD, 粒度级别有整个资源,比方一行记录的操纵权限,这行记录只有某个人才能操纵,某个人只能操纵这行记录的一部分。而角色则是权限的汇合, 在我看来是实现权限和用户的解耦,比方我想对一批用户受权,我能够选一个角色,进行批量受权。那么问题又来了,我该怎么做权限管制呢,或者在 Shiro 中判断某个是否有这个角色呢?

  • 咱们能够从 Subject 这个类的办法中判断以后用户是否具备某个角色或者具备某个权限

    subject.hasRole("admin") // 以后用户是否有 admin 这个角色
    subject.isPermitted("user:create") // 判断以后用户是否容许增加用户 
  • JDK 的注解, 在办法中加注解
// 判断以后角色是否有 admin 这个角色
@RequiresRoles("admin")
private static void requireSpecialRole() {}
 // 判断以后用户是否容许增加用户
@RequiresPermissions("user:create")
private static void requireSpecialRole() {}
  • JSP 标签 (前后端拆散时代了, 这个不做介绍)

Shiro 定义了一组权限形容语法来形容权限,格局为: 资源:(create || update || delete ||query)。

示例:

user:create,update // 示意具备创立和更新用户的权限
user:create,update:test110 // ID 为 test110 有 创立和更新用户的权限   

自定义 Realm

咱们后面始终再说 Realm 是 Shiro 和用户数据之间的桥梁, 咱们大抵看下这个登录过程,重点来领会一下桥梁:

  1. subject.login(token). 目前 Subject 只有一个实现类,那就是 DelegatingSubject。

咱们下面用的是 DefaultSecurityManager, 所以咱们须要进入该类的办法来看是怎么执行登录的, DefaultSecurityManager.login 调用 authenticate(token) 来获取用户信息,authenticate() 办法来自 DefaultSecurityManager 的父类 AuthenticatingSecurityManager, 该类领有一个 Authenticator 成员变量,由该成员变量调用 authenticate,获取用户信息

这在某种成都上很像是模板办法模式, 父类定义骨架或通用办法,子类做调用。Authenticator 是一个接口有许多实现类,然而调用的 authenticate 就只有两个实现:

Realm 这个接口太顶层了,咱们要做本人的 Realm 的话还是找一个抽象类,咱们下面的 SimpleAccountRealm 就是继承自 AuthorizingRealm,重写了 doGetAuthenticationInfo 用于登录验证,doGetAuthorizationInfo 用于做权限验证。咱们本人写的如下:

public class MyRealm extends AuthorizingRealm {

    /**
     * 设置 Realm 的名称
     */
    public MyRealm() {super.setName("myRealm");
    }

    /**
     * 咱们这个自定义 reamls 就能实现权限管制了
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 获取登录凭证
        String userName = (String) principals.getPrimaryPrincipal();
        // 伪装这 roles 是从数据库中查的
        Set<String> roles  = new HashSet<>();
        // 伪装是从数据库查的
        Set<String> permissions  = new HashSet<>();
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setStringPermissions(permissions);
        simpleAuthorizationInfo.setRoles(roles);
        return simpleAuthorizationInfo;
    }

    /**
     * 轻易写个空实现
     * @param userName
     * @return
     */
    private String getPassWordByUserName(String userName) {return "";}

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 获取账号
        String userName = (String) token.getPrincipal();
        String password = null;
        if (userName != null && userName != ""){password = getPassWordByUserName(userName);
        }else {return null;}
        if (Objects.isNull(password)){return null;}
        // 伪装去查了数据库
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName,password,"myRealm");
        return simpleAuthenticationInfo;
    }
}

写在最初

我记得刚开始学 Shiro 的时候是去 B 站搜对应的视频, 然而视频大多都是从 Shiro 的根本组件开始讲,我其实只是想晓得 Shiro 能帮我干啥,去看教程也是先从 Shiro 是一个平安框架讲起,而后讲架构,看了半天视频我只播种了一堆名词,我想看的货色都没看到,切实耗费我的急躁。所以本篇文章是以问题为导向,即 Shiro 最终帮咱们做了什么,同时也调整了一些文章介绍格调,省略掉一些比拟大的词,这篇文章献给当初在网上找 Shiro,找了半天也没找到合情意的教程的本人。

参考资料

  • Shiro 官网文档 https://shiro.apache.org/
  • RBAC 用户、角色、权限、组设计方案 https://zhuanlan.zhihu.com/p/…
  • Shiro 平安框架【疾速入门】就这一篇!https://zhuanlan.zhihu.com/p/…
  • 了解 Apache Shiro 中的权限 – 官网 https://www.jianshu.com/p/1e6…
退出移动版