我的编程空间,编程开发者的网络收藏夹
学习永远不晚

SpringSecurity详解整合JWT实现全过程

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

SpringSecurity详解整合JWT实现全过程

Token

Token和Sessionid的思想一样。Session是存在服务器端JVM中,Token存在Redis中。

解决分布式Session数据一致性问题:Spring-Session

传统的Token,例如:用户登录成功生成对应的令牌,key:为令牌, value:userid,隐藏了数据真实性 ,同时将该token存放到redis中,返回对应的真实令牌给客户端存放。客户端每次访问后端请求的时候,会传递该token在请求中,服务器端接收到该token之后,从redis中查询如果存在的情况下,则说明在有效期内,如果在Redis中不存在的情况下,则说明过期或者token错误。

Token使用:

  1. 验证账号密码成功
  2. 生成一个令牌UUID
  3. 将该令牌存放到redis中,key为令牌,value值对应存放userid
  4. 最终返回令牌给客户端

Token验证回话信息

  1. 在请求头中传递该令牌
  2. 从Redis中验证该令牌是否有效期
  3. 获取value内容
  4. 根据userid查询用户信息,返回给客户端

Token存放数据优缺点:

缺点:

  1. 必须依赖服务器,占用服务器端资源
  2. 效率非常低

优点:

  1. 可以隐藏数据真实性
  2. 适用于分布式/微服务
  3. 安全性高 JWT

Jwt

JSON WEB Token JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

组成

第一部分:header (头部)

描述加密算法

HS256:属于验证签名

RSA256:属于非对称加密

第二部分:playload(载荷)

携带存放的数据 用户名称、用户头像之类,需要注意铭感数据

标准中注册的声明 (建议但不强制使用) :

  • iss: jwt签发者
  • sub: jwt所面向的用户
  • aud: 接收jwt的一方
  • exp: jwt的过期时间,这个过期时间必须要大于签发时间
  • nbf: 定义在什么时间之前,该jwt都是不可用的.
  • iat: jwt的签发时间
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

第三部分:secret (存放在服务器端)

签名值,Base64(header .playload) +秘钥

JWT和Token的区别

1、token对应的数据存放在redis中

2、JWT对应存放的数据(payload中)客户端

优缺点

优点:

1、JWT数据存放在客户端,不依赖于服务器端,减轻服务器端压力

2、效率比传统的token验证还要高

缺点

1、jwt一旦生成之后后期无法修改

2、无法销毁一个jwt

3、建议不要放敏感数据,userid、手机号

4、后端无法统计 生成JWT

手写JWT

public class Test001 {
    private static final String SIGN_KEY = "kaicoSignKey";
    public static void main(String[] args) throws UnsupportedEncodingException {
        //手写jwt 封装三个部分:header、payload、sign签名
        //定义header
        JSONObject header = new JSONObject();
        header.put("alg", "HS256");
        //payload
        JSONObject payload = new JSONObject();
        payload.put("name", "kaico");
        String headerEncode = Base64.getEncoder().encodeToString(header.toJSONString().getBytes());
        String payloadJSONString = payload.toJSONString();
        String payloadEncode = Base64.getEncoder().encodeToString(payloadJSONString.getBytes());
        //sign签名值 实际上就是 md5
        String sign = DigestUtils.md5DigestAsHex((payload + SIGN_KEY).getBytes());
        String jwt = headerEncode + "." + payloadEncode + "." + sign;
        System.out.println(jwt);
        //解密
        String payloadEncodeStr = jwt.split("\\.")[1];
        String payloadDecoder = new String(Base64.getDecoder().decode(payloadEncodeStr), "UTF-8");
        String newSign = DigestUtils.md5DigestAsHex((payloadDecoder + SIGN_KEY).getBytes());
        System.out.println(newSign.equals(jwt.split("\\.")[2]));
    }
}

使用工具类新建JWT

public class Test003 {
    private static final String SIGN_KEY = "kaicoSignKey";
    public static void main(String[] args) {
        long now = System.currentTimeMillis();
        //设置过期时间 (测试使用1秒钟)
        Long exp = now + 1 * 1000;
        JwtBuilder jwtBuilder = Jwts.builder()
                //payload值
                .claim("userImg", "sssss")
                //签名值
                .signWith(SignatureAlgorithm.HS256, SIGN_KEY)
                .setExpiration(new Date(exp));

        //输出JWT的内容
        String jwt = jwtBuilder.compact();
        System.out.println(jwt);
        //解密
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Claims body = Jwts.parser().setSigningKey(SIGN_KEY).parseClaimsJws(jwt).getBody();
        System.out.println(body.get("userImg"));
    }
}

