SpringSecurity详解整合JWT实现全过程

 更新时间:2022年07月26日 09:41:25   作者:kaico2018  
JWT作为一个开放的标准( RFC 7519 ),定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。接下来通过本文给大家介绍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;
/**
 * @Author kaico
 * @Description //TODO
 * @Date 19:26 2022/7/25
 */
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);
    }
    /**
     * 账号或者密码错误
     * @param request
     * @param response
     * @param failed
     * @throws IOException
     * @throws ServletException
     */
    @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;
/**
 * @Author kaico
 * @Description //TODO 
 * @Date 19:37 2022/7/25
 */
public class JWTValidationFilter extends BasicAuthenticationFilter {
    public JWTValidationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }
    /**
     * 过滤请求验证
     *
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @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);
    }
    /**
     * 验证token 并且验证权限
     * @param token
     * @return
     */
    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;
    }
    /**
     * 生成token
     *
     * @param username
     * @param role
     * @return
     */
    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;
        }
    }
    /**
     * 获取用户名
     *
     * @param token
     * @return
     */
    public static String getUsername(String token) {
        Claims claims = Jwts.parser().setSigningKey(APPSECRET_KEY).parseClaimsJws(token).getBody();
        return claims.get("username").toString();
    }
    /**
     * 获取用户角色
     *
     * @param token
     * @return
     */
    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;
    }
    /**
     * 是否过期
     *
     * @param token
     * @return
     */
    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的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot +Vue开发考试系统的教程

    SpringBoot +Vue开发考试系统的教程

    这篇文章主要介绍了SpringBoot +Vue开发考试系统,支持多种题型:选择题、多选题、判断题、填空题、综合题以及数学公式。支持在线考试,教师在线批改试卷。本文通过实例代码给大家介绍的非常详细,需要的朋友可以参考下
    2020-05-05
  • SpringCloud Hystrix熔断器使用方法介绍

    SpringCloud Hystrix熔断器使用方法介绍

    通过hystrix可以解决雪崩效应问题,它提供了资源隔离、降级机制、融断、缓存等功能。接下来通过本文给大家分享SpringCloud集成Hystrix熔断,感兴趣的朋友一起看看吧
    2023-03-03
  • SpringMVC详解如何映射请求数据

    SpringMVC详解如何映射请求数据

    这篇文章主要给大家介绍了关于SpringMvc映射请求数据的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-06-06
  • 一文深入理解Java中的java.lang.reflect.InvocationTargetException错误

    一文深入理解Java中的java.lang.reflect.InvocationTargetException错误

    这篇文章主要给大家介绍了关于Java中java.lang.reflect.InvocationTargetException错误的相关资料,java.lang.reflect.InvocationTargetException是Java中的一个异常类,它通常是由反射调用方法时抛出的异常,需要的朋友可以参考下
    2024-03-03
  • java实现幸运抽奖功能

    java实现幸运抽奖功能

    这篇文章主要为大家详细介绍了java实现幸运抽奖功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • idea快速找到项目中对应的类图文详解(包括源码)

    idea快速找到项目中对应的类图文详解(包括源码)

    用IDEA开发Java项目时经常会使用到各种快捷键,其中搜索是最常用的之一,下面这篇文章主要给大家介绍了关于idea如何快速找到项目中对应的类(包括源码)的相关资料,文中通过图文介绍的非常详细,需要的朋友可以参考下
    2023-06-06
  • 在SpringBoot项目中解决依赖冲突问题的方法

    在SpringBoot项目中解决依赖冲突问题的方法

    在SpringBoot项目中,依赖冲突是一个常见的问题,特别是当项目引入多个第三方库或框架时,依赖冲突可能导致编译错误、运行时异常或不可预测的行为,本文给大家介绍了如何在SpringBoot项目中解决以来冲突问题的方法,需要的朋友可以参考下
    2024-01-01
  • mybatis-plus如何修改日志只打印SQL语句不打印查询结果

    mybatis-plus如何修改日志只打印SQL语句不打印查询结果

    这篇文章主要介绍了mybatis-plus如何修改日志只打印SQL语句不打印查询结果问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-06-06
  • Java中compareTo方法使用小结

    Java中compareTo方法使用小结

    compareTo是Java中Object类中的一个方法,它的作用是比较两个对象的大小关系,本文主要介绍了Java中compareTo方法使用小结,感兴趣的可以了解一下
    2024-01-01
  • springboot @Validated的概念及示例实战

    springboot @Validated的概念及示例实战

    这篇文章主要介绍了springboot @Validated的概念以及实战,使用 @Validated 注解,Spring Boot 应用可以有效地实现输入验证,提高数据的准确性和应用的安全性,本文结合实例给大家讲解的非常详细,需要的朋友可以参考下
    2024-04-04

最新评论