springboot集成JWT实现身份认证(权鉴)的方法步骤

作者:一路奔跑1314 时间:2023-06-02 12:57:37 

一、什么是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引入依赖    

<!--jwt-->
       <dependency>
           <groupId>com.auth0</groupId>
           <artifactId>java-jwt</artifactId>
           <version>3.8.3</version>
       </dependency>

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<String,Object> 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<TabUser>()
               .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<Map> roleList = roleService.qryRoleInfoByUserId(roleMap);
       List<String> 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<TabUser> 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<String> 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<String, String> 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<String, String> headerMap) {
       if (headerMap==null||headerMap.isEmpty()){
           return;
       }

Class<? extends HttpServletRequest> 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<String,String> entry:headerMap.entrySet()){
               mimeHeaders.removeHeader(entry.getKey());
               mimeHeaders.addValue(entry.getKey()).setString(entry.getValue());
           }

}catch (Exception e){
           e.printStackTrace();
       }

}
   @Override
   public void destroy() {

}
}

来源:https://blog.csdn.net/dhklsl/article/details/130086681

标签:springboot,JWT,身份认证
0
投稿

猜你喜欢

  • Java实现递归计算n的阶乘

    2021-06-26 14:34:26
  • Unity实现3D循环滚动效果

    2023-04-22 14:05:47
  • Spring Boot接口限流的常用算法及特点

    2023-12-20 05:16:30
  • Android植物大战僵尸小游戏

    2023-08-05 21:27:04
  • C#实现装饰器模式

    2023-06-18 04:48:16
  • spring boot配置读写分离的完整实现步骤

    2022-02-15 21:23:11
  • Kotlin超简单实现StepView的方法

    2022-01-21 09:28:27
  • SpringBoot配置 Druid 三种方式(包括纯配置文件配置)

    2021-06-03 01:41:19
  • Java语言中Swing组件编程详解

    2023-04-19 04:08:22
  • JAVA熔断和降级真实关系的图文详解

    2023-11-30 12:39:20
  • Android通过实现GridView的横向滚动实现仿京东秒杀效果

    2023-06-08 22:53:46
  • 带你了解Java数据结构和算法之无权无向图

    2023-12-24 10:54:05
  • Android studio利用gradle打jar包并混淆的方法详解

    2021-07-07 06:22:57
  • Android Build类的详解及简单实例

    2023-07-02 21:12:45
  • 基于idea Maven中的redis配置使用详解

    2023-11-29 11:57:28
  • SpringBoot微信消息接口配置详解

    2023-08-23 09:51:21
  • C语言实现扫雷游戏源代码

    2021-10-17 14:20:44
  • java实现MD5加密算法的实例代码

    2021-10-01 16:49:14
  • Unity shader实现遮罩效果

    2023-06-28 10:20:09
  • Java中的线程同步与ThreadLocal无锁化线程封闭实现

    2023-09-17 04:48:06
  • asp之家 软件编程 m.aspxhome.com