Spring security之JWT_spring-security-jwt-程序员宅基地

技术标签: spring  java  后端  

JWT

一级目录

二级目录

三级目录

1.什么是JWT

  • 全称 JSON Web Token,简称JWT,是一个基于RFC7519的爱看i发数据标准,定义了一种宽松且紧凑的数据组合方式
  • 是一种加密后的数据载体,可在各应用之间进行数据传输
  • 最常应用于授权认证,一旦用户登录,每个请求都将包含JWT,系统每次处理用户请求都会及逆行JWT安全检验,通过之后在进行处理

2.JWT的组成部分

JWT由3部分组成,使用.拼接

  • Header

    • 声明类型,默认JWT
    • 声明加密算法,HMAC,RSA,ECDSA等常用算法
    {
           
    "alg": "HS256",
    "typ": "JWT"
    }
    
    • alg:表示签名的算法,默认HMAC SHA256(写成HS256)
    • type:表示声明类型,默认JWT

    使用Base64编码后构成JWT,的第一部分

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
    
  • Payload

    • 存放有效信息的地方

    标准载荷

    建议但不强制使用

    标准载荷 介绍
    iss (issuer) 签发人(谁签发的)
    exp (expiration time) 过期时间,必须要大于签发时间
    sub (subject) 主题(用来做什么)
    aud (audience) 受众(给谁用的)比如:http://www.xxx.com
    nbf (Not Before) 生效时间
    iat (Issued At) 签发时间
    jti (JWT ID) 编号,JWT 的唯一身份标识

    自定义载荷

    可以添加任何信息,一般添加用户的相关信息或业务所需要的必要信息。但不建议添加敏感信息

    {
           
     //第一部分用户信息
     "user_info": [
       {
           
         "id": "1"//id
       },
       {
           
         "name": "dafei"//姓名
       },
       {
           
         "age": "18"//年龄
       }
     ],
     "iat": 1681571257,//签发时间
     "exp": 1682783999,//过期时间
     "aud": "xiaofei",//受众
     "iss": "dafei",//签发人
     "sub": "alluser"//主题
    }
    

    使用Base64编码后构成了第二部分

    eyJ1c2VyX2luZm8iOlt7ImlkIjoiMSJ9LHsibmFtZSI6ImRhZmVpIn0seyJhZ2UiOiIxOCJ9XSwiaWF0IjoxNjgxNTcxMjU3LCJleHAiOjE2ODI3ODM5OTksImF1ZCI6InhpYW9mZWkiLCJpc3MiOiJkYWZlaSIsInN1YiI6ImFsbHVzZXIifQ
    
  • signature

    • 私钥:只有服务器才知道
    • 公钥:按照以下算法产生
    signature = HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
    

    用Base64编码,构成第三部分

    将三部分拼接成一个字符串,便是jwt了

由于密钥的存在,即使调用方修改了前两部分内容,在验证不放呢会出现签名不一致的情况,保证了全性

3.编码/解码

编码工具

https://tooltt.com/jwt-encode/

解码工具

https://tool.box3.cn/jwt.html

4.特点

优点

  • 无状态

    JWT 不需要在服务端存储任何状态,客户端可以携带 JWT 来访问服务端,从而使服务端变得无状态。这样,服务端就可以更轻松地实现扩展和负载均衡。

  • 自定义

    JWT 的载荷部分可以自定义,可以存储任何 JSON 格式的数据。这意味着我们可以使用 JWT 来实现一些自定义的功能,例如存储用户喜好、配置信息等等。

  • 扩展性强

    JWT 有一套标准规范,因此很容易在不同平台和语言之间共享和解析。此外,开发人员可以根据需要自定义声明(claims)来实现更加灵活的功能。

  • 调试性好

    由于 JWT 的内容是以 Base64 编码后的字符串形式存在的,因此非常容易进行调试和分析。

缺点

  • 安全性取决于密钥管理

    JWT 的安全性取决于密钥的管理。如果密钥被泄露或者被不当管理,那么 JWT 将会受到攻击。因此,在使用 JWT 时,一定要注意密钥的管理,包括生成、存储、更新、分发等等。

  • 无法撤销

    由于 JWT 是无状态的,一旦 JWT 被签发,就无法撤销。如果用户在使用 JWT 认证期间被注销或禁用,那么服务端就无法阻止该用户继续使用之前签发的 JWT。因此,开发人员需要设计额外的机制来撤销 JWT,例如使用黑名单或者设置短期有效期等等。

  • 需要缓存到客户端

    由于 JWT 包含了用户信息和授权信息,一般需要客户端缓存,这意味着 JWT 有被窃取的风险。

  • 载荷大小有限制

    由于 JWT 需要传输到客户端,因此载荷大小也有限制。一般不建议载荷超过 1KB,会影响性能。

