Spring Boot整合JWT实现用户认证

news2024/11/15 13:38:36

初探JWT

什么是JWT

JWT(Json Web Token),是一种工具,格式为XXXX.XXXX.XXXX的字符串,JWT以一种安全的方式在用户和服务器之间传递存放在JWT中的不敏感信息。

为什么要用JWT

设想这样一个场景,在我们登录一个网站之后,再把网页或者浏览器关闭,下一次打开网页的时候可能显示的还是登录的状态,不需要再次进行登录操作,通过JWT就可以实现这样一个用户认证的功能。当然使用Session可以实现这个功能,但是使用Session的同时也会增加服务器的存储压力,而JWT是将存储的压力分布到各个客户端机器上,从而减轻服务器的压力。

JWT长什么样

JWT由3个子字符串组成,分别为HeaderPayload以及Signature,结合JWT的格式即:Header.Payload.Signature。(Claim是描述Json的信息的一个Json,将Claim转码之后生成Payload)。

Header

Header是由以下这个格式的Json通过Base64编码(编码不是加密,是可以通过反编码的方式获取到这个原来的Json,所以JWT中存放的一般是不敏感的信息)生成的字符串,Header中存放的内容是说明编码对象是一个JWT以及使用“SHA-256”的算法进行加密(加密用于生成Signature)

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

Claim

Claim是一个Json,Claim中存放的内容是JWT自身的标准属性,所有的标准属性都是可选的,可以自行添加,比如:JWT的签发者、JWT的接收者、JWT的持续时间等;同时Claim中也可以存放一些自定义的属性,这个自定义的属性就是在用户认证中用于标明用户身份的一个属性,比如用户存放在数据库中的id,为了安全起见,一般不会将用户名及密码这类敏感的信息存放在Claim中。将Claim通过Base64转码之后生成的一串字符串称作Payload。

{ 
"iss":"Issuer —— 用于说明该JWT是由谁签发的", 
"sub":"Subject —— 用于说明该JWT面向的对象", 
"aud":"Audience —— 用于说明该JWT发送给的用户", 
"exp":"Expiration Time —— 数字类型,说明该JWT过期的时间", 
"nbf":"Not Before —— 数字类型,说明在该时间之前JWT不能被接受与处理", 
"iat":"Issued At —— 数字类型,说明该JWT何时被签发", 
"jti":"JWT ID —— 说明标明JWT的唯一ID", 
"user-definde1":"自定义属性举例", 
"user-definde2":"自定义属性举例" 
} 

Signature

Signature是由Header和Payload组合而成,将Header和Claim这两个Json分别使用Base64方式进行编码,生成字符串Header和Payload,然后将Header和Payload以Header.Payload的格式组合在一起形成一个字符串,然后使用上面定义好的加密算法和一个密匙(这个密匙存放在服务器上,用于进行验证)对这个字符串进行加密,形成一个新的字符串,这个字符串就是Signature。

总结

JWT实现认证的原理

服务器在生成一个JWT之后会将这个JWT会以Authorization : Bearer JWT 键值对的形式存放在cookies里面发送到客户端机器,在客户端再次访问收到JWT保护的资源URL链接的时候,服务器会获取到cookies中存放的JWT信息,首先将Header进行反编码获取到加密的算法,在通过存放在服务器上的密匙对Header.Payload 这个字符串进行加密,比对JWT中的Signature和实际加密出来的结果是否一致,如果一致那么说明该JWT是合法有效的,认证成功,否则认证失败。

JWT实现用户认证的流程图

JWT的代码实现

这里的代码实现使用的是Spring Boot(版本号:1.5.10)框架,以及Apache Ignite(版本号:2.3.0)数据库。有关Ignite和Spring Boot的整合可以查看这里。

http://blog.csdn.net/ltl112358/article/details/79399026

代码说明:

代码中与JWT有关的内容如下:

  • config包中JwtCfg类配置生成一个JWT并配置了JWT拦截的URL

  • controller包中PersonController 用于处理用户的登录注册时生成JWT,SecureController 用于测试JWT

  • model包中JwtFilter 用于处理与验证JWT的正确性

  • 其余属于Ignite数据库访问的相关内容

JwtCfg 类

这个类中声明了一个@Bean ,用于生成一个过滤器类,对/secure 链接下的所有资源访问进行JWT的验证

