当前位置:

首页 > 编程开发 > springboot怎么集成JWT实现身份认证

springboot怎么集成JWT实现身份认证

一、什么是JWTJSONWebToken(JWT),它是目前最流行的跨域身份验证解决方案。现在的项目开发一般都是前端端分离,这就涉及到跨域和权鉴问题。二、JWT组成由三部分组成:头部(Header)、载荷(Payload)与签名(signature)头部(Header):头部信息由两部分组成1、令牌的类型,即JWT;2、使用的签名算法,例如HMASSHA256或RSA;{"alg":"HS256","typ":"JWT"}这个json中的typ属性,用来标识整个token字符串是一个JWT字符串;它的al

本文目录

    一、什么是JWT

    JSON Web Token (JWT),它是目前最流行的跨域身份验证解决方案。现在的项目开发一般都是前端端分离,这就涉及到跨域和权鉴问题。

    二、JWT组成

    由三部分组成:头部(Header)、载荷(Payload)与签名(signature)

    头部(Header):

    头部信息由两部分组成1、令牌的类型,即JWT;2、使用的签名算法,例如HMASSHA256或RSA;      

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

    这个json中的typ属性,用来标识整个token字符串是一个JWT字符串;它的alg属性,用来说明这个JWT签发的时候所使用的签名和摘要算法,typ跟alg属性的全称其实是type algorithm,分别是类型跟算法的意思。之所以都用三个字母来表示,也是基于JWT最终字串大小的考虑,同时也是跟JWT这个名称保持一致,这样就都是三个字符了…typ跟alg是JWT中标准中规定的属性名称。

    载荷(Payload):

    payload用来承载要传递的数据,它的json结构实际上是对JWT要传递的数据的一组声明,这些声明被JWT标准称为claims,它的一个“属性值对”其实就是一个claim(要求), 每一个claim的都代表特定的含义和作用。

    我们可以在claim里放一些业务信息。

    签名(signature):

    签名是把header和payload对应的json结构进行base64url编码之后得到的两个串用 '英文句点号' 拼接起来,然后根据header里面alg指定的签名算法生成出来的。
    算法不同,签名结果不同。以alg: HS256为例来说明前面的签名如何来得到。

    按照前面alg可用值的说明,HS256其实包含的是两种算法:HMAC算法和SHA256算法,前者用于生成摘要,后者用于对摘要进行数字签名。这两个算法也可以用HMACSHA256来统称    

    jwt数据结构图:

    springboot怎么集成JWT实现身份认证

    三、JWT运行原理

    1.第一次发送登录请求,必然会携带用户信息uname和pwd

    2.通过用户信息uname和pwd登录成功,会将用户信息通过jwt工具类生成一个加密的字符串

    3.加密字符串 会以response header 响应头的形式 相应到前端

    4.前端服务器会有响应拦截器拦截,截取到响应头承载的jwt串,又会放到Vuex中

    5.当第二次请求,前端服务器中有一个请求拦截器,会将Vuex中的jwt串放入request header 请求当中

    6.当请求通过跨域的方式到达后台服务器,后台服务器中又有一个过滤器,会截取到 request header 请求当中的jwt串

    7.jwt工具类会对jwt串进行解析,解析成用户信息,最终进行校验

    四、springboot集成JWT

    整体思路:

    当前端访问后台登录接口login时,先根据用户名和密码判断用户表是否存在该用户,如果存在该用户,则生成jwt串,可以在jwt串里添加些业务信息(比如用登录账号,用户真实姓名等),并把jwt串返给前端

    当前端拿到jwt串后放到所有请求的header中,比如token=jwt串

    后端开发一个filter,拦截所有请求(除login请求外,因为login请求还没有生成jwt),并从request的header中获取jwt(即token的值),对jwt校验和获取jwt中的业务信息,在把这些业务信息放到request的header中,这样方便后端接口直接从header中获取

    如果filter中jwt过期,或者校验失败,则返回给前端提示,前端返回登录页面,让用户重新登录。

    1、在pom.xml引入依赖    

    
            
                com.auth0
                java-jwt
                3.8.3
            

    2、开发jwt生成工具类,代码如下:

    package com.lsl.exam.utils;
     
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.lsl.exam.entity.TabUser;
     
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
     
    public class JwtUtil {
     
        private static final long EXPIRE_TIME = 1000 * 60 * 60 *24;
     
        //设置私钥
        private static final String TOKEN_SECRET = "aa082c-66rt89-29sr3t-y9t7b8";
     
     
        /**
         * 创建携带自定义信息和声明的自定义私钥的jwt
         * @param user  用户信息表
         * @return  jwt串
         */
        public static String creatJwt(TabUser user){
            //构建头部信息
            Map header = new HashMap<>();
            header.put("typ","JWT");
            header.put("alg","HS256");
     
            //根据私钥构建密钥信息
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
     
            //根据当前用户密码构建密钥信息
    //        Algorithm algorithm = Algorithm.HMAC256(user.getUserpwd());
     
            //设置过期时间为当前时间一天后
            Date nowDate = new Date();
            Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
     
            String jwt = JWT.create().withHeader(header)
                    .withClaim("account",user.getAccount())//业务信息:员工号
                    .withClaim("username",user.getUsername())//业务信息:员工姓名
                    .withClaim("rolename",user.getRoleName())//业务信息:角色
                    .withIssuer("SERVICE")//声明,签名是有谁生成 例如 服务器
                    .withNotBefore(new Date())//声明,定义在什么时间之前,该jwt都是不可用的
                    .withExpiresAt(expireDate)//声明, 签名过期的时间
                    .sign(algorithm);//根据algorithm生成签名
     
            return jwt;
     
        }
    }

    3、后端login接口逻辑如下:

    package com.lsl.exam.controller;
     
    import com.alibaba.fastjson.JSON;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.lsl.exam.entity.TabUser;
    import com.lsl.exam.entity.backresult.ResultVO;
    import com.lsl.exam.service.ITabRoleService;
    import com.lsl.exam.service.IUserService;
    import com.lsl.exam.utils.Base64Util;
    import com.lsl.exam.utils.JwtUtil;
    import com.lsl.exam.utils.ResultVoUtil;
    import org.slf4j.Logger;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
     
    import javax.servlet.http.HttpServletRequest;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
     
    @RestController
    @RequestMapping("/exam")
    public class UserController {
        private static final Logger LOG = org.slf4j.LoggerFactory.getLogger("UserController");
     
        @Autowired
        IUserService userService;
     
        @Autowired
        ITabRoleService roleService;
     
        @PostMapping(value = "login",produces = "application/json;charset=UTF-8")
        @ResponseBody
        public ResultVO login(@RequestBody Map params){
            Map reuslt = new HashMap();
     
            String account = params.get("account") == null ? "" : params.get("account").toString();
            String pwd = params.get("pwd") == null ? "" : params.get("pwd").toString();
     
            if ("".equals(account) || "".equals(pwd)){
                return ResultVoUtil.error(30000,"用户名或者密码不能为空!");
            }
     
            //pwd解密
            String decodePwd = Base64Util.decode(pwd);
            if ("".contains(decodePwd)){
                return ResultVoUtil.error(30000,"密码错误!");
            }
     
            TabUser user = userService.getOne(new QueryWrapper()
                    .eq("account",account)
                    .eq("userpwd",decodePwd));
            if (null == user){
                return ResultVoUtil.error(30000,"用户名或者密码错误");
            }
            
            //获取当前用户拥有的角色
            String userId = user.getId();
            Map roleMap = new HashMap();
            roleMap.put("userId",userId);
            List roleList = roleService.qryRoleInfoByUserId(roleMap);
            List roleNames = new ArrayList<>();
            for(Map role : roleList){
                roleNames.add(role.get("role").toString());
            }
            user.setRoleName(JSON.toJSONString(roleNames));
     
            //生成带有业务信息的jwt串
            String jwt = JwtUtil.creatJwt(user);
            
            //把jwt和当前用户信息返给前端
            reuslt.put("jwt",jwt);
            reuslt.put("roleNames",roleNames);
            reuslt.put("username",user.getUsername());
            reuslt.put("account",user.getAccount());
            
            return ResultVoUtil.success(reuslt);
        }
     
        @PostMapping(value = "qryUser",produces = "application/json;charset=UTF-8")
        @ResponseBody
        public Object qryUser(HttpServletRequest request){
     
            //这里header中的信息是filter中放进去的
            String account = request.getHeader("account");
            String username = request.getHeader("username");
            String rolename = request.getHeader("rolename");
     
            List list = userService.list();
            return ResultVoUtil.success(list);
        }
    }

    4、开发filter,进行jwt校验

    package com.lsl.exam.filter;
     
    import com.alibaba.fastjson.JSON;
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.algorithms.Algorithm;
    import com.auth0.jwt.interfaces.DecodedJWT;
    import com.lsl.exam.entity.backresult.ResultVO;
    import com.lsl.exam.utils.ResultVoUtil;
    import org.apache.tomcat.util.http.MimeHeaders;
    import org.springframework.stereotype.Component;
     
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.lang.reflect.Field;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
     
    /**
     * jwt校验过滤器
     */
    @Component
    @WebFilter(filterName = "jwtFilter",urlPatterns = {"/*"})
    public class AuthJwtFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
     
        }
     
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            String url = httpServletRequest.getRequestURL().toString();
            
            //配置不进行jwt校验的请求路径
            List urlList = new ArrayList<>();
            urlList.add("/exam/login");
     
            boolean flag = false;
            for (String strUrl : urlList){
                if (url.contains(strUrl)){
                    flag = true;
                }
            }
     
            try {
                if (!flag){
                    String token = httpServletRequest.getHeader("token");
                    //校验token,jwt过期有jwt自行校验,如果超时了,会执行catch里代码
                    DecodedJWT decodeJwt = JWT.require(Algorithm.HMAC256("aa082c-66rt89-29sr3t-y9t7b8")).build().verify(token);
                    
                    //获取jwt中的业务信息
                    String account = decodeJwt.getClaim("account").asString();
                    String username = decodeJwt.getClaim("username").asString();
                    String rolename = decodeJwt.getClaim("rolename").asString();
                    Map headerMap = new HashMap<>();
                    headerMap.put("account",account);
                    headerMap.put("username",username);
                    headerMap.put("rolename",rolename);
                    
                    //把业务信息添加到request的header
                    addHeader(httpServletRequest,headerMap);
     
     
    //                Class superclass = servletRequest.getClass().getSuperclass().getSuperclass();
    //                Field requestField = superclass.getDeclaredField("request");
    //                requestField.setAccessible(true);
    //                RequestFacade requestFacadeInstance = (RequestFacade) requestField.get(servletRequest);
                    RequestFacade requestFacadeInstance = (RequestFacade)superclass3;
    //                Field requestField1 = requestFacadeInstance.getClass().getDeclaredField("request");
    //                requestField1.setAccessible(true);
    //                Object requestInstance =  requestField1.get(requestFacadeInstance);
    //                Field coyoteRequestField = requestInstance.getClass().getDeclaredField("coyoteRequest");
    //                coyoteRequestField.setAccessible(true);
    //
    //                Object coyoRequestInstance =  requestField1.get(requestInstance);
    //                Field headersField = coyoRequestInstance.getClass().getDeclaredField("headers");
    //                headersField.setAccessible(true);
    //
    //                MimeHeaders headers = (MimeHeaders) headersField.get(coyoRequestInstance);
    //                headers.removeHeader("token");
    //                headers.addValue("account").setString(account);
    //                headers.addValue("username").setString(username);
    //                headers.addValue("roleid").setString(roleid);
    //
     
                }
            } catch (Exception e) {
                //jwt校验失败,返给前端的code=1,前端要重定向到登录页面
                PrintWriter writer = null;
                servletResponse.setCharacterEncoding("UTF-8");
                servletResponse.setContentType("text/html; charset=utf-8");
     
                try {
                    writer = servletResponse.getWriter();
                    ResultVO vo = ResultVoUtil.successLogout();
                    String msg = JSON.toJSONString(vo);
                    writer.println(msg);
                } catch (IOException ex) {
     
                } finally {
                    if (writer != null){
                        writer.close();
                    }
                    return;
                }
     
            }
     
            filterChain.doFilter(servletRequest,servletResponse);
        }
     
        /**
         * 向request的header中放业务信息
         * @param request
         * @param headerMap
         */
        private void addHeader(HttpServletRequest request, Map headerMap) {
            if (headerMap==null||headerMap.isEmpty()){
                return;
            }
     
            Class c=request.getClass();
            //System.out.println(c.getName());
            System.out.println("request实现类="+c.getName());
            try{
                Field requestField=c.getDeclaredField("request");
                requestField.setAccessible(true);
     
                Object o=requestField.get(request);
                Field coyoteRequest=o.getClass().getDeclaredField("coyoteRequest");
                coyoteRequest.setAccessible(true);
     
                Object o2=coyoteRequest.get(o);
                System.out.println("coyoteRequest实现类="+o2.getClass().getName());
                Field headers=o2.getClass().getDeclaredField("headers");
                headers.setAccessible(true);
     
                MimeHeaders mimeHeaders=(MimeHeaders) headers.get(o2);
                for (Map.Entry entry:headerMap.entrySet()){
                    mimeHeaders.removeHeader(entry.getKey());
                    mimeHeaders.addValue(entry.getKey()).setString(entry.getValue());
                }
     
            }catch (Exception e){
                e.printStackTrace();
            }
     
        } 
        @Override
        public void destroy() {
     
        }
    }
    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    Golang JWT验证实战,gin-jwt库使用教程
    Golang JWT验证实战,gin-jwt库使用教程

    如何在Golang中实现JWT身份验证?最常用的方法是使用gin-jwt库。步骤包括:1.安装gin和gin-jwt库;2.定义用户数据结构,如User;3.创建JWT中间件并配置密钥、过期时间、PayloadFunc、IdentityHandler、Authenticator、Authorizator等参数;4.配置路由,将LoginHandler和RefreshHandler绑定到对应路径,并对需认证的接口应用中间件;5.选择合适的JWT库时应考虑性能、安全性、易用性和社区支持;6.确保JWT安全需使

    PHP实现JWT白名单验证方法详解
    PHP实现JWT白名单验证方法详解

    JWT白名单允许特定JWT直接通过验证无需重复检查签名或过期时间适用于内部服务快速授权但存在安全风险。核心解决方案包括:1.验证前先检查JWT是否在白名单中若在则直接通过;2.白名单可存储于数据库缓存或配置文件;3.验证流程优先查白名单未命中则执行标准JWT验证;4.需提供接口管理白名单并定期清理过期项。主要安全风险包括JWT泄露后长期有效权限过大及管理难度增加。为安全使用应限制场景缩短有效期控制权限加强管理使用jti标识并结合其他安全措施。替代方案有刷新令牌Session机制OAuth2.0和ACL。白

    比较JWT、Session、SSO和OAuth2.0:场景应用与优劣势分析
    比较JWT、Session、SSO和OAuth2.0:场景应用与优劣势分析

    在现代Web应用及分布式系统中,身份认证与授权是保障系统安全性的关键环节。JWT(JSONWebTokens)、Session、SSO(SingleSign-On,单点登录)和OAuth2.0是四种常见的身份认证与授权机制,它们各自有不同的应用场景、优缺点。本文将对这四种机制进行对比分析,以便读者能够更好地理解和选择适合自身业务需求的认证授权方案。一、JWT(JSONWebTokens)JWT是一种在双方之间安全传输信息的开放标准(RFC7519)。这些信息可以被验证和信任,因为它们经过数字签名。JWT可

    springBoot项目常用目录有哪些
    springBoot项目常用目录有哪些

    springBoot项目常用目录springBoot项目的目录结构及名规范介绍基于SpringBoot开发时的目录结构及命名规范,通过介绍能够为你解决,在实际项目中如何规划目录结构?如何命名目录更规范?各个目录分别代表什么含义?等三个问题。目录说明servicex//项目名|-admin-ui//管理服务前端代码(一般将UI和SERVICE放到一个工程中,便于管理)|-servicex-auth//模块1|-servicex-common//模块2|-servicex-gateway//模块3|-serv

    Java API 开发中使用 JWT 鉴权
    Java API 开发中使用 JWT 鉴权

    随着互联网应用的广泛使用,安全性问题也逐渐成为重要的话题。为了保证应用的安全性,开发人员需要采取各种措施来防止未授权的访问和攻击。JWT(JSONWebToken)就是一种在网络应用中用于声明的一种安全传输方式。在JavaAPI开发中使用JWT鉴权,可以有效保护API的安全性,并且方便开发过程中的访问控制。JWT的基本概念JWT是由

    利用JWT在Go语言中实现身份验证的最佳实施方法
    利用JWT在Go语言中实现身份验证的最佳实施方法

    随着Web应用程序的普及和应用范围的扩大,保护用户数据和身份信息越来越重要。在应用程序中进行身份验证是非常必要的,本文将介绍在Go语言中如何使用JWT实现身份验证的最佳实践。什么是JWTJWT是JSONWebToken的缩写,是一种不需要Cookie等存储方式而能够安全传递信息的身份验证机制。一个JWT由三部分组成:Header:包含算法和类型等信息。P

    使用ThinkPHP6实现JWT认证的方法详情
    使用ThinkPHP6实现JWT认证的方法详情

    JWT(JSONWebToken)是一种轻量级的认证和授权机制,它使用JSON对象作为安全令牌,可以在多个系统之间安全地传输用户身份信息。而ThinkPHP6是一种基于PHP语言的高效、灵活的MVC框架,它提供了许多有用的工具和功能,其中就包括JWT认证机制。在本文中,我们将介绍如何使用ThinkPHP6进行JWT认证,以保障Web应用程序的安全性和可靠

    用Go语言实现简单而安全的用户认证,使用JWT
    用Go语言实现简单而安全的用户认证,使用JWT

    随着网络技术的快速发展,越来越多的网站和应用需要实现用户认证功能。但是,传统的用户名和密码认证方式存在安全风险,因为它们通常会被保存在数据库中,一旦数据库被攻击,就会导致用户信息泄露。为了解决这个问题,JWT(JSONWebToken)应运而生。JWT是一种开放标准(RFC7519),它定义了一种简单的、自包含的方式来传输信息,该信息可以被验证和信任。

    解析SpringBoot和SpringMVC之间的异同
    解析SpringBoot和SpringMVC之间的异同

    解析SpringBoot和SpringMVC之间的异同SpringBoot和SpringMVC是Java领域中非常重要的开发框架。虽然它们都属于Spring框架的一部分,但是在使用和功能上有一些明显的区别。本文将对SpringBoot和SpringMVC进行比较,解析它们之间的异同。首先,让我们来了解一下SpringBoot。SpringBo

    探索SpringCloud和SpringBoot在微服务领域的不同应用方式
    探索SpringCloud和SpringBoot在微服务领域的不同应用方式

    近年来,随着云计算和分布式架构的兴起,微服务架构的应用越来越广泛。而SpringCloud和SpringBoot作为Java开发中的两个重要框架,对于微服务的实现起到了重要的作用。然而,很多人对于它们在微服务领域的不同应用方式还存在一定的疑惑。本文将从不同的角度探索SpringCloud和SpringBoot在微服务中的应用方式。首先,我们来了解一下Spri

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。