5. 为什么使用JWT

5.1传统的验证方式

  • 传统的认证方式,是基于Session的认证

Cookie和Session

  • Cookie:一种让程序员在本地存储数据的能力,让数据在客户端这边更持久化
  • Cookie里面存的是键值对的格式数据,键值对用“;”分割,键和值之间用“,”分割

登录的认证

Session

  • session是在服务器的一种机制,因为cookie是客户端保存的数据,而这些数据又是跟用户强烈相关联的,显然保存在客户端这边就不太合适(太多,也占资源),所以把数据都保存在服务器这边就比较的合适;保存的方式就是通过session的方式来进行保存的。键值对结构

  • 使用之后,客户端只需要保存 sessionId就可以了,后续的请求带上 sessionId,服务器就会根据 sessionId 就会找到对应的用户数据详细的信息。

认证流程

image-20230808162008667

  1. 客户端:发起一个认证请求
  2. 服务器端:认证通过存储一个session的用户信息;把sessionid写入客户端cookie
  3. 客户端再次访问:自动携带sessionid,找到对应session

问题分析

  • 通常来说session保存在服务器的内存中,随着认证用户增多,会加大服务器的开销

  • 因为session保存在服务器内存中,所以对于分布式应用来说,限制了应用的扩展能力

  • cookie被截取,会容易受造跨站请求伪造攻击

  • 在前后端分离的系统中更加痛苦,比如后端是集群分布,需要设计共享机制、

5.2基于JWT的验证方式

image-20230808165551764

  1. 客户端: 发起一个请求认证,带有用户名,密码
  2. 服务器:认证通过,生成JWT,响应给客户端
  3. 客户端:将Token保存到本地

日后客户端再次认证

  1. 客户端:携带JWT发送给服务器
  2. 服务器:进行验证,通过,展示数据;否则返回错误信息

6.JWT进行登录验证

6.1依赖安装

  • spring-boot-starter-security
  • jjwt-api
  • jjwt-impl
  • jjwt-jackson

meaven仓库地址:Maven Repository: Search/Browse/Explore (mvnrepository.com)

6.2编写UserDetailServiceImpl类

  • 继承UserDetailsService接口
  • 作用:接入数据库信息,主要是用来根据用户名查找用户
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    
    @Autowired
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("username",username);
        User user=userMapper.selectOne(queryWrapper);
        if(user==null){
    
            throw new RuntimeException("用户不存在");
        }
        return new UserDetailsImpl(user);
    }
}

6.3编写UserDetailsImpl类

  • 使用户的一个接口类,用来判断用户的密码,权限是否失效,过期。
package com.kob.backend.service.impl.utils;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import  com.kob.backend.pojo.User;

@Data
@AllArgsConstructor
@NoArgsConstructor

public class UserDetailsImpl implements UserDetails {
    
    private User user;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
    
        return null;
    }

    @Override
    public String getPassword() {
    
        return user.getPassword();
    }

    @Override
    public String getUsername() {
    
        return null;
    }

    @Override
    public boolean isAccountNonExpired() {
    
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
    
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
    
        return true;
    }

    @Override
    public boolean isEnabled() {
    
        return true;
    }
}

6.4 实现config.SecurityConfig类

  • 用来实现用户密码的加密存储
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    

    @Bean
    public PasswordEncoder passwordEncoder() {
    
        return new BCryptPasswordEncoder();
    }
}

6.5实现utils.JwtUtil类

  • 是jwt工具类的一种
  • 作用:用来创建,解析jwt token
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;

@Component
public class JwtUtil {
    
    public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;  // 有效期14天
    public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";

    public static String getUUID() {
    
        return UUID.randomUUID().toString().replaceAll("-", "");
    }

    public static String createJWT(String subject) {
    
        JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
        return builder.compact();
    }

    private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
    
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        SecretKey secretKey = generalKey();
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        if (ttlMillis == null) {
    
            ttlMillis = JwtUtil.JWT_TTL;
        }

        long expMillis = nowMillis + ttlMillis;
        Date expDate = new Date(expMillis);
        return Jwts.builder()
                .setId(uuid)
                .setSubject(subject)
                .setIssuer("sg")
                .setIssuedAt(now)
                .signWith(signatureAlgorithm, secretKey)
                .setExpiration(expDate);
    }

    public static SecretKey generalKey() {
    
        byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
        return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
    }

    public static Claims parseJWT(String jwt) throws Exception {
    
        SecretKey secretKey = generalKey();
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(jwt)
                .getBody();
    }
}