/**
 * This is Jwt configuration which set the url "/secure/*" for filtering
 * @program: users
 * @create: 2018-03-03 21:18
 **/
@Configuration
public class JwtCfg {

    @Bean
    public FilterRegistrationBean jwtFilter() {
        final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new JwtFilter());
        registrationBean.addUrlPatterns("/secure/*");

        return registrationBean;
    }

}

JwtFilter 类

这个类声明了一个JWT过滤器类,从Http请求中提取JWT的信息,并使用了”secretkey”这个密匙对JWT进行验证

/**
 * Check the jwt token from front end if is invalid
 * @program: users
 * @create: 2018-03-01 11:03
 **/
public class JwtFilter extends GenericFilterBean {

    public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain)
            throws IOException, ServletException {

        // Change the req and res to HttpServletRequest and HttpServletResponse
        final HttpServletRequest request = (HttpServletRequest) req;
        final HttpServletResponse response = (HttpServletResponse) res;

        // Get authorization from Http request
        final String authHeader = request.getHeader("authorization");

        // If the Http request is OPTIONS then just return the status code 200
        // which is HttpServletResponse.SC_OK in this code
        if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);

            chain.doFilter(req, res);
        }
        // Except OPTIONS, other request should be checked by JWT
        else {

            // Check the authorization, check if the token is started by "Bearer "
            if (authHeader == null || !authHeader.startsWith("Bearer ")) {
                throw new ServletException("Missing or invalid Authorization header");
            }

            // Then get the JWT token from authorization
            final String token = authHeader.substring(7);

            try {
                // Use JWT parser to check if the signature is valid with the Key "secretkey"
                final Claims claims = Jwts.parser().setSigningKey("secretkey").parseClaimsJws(token).getBody();

                // Add the claim to request header
                request.setAttribute("claims", claims);
            } catch (final SignatureException e) {
                throw new ServletException("Invalid token");
            }

            chain.doFilter(req, res);
        }
    }
}

PersonController 类

这个类中在用户进行登录操作成功之后,将生成一个JWT作为返回

/**
 * @program: users
 * @create: 2018-02-27 19:28
 **/
@RestController
public class PersonController {

    @Autowired
    private PersonService personService;


    /**
     * User register with whose username and password
     * @param reqPerson
     * @return Success message
     * @throws ServletException
     */
    @RequestMapping(value = "/register", method = RequestMethod.POST)
    public String register(@RequestBody() ReqPerson reqPerson) throws ServletException {
        // Check if username and password is null
        if (reqPerson.getUsername() == "" || reqPerson.getUsername() == null
                || reqPerson.getPassword() == "" || reqPerson.getPassword() == null)
            throw new ServletException("Username or Password invalid!");

        // Check if the username is used
        if(personService.findPersonByUsername(reqPerson.getUsername()) != null)
            throw new ServletException("Username is used!");

        // Give a default role : MEMBER
        List<Role> roles = new ArrayList<Role>();
        roles.add(Role.MEMBER);

        // Create a person in ignite
        personService.save(new Person(reqPerson.getUsername(), reqPerson.getPassword(), roles));
        return "Register Success!";
    }

    /**
     * Check user`s login info, then create a jwt token returned to front end
     * @param reqPerson
     * @return jwt token
     * @throws ServletException
     */
    @PostMapping
    public String login(@RequestBody() ReqPerson reqPerson) throws ServletException {
        // Check if username and password is null
        if (reqPerson.getUsername() == "" || reqPerson.getUsername() == null
                || reqPerson.getPassword() == "" || reqPerson.getPassword() == null)
            throw new ServletException("Please fill in username and password");

        // Check if the username is used
        if(personService.findPersonByUsername(reqPerson.getUsername()) == null
                || !reqPerson.getPassword().equals(personService.findPersonByUsername(reqPerson.getUsername()).getPassword())){
            throw new ServletException("Please fill in username and password");
        }

        // Create Twt token
        String jwtToken = Jwts.builder().setSubject(reqPerson.getUsername()).claim("roles", "member").setIssuedAt(new Date())
                .signWith(SignatureAlgorithm.HS256, "secretkey").compact();

        return jwtToken;
    }
}

SecureController 类

这个类中只是用于测试JWT功能,当用户认证成功之后,/secure 下的资源才可以被访问