Springboot整合JWT

登录流程

1、验证账号密码

2、账号密码验证成功,生成JWT返回给客户端(移动app、浏览器、微信小程序)

3、客户端请求服务端,服务端验证JWT

    1. base64解密jwt获取payload中的数据
    2. 获取roles权限列表注册到SpringSecurity框架中

代码整合

在上次整合SpringSecurity的基础上

新增两个过滤器

package com.kaico.jwt.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.kaico.jwt.entity.UserEntity;
import com.kaico.jwt.utils.JwtUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;

public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter {
    
    private AuthenticationManager authenticationManager;
    public JWTLoginFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        
        super.setFilterProcessesUrl("/auth/login");
    }
    @Override
    public Authentication attemptAuthentication(HttpServletRequest req,
                                                HttpServletResponse res) {
        try {
            UserEntity user = new ObjectMapper()
                    .readValue(req.getInputStream(), UserEntity.class);
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(
                            user.getUsername(),
                            user.getPassword(),
                            new ArrayList<>())
            );
        } catch (IOException e) {
            logger.error(e.getMessage());
            return  null;
        }
    }
    @Override
    
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        UserEntity userEntity = (UserEntity) authResult.getPrincipal();
        String jwtToken = JwtUtils.generateJsonWebToken(userEntity);
        response.addHeader("token", jwtToken);
    }
    
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.getWriter().print("账号或者密码错误");
    }
}
package com.kaico.jwt.filter;
import com.kaico.jwt.utils.JwtUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class JWTValidationFilter extends BasicAuthenticationFilter {
    public JWTValidationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        SecurityContextHolder.getContext().setAuthentication(setAuthentication(request.getHeader("token")));
        super.doFilterInternal(request, response, chain);
    }
    
    private UsernamePasswordAuthenticationToken setAuthentication(String token) {
        String username = JwtUtils.getUsername(token);
        if (username == null) {
            return null;
        }
        //解析权限列表
        List<SimpleGrantedAuthority> userRoleList = JwtUtils.getUserRole(token);
        return new UsernamePasswordAuthenticationToken(username, null, userRoleList);
    }
}

JWT工具类

