热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

SpringBoot整合JWT实现用户认证(附源码)

SpringBoot整合JWT实现用户认证(附源码),Go语言社区,Golang程序员人脉社




作者:LTLAYX

初探 JWT

什么是 JWT

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

为什么要用 JWT

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

JWT 长什么样

JWT 由 3 个子字符串组成,分别为 Header, Payload以及 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 roles = new ArrayList();
        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. 首先进行一个新的测试用户的注册,可以看到注册成功的提示返回

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

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

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

示例代码

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

参考

http://blog.leapoahead.com/2015/09/06/understanding-jwt/ ↩
https://aboullaite.me/spring-boot-token-authentication-using-jwt/

(完)

推荐阅读

IDEA新特性:提前知道代码怎么走

还在担心写的一手烂SQL,送你4款工

瞬间几千次的重复提交,我用 SpringBoot+Redis 扛住了

爬了知乎“神回复”,笑得根本停不下来

这 17 个 JVM 参数,高级 Java 必须掌握!


好文!必须点赞



推荐阅读
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • Android日历提醒软件开源项目分享及使用教程
    本文介绍了一款名为Android日历提醒软件的开源项目,作者分享了该项目的代码和使用教程,并提供了GitHub项目地址。文章详细介绍了该软件的主界面风格、日程信息的分类查看功能,以及添加日程提醒和查看详情的界面。同时,作者还提醒了读者在使用过程中可能遇到的Android6.0权限问题,并提供了解决方法。 ... [详细]
  • 一次上线事故,30岁+的程序员踩坑经验之谈
    本文主要介绍了一位30岁+的程序员在一次上线事故中踩坑的经验之谈。文章提到了在双十一活动期间,作为一个在线医疗项目,他们进行了优惠折扣活动的升级改造。然而,在上线前的最后一天,由于大量数据请求,导致部分接口出现问题。作者通过部署两台opentsdb来解决问题,但读数据的opentsdb仍然经常假死。作者只能查询最近24小时的数据。这次事故给他带来了很多教训和经验。 ... [详细]
  • Sleuth+zipkin链路追踪SpringCloud微服务的解决方案
    在庞大的微服务群中,随着业务扩展,微服务个数增多,系统调用链路复杂化。Sleuth+zipkin是解决SpringCloud微服务定位和追踪的方案。通过TraceId将不同服务调用的日志串联起来,实现请求链路跟踪。通过Feign调用和Request传递TraceId,将整个调用链路的服务日志归组合并,提供定位和追踪的功能。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 知识图谱——机器大脑中的知识库
    本文介绍了知识图谱在机器大脑中的应用,以及搜索引擎在知识图谱方面的发展。以谷歌知识图谱为例,说明了知识图谱的智能化特点。通过搜索引擎用户可以获取更加智能化的答案,如搜索关键词"Marie Curie",会得到居里夫人的详细信息以及与之相关的历史人物。知识图谱的出现引起了搜索引擎行业的变革,不仅美国的微软必应,中国的百度、搜狗等搜索引擎公司也纷纷推出了自己的知识图谱。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 标题: ... [详细]
  • r2dbc配置多数据源
    R2dbc配置多数据源问题根据官网配置r2dbc连接mysql多数据源所遇到的问题pom配置可以参考官网,不过我这样配置会报错我并没有这样配置将以下内容添加到pom.xml文件d ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • 2018深入java目标计划及学习内容
    本文介绍了作者在2018年的深入java目标计划,包括学习计划和工作中要用到的内容。作者计划学习的内容包括kafka、zookeeper、hbase、hdoop、spark、elasticsearch、solr、spring cloud、mysql、mybatis等。其中,作者对jvm的学习有一定了解,并计划通读《jvm》一书。此外,作者还提到了《HotSpot实战》和《高性能MySQL》等书籍。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
author-avatar
我想飞
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有