6.6实现config.filter.JwtAuthenticationTokenFilter类

  • 进行jwt token的验证,如果成功,将用户信息注入上下文中
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    
    @Autowired
    private UserMapper userMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
    
        String token = request.getHeader("Authorization");

        if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
    
            filterChain.doFilter(request, response);
            return;
        }

        token = token.substring(7);

        String userid;
        try {
    
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
    
            throw new RuntimeException(e);
        }

        User user = userMapper.selectById(Integer.parseInt(userid));

        if (user == null) {
    
            throw new RuntimeException("用户名未登录");
        }

        UserDetailsImpl loginUser = new UserDetailsImpl(user);
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUser, null, null);

        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        filterChain.doFilter(request, response);
    }
}


6.7配置config.SecurityConfig类

  • 放行登录,注册页面
import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Bean
    public PasswordEncoder passwordEncoder() {
    
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
    
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
            //在这里方形的登录注册页面,填写访问地址
                .antMatchers("/user/account/token/", "/user/account/register/").permitAll()
                .antMatchers(HttpMethod.OPTIONS).permitAll()
                .anyRequest().authenticated();

        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    }
}


7.常见的面试题

问:什么是JWT?解释一下它的结构。

JWT是一种开放标准,用于在网络上安全地传输信息。它由三部分组成:头部、载荷和签名。头部包含令牌的元数据,载荷包含实际的信息(例如用户ID、角色等),签名用于验证令牌是否被篡改。

问:JWT的优点是什么?它与传统的session-based身份验证相比有什么优缺点?

JWT的优点包括无状态、可扩展、跨语言、易于实现和良好的安全性。相比之下,传统的session-based身份验证需要在服务端维护会话状态,使得服务端的负载更高,并且不适用于分布式系统。

问:在JWT的结构中,分别有哪些部分?每个部分的作用是什么?

JWT的结构由三部分组成:头部、载荷和签名。头部包含令牌类型和算法,载荷包含实际的信息,签名由头部、载荷和密钥生成。

问:JWT如何工作?从开始到验证过程的完整流程是怎样的?

JWT的工作流程分为三个步骤:生成令牌、发送令牌、验证令牌。在生成令牌时,服务端使用密钥对头部和载荷进行签名。在发送令牌时,将令牌发送给客户端。在验证令牌时,客户端从令牌中解析出头部和载荷,并使用相同的密钥验证签名。

问:什么是JWT的签名?为什么需要对JWT进行签名?如何验证JWT的签名?

JWT的签名是由头部、载荷和密钥生成的,用于验证令牌是否被篡改。签名使用HMAC算法或RSA算法生成。在验证JWT的签名时,客户端使用相同的密钥和算法生成签名,并将生成的签名与令牌中的签名进行比较。

问:什么是JWT的令牌刷新?为什么需要这个功能?

令牌刷新是一种机制,用于解决JWT过期后需要重新登录的问题。在令牌刷新中,服务端生成新的JWT,并将其发送给客户端。客户端使用新的JWT替换旧的JWT,从而延长令牌的有效期。

问:JWT是否加密?如果是,加密的部分是哪些?如果不是,那么它如何保证数据安全性?

JWT本身并不加密,但可以在载荷中包含敏感信息。为了保护这些信息,可以使用JWE(JSON Web Encryption)对载荷进行加密。如果不加密,则需要在生成JWT时确保不在载荷中包含敏感信息。

问:在JWT中,如何处理Token过期的问题?有哪些方法可以处理?

JWT过期后,客户端需要重新获取新的JWT。可以通过在JWT中包含过期时间或使用refresh token等机制来解决过期问题。

问:JWT和OAuth2有什么关系?它们之间有什么区别?

JWT和OAuth2都是用于身份验证和授权的开放标准。JWT是一种身份验证机制,而OAuth2是一种授权机制。JWT用于在不同的系统中安全地传输信息,OAuth2用于授权第三方应用程序访问受保护的资源。

问:JWT在什么场景下使用较为合适?它的局限性是什么?

JWT在单体应用或微服务架构中的使用比较合适。它的局限性包括无法撤销、令牌较大、无法处理并发等问题。在需要针对每次请求进行访问控制或需要撤销令牌的情况下,JWT可能不是最佳选择。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_45400340/article/details/132172965

智能推荐

级联样式表_级联样式表| 第三部分-程序员宅基地

文章浏览阅读173次。级联样式表 CSS-难以成熟 (CSS — Difficult to maturation)Unlike software, the CSS specifications are developed by successive versions, which would allow a browser to refer to a particular version. CSS was devel..._级联样式表是哪年产生的

sql server学习笔记——批处理语句、存储过程_sql的批处理-程序员宅基地