/**
 * Test the jwt, if the token is valid then return "Login Successful"
 * If is not valid, the request will be intercepted by JwtFilter
 * @program: users
 * @create: 2018-03-01 11:05
 **/
@RestController
@RequestMapping("/secure")
public class SecureController {

    @RequestMapping("/users/user")
    public String loginSuccess() {
        return "Login Successful!";
    }

}

代码功能测试

本例使用Postman对代码进行测试,这里并没有考虑到安全性传递的明文密码,实际上应该用SSL进行加密

1.首先进行一个新的测试用户的注册,可以看到注册成功的提示返回

2.再让该用户进行登录,可以看到登录成功之后返回的JWT字符串

3.直接申请访问/secure/users/user ,这时候肯定是无法访问的,服务器返回500错误

4.将获取到的JWT作为Authorization属性提交,申请访问/secure/users/user ,可以访问成功

示例代码

https://github.com/ltlayx/SpringBoot-Ignite

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/64707.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[Cortex-M3]-2-map文件解析

目录 1 几个问题 1.1 什么是map文件 1.2 如何查看编译出的程序和数据的信息 1.3 如何生成map文件 1.4 map文件里面有哪些信息 2 map文件信息详解 2.1 Section Cross References 2.2 Removing Unused input…

15年磨一剑,亚马逊云科技数据产品掌门人 Swami 揭秘云原生数据战略的三大关键要素

2022亚马逊云科技 re:Invent 全球大会正在拉斯维加斯如火如荼进行中&#xff0c;亚马逊云科技数据与机器学习副总裁 Swami Sivasubramanian 博士发表了“数据与机器学习如何助力企业构建端到端的数据战略”的主题演讲来开启第三天的日程。 Swami 博士重点介绍了亚马逊云科技在…

玉米脱粒机设计全套

目 录 摘要 I Abstract II 1引言 1 1.1 课题的来源与研究的目的和意义 1 1.2 本课题研究的内容 3 2玉米脱粒机总体结构的设计 5 2.1 玉米脱粒机工作方式的选择 7 2.2 玉米脱粒机的结构原理 10 2.3 机械传动部分的设计计算 11 2.3.1电机的选型计算 12 2.3.2 V带传动的设计计算 1…

C语言:文件操作(2)

文件的打开和关闭 文件在读写之前应该先打开文件&#xff0c;在使用结束之后应该关闭文件。 在编写程序的时候&#xff0c;在打开文件的同时&#xff0c;都会返回一个FILE*的指针变量指向该文件&#xff0c;也相当于建立了指针和文件的关系。 ANSIC规定使用fopen函数来打开文…

(十五) 共享模型之工具【线程池】