package com.kaico.jwt.utils;
import com.alibaba.fastjson.JSONArray;
import com.kaico.jwt.entity.UserEntity;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class JwtUtils {
    public static final String TOKEN_HEADER = "token";
    public static final String TOKEN_PREFIX = "Bearer ";
    private static final String SUBJECT = "kaico";
    //JWT有效期
    private static final long EXPIRITION = 1000 * 24 * 60 * 60 * 7;
    private static final String APPSECRET_KEY = "kaico_secret";
    private static final String ROLE_CLAIMS = "roles";
    public static String generateJsonWebToken(UserEntity user) {
        String token = Jwts
                .builder()
                .setSubject(SUBJECT)
                .claim(ROLE_CLAIMS, user.getAuthorities())
                .claim("username", user.getUsername())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
                .signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact();
        return token;
    }
    
    public static String createToken(String username, String role) {
        Map<String, Object> map = new HashMap<>();
        map.put(ROLE_CLAIMS, role);
        String token = Jwts
                .builder()
                .setSubject(username)
                .setClaims(map)
                .claim("username", username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRITION))
                .signWith(SignatureAlgorithm.HS256, APPSECRET_KEY).compact();
        return token;
    }
    public static Claims checkJWT(String token) {
        try {
            final Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
            return claims;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    public static String getUsername(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("username").toString();
    }
    
    public static List<SimpleGrantedAuthority> getUserRole(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        List roles = (List) claims.get(ROLE_CLAIMS);
        String json = JSONArray.toJSONString(roles);
        List<SimpleGrantedAuthority>
                grantedAuthorityList =
                JSONArray.parseArray(json, SimpleGrantedAuthority.class);
        return grantedAuthorityList;
    }
    
    public static boolean isExpiration(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.getExpiration().before(new Date());
    }
}

修改SecurityConfig配置类

@Override
    protected void configure(HttpSecurity http) throws Exception {
        List<PermissionEntity> allPermission = permissionMapper.findAllPermission();
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry
                expressionInterceptUrlRegistry = http.authorizeRequests();
        allPermission.forEach((permission) -> {
            expressionInterceptUrlRegistry.antMatchers(permission.getUrl()).
                    hasAnyAuthority(permission.getPermTag());
        });
        // 配置前后令牌登陆
        expressionInterceptUrlRegistry.antMatchers("/auth/login").permitAll()
                .antMatchers("/**").fullyAuthenticated()
                .and()
                //配置过滤器
                .addFilter(new JWTValidationFilter(authenticationManager()))
                .addFilter(new JWTLoginFilter(authenticationManager())).csrf().disable()
                //提出session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

测试:

1、post方式请求登录接口:localhost:8080/auth/login

请求参数json,返回请求头中带有token

{
    "username":"kaico_add",
    "password":"kaico"
}

2、再次请求其他接口时,请求头上带上token,

存在的问题

Jwt如何实现注销?

  • 客户端清除缓存,比如:浏览器cookie清除(但是服务器还是存在)
  • 权限发生变化的情况下,管理员同志用户重新登录或者提示权限不足,请联系管理员开放权限。
  • 建议将时间设置稍微短一点
  • 使用黑名单过滤,后期对服务器端压力大

JWT是否安全?

安全机制肯定有

就算黑客篡改payload中的权限列表,必须先获取到服务器端秘钥,自己生产JWT才行。

JWT中存放userid

可以单独对userid做对称加密之后再存在payload中,解密的秘钥在服务器端,也是安全的。

以上就是SpringSecurity详解整合JWT实现全过程的详细内容,更多关于SpringSecurity JWT的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

SpringSecurity详解整合JWT实现全过程

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

SpringBoot SpringSecurity JWT实现系统安全策略详解

Spring Security是Spring的一个核心项目,它是一个功能强大且高度可定制的认证和访问控制框架。它提供了认证和授权功能以及抵御常见的攻击,它已经成为保护基于spring的应用程序的事实标准
2022-11-21

springcloud-gateway整合jwt+jcasbin实现权限控制的详细过程

这篇文章主要介绍了springcloud-gateway整合jwt+jcasbin实现权限控制,基于springboot+springcloud+nacos的简单分布式项目,项目交互采用openFeign框架,单独提取出来成为一个独立的model,需要的朋友可以参考下
2023-02-07

SpringBoot集成整合JWT与Shiro流程详解

安全管理是软件系统必不可少的的功能。根据经典的“墨菲定律”——凡是可能,总会发生。如果系统存在安全隐患,最终必然会出现问题,这篇文章主要介绍了SpringBoot集成JWT、Shiro框架的使用
2022-12-08

SpringBoot整合SpringSecurity实现认证拦截的教程

我们写的任何一个项目,都应该有安全防护,不应该让这个项目进行“裸奔”,否则很容易被别人进行攻击。而在SpringBoot环境中,其实可以很容易实现安全保护,本文给大家介绍SpringBoot如何整合SpringSecurity实现认证拦截,需要的朋友可以参考下
2023-05-20

SpringBoot整合JPA框架实现过程讲解

在开发中,我们通常会对数据库的数据进行操作,Sprirng Boot对关系型数据库和非关系型数据库的访问操作都提供了非常好的整合支持
2022-12-21

【全网最细致】SpringBoot整合Spring Security + JWT实现用户认证

【全网最细致】SpringBoot整合Spring Security + JWT实现用户认证   登录和用户认证是一个网站最基本的功能,在这篇博客里,将介绍如何用SpringBoot整合Spring Security + JWT实现登录及用
2023-08-18

SpringBoot2 整合SpringSecurity框架是怎么实现用户权限安全管理

这篇文章给大家介绍SpringBoot2 整合SpringSecurity框架是怎么实现用户权限安全管理,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。一、Security简介1、基础概念Spring Security是
2023-06-05

SpringBoot整合Mybatis与druid实现流程详解

这篇文章主要介绍了springboot整合mybatisplus与druid详情,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的下伙伴可以参考一下
2022-11-13

Pytorch结合PyG实现MLP过程详解

这篇文章主要为大家介绍了Pytorch结合PyG实现MLP过程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录