文章浏览阅读1.7k次。目录批处理语句1、批处理语句简介示例一:示例二:存储过程一、什么是存储过程1、存储过程的简介2、存储过程包含的内容3、存储过程的优点4、存储过程的分类系统存储过程:用户定义存储过程5、常用的系统储存过程(1)一般常用的存储过程(2)xp_cmdshell二、创建存储过程1、定义存储过程的语法2、不带参数的存储过程3、带参数..._sql的批处理

css代码的定位及浮动

上次,我们解除了css的内外边距、鼠标悬停及其练习。现在我们学习css元素练习和定位。

一种简单的蒙特卡洛树搜索并行化方法-程序员宅基地

文章浏览阅读2.2k次,点赞4次,收藏6次。本文提出了 WU-UCT, 一种新颖的并行 MCTS 算法, 通过监控未观察样本的数量来解决并行化过程中统计数据过时的问题. 基于新设计的统计数据, 它有正确地修正了UCT节点选择策略, 实现了有效的探索和利用的权衡. _树搜索并行化

python多页爬取page_python爬虫实现爬取同一个网站的多页数据的实例讲解-程序员宅基地

文章浏览阅读3.2k次。对于一个网站的图片、文字音视频等,如果我们一个个的下载,不仅浪费时间,而且很容易出错。Python爬虫帮助我们获取需要的数据,这个数据是可以快速批量的获取。本文小编带领大家通过python爬虫获取获取总页数并更改url的方法,实现爬取同一个网站的多页数据。一、爬虫的目的从网上获取对你有需要的数据二、爬虫过程1、获取url(网址)。2、发出请求,获得响应。3、提取数据。4、保存数据。三、爬虫功能可以..._python 爬虫 get_page_size

《从0到1学习Flink》—— Flink 读取 Kafka 数据批量写入到 MySQL_flink+kafka统计数据后插入mysql-程序员宅基地

文章浏览阅读2.1k次。本文原创地址是: http://www.54tianzhisheng.cn/2019/01/15/Flink-MySQL-sink/ , 未经允许禁止转载。前言之前其实在 《从0到1学习Flink》—— 如何自定义 Data Sink ? 文章中其实已经写了点将数据写入到 MySQL,但是一些配置化的东西当时是写死的,不能够通用,最近知识星球里有朋友叫我: 写个从 kafka 中读取数据,..._flink+kafka统计数据后插入mysql

随便推点

ESP32-C3 BLE5.0 扩展蓝牙名称长度的流程_蓝牙广播名称过长-程序员宅基地

文章浏览阅读1.8k次,点赞4次,收藏5次。BLE5.0 扩展蓝牙名称长度_蓝牙广播名称过长

centos8安装NVIDIA显卡驱动,docker模式运行机器学习_centos8安装显卡驱动-程序员宅基地

文章浏览阅读3.5k次。centos8安装NVIDIA显卡驱动,docker模式运行机器学习_centos8安装显卡驱动

利用优先级拥抱需求变更_需求优先级反复变化-程序员宅基地

文章浏览阅读2.4k次。需求变更这件事,每个开发人员都遇到过,每个产品经理也都遇到过。 以前,我们会追求需求不变更,但无论是产品型团队还是项目型团队,需求不变更都是天方夜谈,不可能实现的。即使把需求变更的成本提得很高,流程搞得很复杂,又要填变更单,又要几级经理审批,又要需求评审,依然无法避免。 于是,团队的目标变成了少变更,希望尽量少的变更既能满足业务的需要,又能减少开发团队的反感。但‘少’是个相对的概念_需求优先级反复变化

素数筛法_筛法求素数-程序员宅基地

文章浏览阅读2.3k次。一,素数筛法如果需要用到素数表,要么硬编码,要么自己求出前若干项素数。硬编码适合小表,大表只能自己求,而最常见的就是筛法。_筛法求素数

【深度长文】细思极恐的YouTube可跳过广告-程序员宅基地

文章浏览阅读1.8k次。【预警】:Youtube的可跳过广告尽管极其精妙(看完你就知道了),但其实并没有到细思极恐的程度;同时本文也并非深度长文,长是比较长,深不深度就另说了!作为一个广告PM,这种标题党行为都是为了优化点击率。开篇就明确文章属标题党,如今像我这样的良心作者已经不多了,快猛戳关注啊!有一些互联网产品:你认为它很简单、甚至很愚蠢,然而实际上它的设计却无比精妙、细思极恐,YouTube的5秒可跳过的贴片广

http作业

(1)学生信息网站只有song和tian两人可以访问,其他用户不能访问。2.给该公司创建三个子界面分别显示学生信息,教学资料和缴费网站,基于。可以访问网站内容为 welcome to openlab!(2)访问缴费网站实现数据加密基于https访问。综合练习:请给openlab搭建web网站。

推荐文章

热门文章

相关标签