JWT的权限控制与Shiro入门

JWT的权限控制与Shiro入门

一、前端权限控制

1.1介绍

在vue工程中,需要根据登录用户所拥有的权限信息动态的加载菜单列表(路由列表)

  1. 登录成功后获取用户信息,包含权限列表(菜单权限,按钮权限等)
  2. 根据用户的权限,去动态的渲染页面(根据路由名称和权限标识比较)
  3. 页面按钮权限通过自定义方法控制可见性

<span>JWT的权限控制与Shiro入门</span>

1.2具体

  1. 此时需要准备所有使用该系统的用户的等级:
  • saas管理员—-拥有所有权限
  • 企业管理员—-创建租户企业的权限
  • 普通用户—–被分配的权限
  1. 此时前端访问登录界面后,就开始渲染页面,使用了路由
  2. 路由首先把公共的组件路由出来,然后调用前置构字函数(beforeEach),获取用户的信息存储起来。
  3. 最后判断当前的模块路由是否具有访问权限,来决定是否渲染此路由。

<span>JWT的权限控制与Shiro入门</span>

二、有状态服务和无状态服务

对服务器程序来说,究竟是有状态服务,还是无状态服务,其判断依据——两个来自相同发起者的请求在服务器端是否具备上下文关系。

2.1无状态服务

无状态服务对于客户端的单次请求,不会依赖其他请求的数据。即:处理请求的所需信息全部包含在该请求里。

如:cookie保存token的方式传输数据,对于服务端来讲,每次只是验证token是否合法,是否是我这个服务端颁发出的内容,从而辨别是否有权限,而不会保存用户的信息。

<span>JWT的权限控制与Shiro入门</span>

2.2有状态服务

有些数据会被记录在服务端,先后的请求是有关联的

当客户端登录后,服务端会颁发一个sessionId,此时在服务端就存储了该sessionId对应的session信息。客户端一般把返回的sessionId存储在cookie中。从而将http的无状态服务变相转换为有状态服务。

<span>JWT的权限控制与Shiro入门</span>

三、基于JWT的API鉴权

3.1JWT(JSON Web Token)

3.1.1 介绍

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

3.1.2JWT的构成

第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

1.header

jwt的头部承载两部分信息:

  • 声明类型,这里是jwt
  • 声明加密的算法 通常直接使用 HMAC SHA256

完整的头部就像下面这样的JSON:

{
  'typ': 'JWT',
  'alg': 'HS256'
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

2.playload

载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

  • 标准中注册的声明
  • 公共的声明
  • 私有的声明

定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后将其进行base64加密,得到Jwt的第二部分。

3.signature

jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

注意

secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

3.1.3JWT与Session的差异

  1. Session是在服务器端的,而JWT是在客户端的。
  2. Session方式存储用户信息的最大问题在于要占用大量服务器内存,增加服务器的开销。而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。
  3. Session的状态是存储在服务器端,客户端只有session id;而Token的状态是存储在客户端。

3.1.4 工作流程

-每一次请求都需要token -Token应该放在请求header中 -我们还需要将服务器设置为接受来自所有域的请求,用Access-Control-Allow-Origin: *

<span>JWT的权限控制与Shiro入门</span>

3.1.5 用Token的好处

  • 无状态和可扩展性:Tokens存储在客户端。完全无状态,可扩展。我们的负载均衡器可以将用户传递到任意服务器,因为在任何地方都没有状态或会话信息。
  • 安全:Token不是Cookie。(The token, not a cookie.)每次请求的时候Token都会被发送。而且,由于没有Cookie被发送,还有助于防止CSRF攻击。
    CSRF(Cross-site request forgery),中文名称:跨站请求伪造。CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。
  • token在一段时间以后会过期,这个时候用户需要重新登录。这有助于我们保持安全。

在spring拦截器中,进行拦截

四、Shiro安全框架

<span>JWT的权限控制与Shiro入门</span>

4.1介绍

shiro 是一个功能强大和易于使用的Java安全框架,为开发人员提供一个直观而全面的解决方案的认证,授权,加密,会话管理。

4.2 作用

  • Authentication:验证用户核实身份
  • Authorization:对用户进行访问控制:如判断用户是否被允许做某件事
  • SessionManager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储

4.3 特点

Web Support:Web支持,可以非常容易的集成到Web环境;

Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;

Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;

Testing:提供测试支持;

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

SSO:单点登录功能

4.4 快速入门

1.导入依赖

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.3.2</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

2.创建配置文件

<span>JWT的权限控制与Shiro入门</span>

[users]
#模拟从数据库查询用户
#数据格式: 用户名=密码
zhangsan=123456
lisi=1234

3.测试login

 /**
     * 测试用户认证
     *  认证: 用户登录
     *  1.通过配置文件创建SecurityManagerFactory
     *  2.通过工厂获取securityManager
     *  3.将securityManager绑定到当前运行环境
     *  4.从当前运行环境中构造subject
     *  5.构造shiro登录的数据
     *  6.登录
     */
@Test
public void testLogin(){
    //1.获取工厂
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-test-1.ini");
    //2.获取安全管理
    SecurityManager securityManager = factory.getInstance();
    //3.绑定
    SecurityUtils.setSecurityManager(securityManager);
    //4.构造subject
    Subject subject = SecurityUtils.getSubject();
    //5.构造数据
    String username = "zhangsan";
    String password = "123456";
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    //6,登录
    subject.login(token);
    //判断是否登录成功
    System.out.println(subject.isAuthenticated());
    System.out.println(subject.getPrincipal());
}

4.测试用户权限

[users]
#数据格式: 用户名=密码,角色名列表
zhangsan=123456,role1,role2
lisi=1234,role2
[roles]
#角色
#角色名=权限列表
role1=user:save,user:update
role2=user:find
/**
 * 测试用户权限
 */
@Test
public void testAble(){
    //1.获取工厂
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-test-2.ini");
    //2.获取安全管理
    SecurityManager securityManager = factory.getInstance();
    //3.绑定
    SecurityUtils.setSecurityManager(securityManager);
    //4.构造subject
    Subject subject = SecurityUtils.getSubject();
    //5.构造数据
    String username = "lisi";
    String password = "1234";
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    //6,登录
    subject.login(token);
    //登录成功
    if (subject.isAuthenticated()){
        //查看是否有role1角色
        System.out.println(subject.hasRole("role2"));
        //是否具有权限
        System.out.println(subject.isPermitted("user:find"));
    }
}
//结果:true
//    false

4.5使用

1.导入maven坐标

<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.3.2</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.25</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

2.创建ini配置文件

其中指定编写的realm域的位置,并且将realm绑定到SecurityManager

<span>JWT的权限控制与Shiro入门</span>

[main]
#自定写的realm域,全包名
permReam=gyb.shiro.PermissionRealm
#注册realm到SecurityManager
securityManager.realms=$permReam

3.编写PermissionRealm类,继承AuthorizingRealm,重写方法

package gyb.shiro;

import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

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

/**
 * @Author: 郜宇博
 * @Date: 2021/9/19 16:30
 */
public class PermissionRealm extends AuthorizingRealm {
    /**
     * 一般重写setName方法
     */
    public void setName(){
        super.setName("permissionRealm");
    }
    /**
     * 重写抽象doGetAuthorizationInfo:授权(获取到用户的授权数据)
     * 目的:根据认证数据获取用户的权限信息
     * principals:包含已认证安全数据
     * AuthorizationInfo 授权数据
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("执行授权方法");
        //1.获取安全数据
        //2.通过安全数据内的username查询数据库,获取到权限和角色
        //模拟查数据库的内容
        List<String> perms = new ArrayList<String>();
        perms.add("user:save");
        perms.add("user:update");
        List<String> roles = new ArrayList<String>();
        roles.add("role1");
        roles.add("role2");
        //3.构造返回
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //4.设置权限、角色集合
        info.addStringPermissions(perms);
        info.addRoles(roles);
        return info;
    }

    /**
     * doGetAuthenticationInfo :认证(根据用户名密码登录,将用户数据(安全数据)保存)
     * 目的:比较用户名和密码是否和数据库中的一致,并将安全数据存入到shiro中进行保管
     * AuthenticationToken:登录时构造的token
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行认证方法");
        //1.构造upToken
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        //2.获取输入的用户名密码
        String username = upToken.getUsername();
        String password = new String(upToken.getPassword());
        //3.与数据库比较
        if("123456".equals(password)){
            //一致,向shiro存入安全数据
            //三个参数:1.安全数据,2.密码,3,当前realm域名称
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,password,this.getName());
            return info;
        }else {
            throw new RuntimeException("用户名或密码错误");
        }
    }
}

4.使用

/**
 * 通过认证授权方法进行判断
 */
@Test
public void testShiro(){
    //1.获取工厂
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-test-3.ini");
    //2.获取安全管理
    SecurityManager securityManager = factory.getInstance();
    //3.绑定
    SecurityUtils.setSecurityManager(securityManager);
    //4.构造subject
    Subject subject = SecurityUtils.getSubject();
    //5.构造数据
    String username = "zhangsan";
    String password = "123456";
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    //6,执行认证方法(登录),此时自动找到配置的Shiro类(在ini文件中),然后执行认证方法
    subject.login(token);
    //7.执行授权方法
    System.out.println(subject.hasRole("role1"));
    System.out.println(subject.isPermitted("user:save"));
}

4.6工作流程

1.认证

  1. subject调用login方法,传递token,会自动委任给SecurityManager
  2. SecurityManager中使用认证器
  3. 认证器找到了所有的realm域,此时就找到了自己写好的。
  4. 然后就可以执行自己实现的realm域
    <span>JWT的权限控制与Shiro入门</span>

2.授权

首先调用isPermitted或hasRole方法,然后会委任给SecurityManager。

SecurityManager中使用授权器

<span>JWT的权限控制与Shiro入门</span>

<span>JWT的权限控制与Shiro入门</span>

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/115166.html原文链接:https://javaforall.net

(0)
上一篇 2021年11月12日 下午6:00
下一篇 2021年11月12日 下午7:00


相关推荐

  • stat函数的使用说明[通俗易懂]

    stat函数的使用说明[通俗易懂]1:stat函数取得指定文件的文件属性,文件属性存储在结构体stat里#include<sys/stat.h>intstat(constchar*pathname,structstat*statbuf);2:结构体statstructstat{dev_tst_dev;/*IDofdevicecontainingfile*/ino_tst_ino;

    2022年8月21日
    10
  • WPF 使用TextBox做密码输入框

    WPF 使用TextBox做密码输入框密码输入框需要输入的密码不能显示明文,用其他的特殊字符代替显示。显示效果如下:Xaml部分代码如下:

    2022年7月25日
    117
  • 高中数学平面解析几何解题技巧-直线与圆锥曲线的位置关系-习题

    高中数学平面解析几何解题技巧-直线与圆锥曲线的位置关系-习题今天主讲高中数学平面解析几何解题技巧直线与圆锥曲线的位置关系习题从 4 个方面进行讲解 知识梳理 要点整合 经典考题 2017 高考题 每个方面根据平面解析几何 直线与圆锥曲线的位置关系 习题详细介绍 并附解题过程与答案 解题技巧 一 平面解析几何 平面解析几何直线与圆锥曲线的位置关系 知识梳理 1 直线与圆锥曲线的位置关系的判定 2 直线与圆锥曲线的相交弦长问题二 平面解析几何 直线

    2026年3月18日
    2
  • bullet HashMap 内存紧密的哈希表

    bullet HashMap 内存紧密的哈希表

    2022年2月1日
    46
  • idea设置热部署

    idea设置热部署其实,idea和eclipse设置热部署都很简单,但是很多人都会忽略掉一点,那就是:一定要在debug模式下才有用!!(配置文件什么的还是要重启的)(不像myeclipse直接star也可以热部署,对于我这种用惯了myeclipse的来说,基本没用debug启动过,所以老觉得配置没用)idea部署方法:(转自https://www.cnblogs.com/1024zy/p

    2022年6月12日
    43
  • Android Handler消息机制原理最全解读(持续补充中)

    Android Handler消息机制原理最全解读(持续补充中)Handler 在 Android 开发的过程中 我们常常会将耗时的一些操作放在子线程 workthread 中去执行 然后将执行的结果告诉 UI 线程 mainthread 熟悉 Android 的朋友都知道 UI 的更新只能通过 Mainthread 来进行 那么这里就涉及到了如何将子线程的数据传递给 mainthread 呢 Android 已经为我们提供了一个消息传递的机制 Hand

    2026年3月19日
    2

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号