一、自定义线程池 1. 简易线程池 Slf4j(topic "c.TestPool") public class TestPool {public static void main(String[] args) {ThreadPool threadPool new ThreadPool(2,1000, TimeUnit.MILLISECONDS, 10);for (int i 0; i < 5; i) {int j i;threadPool.exe…

博球一看,记录疯狂!我与世界杯的那些二三事

文章目录 &#x1f525;关于世界杯 &#x1f525;关于2022卡塔尔世界杯 &#x1f525;我与足球 &#x1f525;我与世界杯 ⚽分享一颗足球 ⚽实现效果 &#x1f525;关于世界杯 大力神杯 国际足联世界杯&#xff08;FIFA World Cup&#xff09;&#xff0c;简称“世界杯”…

Vue中的数据代理与数据劫持

数据代理 数据代理字面上是通过一个对象代理对另一个对象属性的操作在vue中的数据代理&#xff0c;实际上是通过vm上的属性代理对_data中属性的操作 数据劫持 数据劫持也可称作数据代理&#xff0c;字面上是劫持到某个属性的变化&#xff0c;去做其他的操作在vue中的数据劫…

练习:查询学生新学期选课(python之str、dict、list试炼)

查询学生新学期选课(python之str、dict、list试炼)&#xff0c;数据用字典、列表存储。考验字符串的各种转换&#xff0c;字典、列表的读写。 (本文获得CSDN质量评分【88】)【学习的细节是欢悦的历程】Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免…

【Git 教程系列第 26 篇】Mac 升级系统到 Ventura 后,Git 公钥报 Permission denied 错误问题的解决方案

这是【Git 教程系列第 26 篇】&#xff0c;如果觉得有用的话&#xff0c;欢迎关注专栏。 注&#xff1a; 如果你是因为升级系统到 Ventura 后遇到的这个问题&#xff0c;可以直接看第三步的解决方案&#xff0c;前两步是我自己的写作习惯&#xff0c;只是记录一下这个过程&…

Qt OpenGL 图形字体的纹理映射

这次教程中&#xff0c;我们将在第14课的基础上创建带有纹理的字体&#xff0c;它真的很简单。也许你想知道如何才能给字体赋予纹理贴图&#xff1f;我们可以使用自动纹理坐标生成器&#xff0c;它会自动为字体上的每一个多边形生成纹理坐标。 这次课中我们还将使用Wingdings字…

BNext

又搬来了大神器啊 来自德国HassoPlattner计算机系统工程研究院的NianhuiGuo和HaojinYang等研究者提出了BNext模型&#xff0c;成为第一个在ImageNet数据集上top1分类准确率突破80%的BNN。 两年前&#xff0c;依靠早期 BNN 工作 XNOR-Net 起家的 XNOR.AI 被苹果公司收购&#…

redis基础5——redis的频道订阅和模式频道、redis主从复制、哨兵集群

文章目录一、发布订阅1.1 发布订阅过程1.2 发布订阅类型1.2.1 频道的发布订阅1.2.1.1 订阅频道* 实现原理1.2.1.2 发送消息* 实现原理1.2.1.3 退订频道* 实现原理1.2.2 模式的发布订阅1.2.2.1 订阅模式频道* 实现原理1.2.2.2 查询订阅与发布系统状态1.2.2.3 发送消息* 实现原理…

【c++基础】第三章 宏观部分:面向对象之类的层次结构与相互关系

第三章 宏观部分&#xff1a;面向对象之类的层次结构与相互关系关系has_a关系use_a关系&#xff08;友元关系&#xff09;is_a关系&#xff08;继承关系&#xff09;单继承多继承菱形继承多态&#xff08;polymorphic&#xff09;虚函数多态底层机制&#xff1a;虚函数、虚函数…

spring导入第三方资源

一、第三方资源配置管理 说明&#xff1a;以管理DataSource连接池对象为例讲解第三方资源配置管理 1 管理DataSource连接池对象 问题导入 配置数据库连接参数时&#xff0c;注入驱动类名是用driverClassName还是driver&#xff1f; 1.1 管理Druid连接池【重点】 数据库准备…

Android挂载系统分区执行mount和remount

第一种&#xff0c;通过adb挂载&#xff0c;需要root权限&#xff1b; adb root //adb 获取root权限&#xff1b; adb remount /system //从新挂载system目录或者说分区 第二种&#xff1a;通过Android下的Linux命令挂载&#xff0c;需要root权限&#xff1b; adb shell //…

【第八章 索引,索引结构,B-Tree,B+Tree,Hash,索引分类,聚集索引二级索引,索引语法】

第八章 索引&#xff0c;索引结构&#xff0c;B-Tree&#xff0c;BTree&#xff0c;Hash&#xff0c;索引分类&#xff0c;聚集索引&二级索引&#xff0c;索引语法 1.索引&#xff1a; ①索引&#xff08;index&#xff09;是帮助MySQL高效获取数据的数据结构(有序)。 ②特…

[附源码]Python计算机毕业设计Django社区人员信息管理系统设计与实现

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

Java学习笔记——StringBuilder

目录StringBuilder概述StringBuilder的构造方法StringBuilder常用的成员方法StringBuilder提高效率的原因String和StringBuilder之间的转换StringBuilder概述 StringBuilder是一个可变字符串类&#xff0c;我们可以把它看成一个容器。 作用&#xff1a;提高字符串的操作效率 …

HTML5期末作业:明星网站设计与实现——明星薛之谦介绍网页设计7个页面HTML+CSS+JavaScript

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

一文叫你使用trace查看函数调用关系|分析Linux性能

抓取函数调用流程关系 抓取函数耗时 抓取代码片耗时 抓取函数里每个子函数时间戳 抓取事件信息 trace是内核自带的工具&#xff0c;相比于perf工具&#xff0c;trace只管抓trace数据并没有分析&#xff0c;perf在trace数据分析方面做出了很多成果。 但是我们现在就想看一下…