JWT oss登录

发布时间 2023-05-31 17:56:51作者: 旧梦旧忆旧温存

可参考:https://juejin.cn/post/7106702145520402468

流程

image

组成部分

1.Header(头) 作用:记录令牌类型、签名算法等 例如:{“alg":"HS256","type","JWT}

2.Payload(有效载荷)作用:携带一些用户信息 例如{"userId":"1","username":"mayikt"}

3.Signature(签名)作用:防止Token被篡改、确保安全性 例如 计算出来的签名,一个字符串

1.第一部分:header (头部)
{
Typ=“jwt” —类型为jwt
Alg:“HS256” --加密算法为hs256//主要是用于验签,防止数据被篡改
}
2.第二部分:playload(载荷) 携带存放的数据 用户名称、用户头像之类 注意铭感数据
标准中注册的声明 (建议但不强制使用) :
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
3.第三部分:Signature(签名)

jwt和session区别

session是保存在服务端的,而JWT是保存在客户端的

  • 基于session的认证流程

用户在浏览器中输入用户名和密码,服务器通过密码校验后生成一个session并保存到数据库
服务器为用户生成一个sessionId,并将具有sesssionId的cookie放置在用户浏览器中,在后续的请求中都将带有这个cookie信息进行访问
服务器获取cookie,通过获取cookie中的sessionId查找数据库判断当前请求是否有效

  • 基于JWT的认证流程

用户在浏览器中输入用户名和密码,服务器通过密码校验后生成一个token并保存到数据库
前端获取到token,存储到cookie或者local storage中,在后续的请求中都将带有这个token信息进行访问
服务器获取token值,通过查找数据库判断当前token是否有效

安全性

JWT的payload使用的是base64编码的,因此在JWT中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全

性能

经过编码之后JWT将非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以JWT一般放在local storage里面。并且用户在系统中的每一次http请求都会把JWT携带在Header里面,HTTP请求的Header可能比Body还要大。而sessionId只是很短的一个字符串,因此使用JWT的HTTP请求比使用session的开销大得多

一次性

无状态是JWT的特点,但也导致了这个问题,JWT是一次性的。想修改里面的内容,就必须签发一个新的JWT

无法废弃

一旦签发一个JWT,在到期之前就会始终有效,无法中途废弃。若想废弃,一种常用的处理手段是结合redis

续签

如果使用JWT做会话管理,传统的cookie续签方案一般都是框架自带的,session有效期30分钟,30分钟内如果有访问,有效期被刷新至30分钟。一样的道理,要改变JWT的有效时间,就要签发新的JWT。
最简单的一种方式是每次请求刷新JWT,即每个HTTP请求都返回一个新的JWT。这个方法不仅暴力不优雅,而且每次请求都要做JWT的加密解密,会带来性能问题。另一种方法是在redis中单独为每个JWT设置过期时间,每次访问时刷新JWT的过期时间
选择JWT或session
JWT有很多缺点,但是在分布式环境下不需要像session一样额外实现多机数据共享,虽然seesion的多机数据共享可以通过粘性session、session共享、session复制、持久化session、terracoa实现seesion复制等多种成熟的方案来解决这个问题。但是JWT不需要额外的工作,使用JWT不香吗?并且JWT一次性的缺点可以结合redis进行弥补

实现

  • java

添加依赖

  <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

代码编写

//controller层
@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public JwtResponse login(@RequestParam(name = "userName") String userName,
                             @RequestParam(name = "passWord") String passWord){
        String jwt = "";
        try {
            jwt = userService.login(userName, passWord);
            return JwtResponse.success(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            return JwtResponse.fail(jwt);
        }
    }


    @Resource
    private JwtUtils jwtUtil;

    @RequestMapping("/test")
    public Map<String, Object> test(@RequestParam("jwt") String jwt) {
        //这个步骤可以使用自定义注解+AOP编程做解析jwt的逻辑,这里为了简便就直接写在controller里
        Claims claims = null;
        HashMap<String, Object> map = new HashMap<>();
        try {
             claims = jwtUtil.parseJWT(jwt);
        } catch (Exception e) {
            map.put("code", "1");
            map.put("msg", "已过期,请重新登录");
           return map;
        }

        String name = claims.get("name", String.class);
        String age = claims.get("age", String.class);
        map.put("name", name);
        map.put("age", age);
        map.put("code", "0");
        map.put("msg", "请求成功");
        return map;
    }

//service层
public interface UserService {

     String login(String username, String password) throws Exception;
}

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private JwtUtils jwtUtil;

    @Resource
    private UserMapper userMapper;

    @Override
    public String login(String userName, String passWord) throws Exception {
        //登录验证
        User user = userMapper.findByUserNameAndPassword(userName, passWord);
        if (user == null) {
            return null;
        }
        //如果能查出,则表示账号密码正确,生成jwt返回
        String uuid = UUID.randomUUID().toString().replace("-", "");
        HashMap<String, Object> map = new HashMap<>();
        map.put("name", user.getName());
        map.put("age", user.getAge());
      //ttl设置  下面是一分钟示例(一分钟后过期)
        return jwtUtil.createJWT(uuid, "login subject", 60 * 1000L, map);
    }
	
	
	//核心
@Component
public class JwtUtils {

    @Value("${jwt.secretKey}")
    private String secretKey;

    public String createJWT(String id, String subject, long ttlMillis, Map<String, Object> map) throws Exception {

        //头部信息,不设置也是默认这个值
        Map<String, Object> header = new HashMap<>(2);
        header.put("Type", "Jwt");
        header.put("alg", "HS256");

        JwtBuilder builder = Jwts.builder()
                .setHeader(header)
                .setId(id)
                .setSubject(subject) // 发行者
                .setIssuedAt(new Date()) // 发行时间
                .signWith(SignatureAlgorithm.HS256, secretKey) // 签名类型 与 密钥
                .compressWith(CompressionCodecs.DEFLATE);// 对载荷进行压缩
        if (!CollectionUtils.isEmpty(map)) {
            builder.setClaims(map);
        }
        if (ttlMillis > 0) {
            builder.setExpiration(new Date(System.currentTimeMillis() + ttlMillis));
        }
        return builder.compact();// 拼接header + payload
    }


    public Claims parseJWT(String jwtString) {
        return Jwts.parser().setSigningKey(secretKey)
                .parseClaimsJws(jwtString)
                .getBody();
    }

    public static void main(String[] args) {
        Date date1 = new Date(System.currentTimeMillis());
        Date date = new Date(System.currentTimeMillis() + 60 * 1000L);
        System.out.println(date1);
        System.out.println(date);
    }
}
}



//mapper
@Component
public class UserMapper {

    private static final Map<String, User> map = new HashMap<>();

    static {
        User value = new User();
        value.setUsername("zs");
        value.setPassword("123");
        value.setName("林大大");
        value.setAge("999");
        map.put("zs", value);
    }
    public User findByUserNameAndPassword(String userName, String passWord) {
        User user = map.get(userName);
        if (!Objects.isNull(user)) {
            if (passWord.equals(user.getPassword())) {
                return user;
            }
        }
        return null;
    }
}


//common

@Data
@AllArgsConstructor
@NoArgsConstructor
public class BaseResponse {

    private String code;

    private String msg;

    public static BaseResponse success() {
        return new BaseResponse("0", "成功");
    }

    public static BaseResponse fail() {
        return new BaseResponse("1", "失败");
    }
    //构造器、getter、setter方法
}



@EqualsAndHashCode(callSuper = true)
@Data
public
class JwtResponse extends BaseResponse {

    private String jwtData;

    private String code;

    private String msg;

    public JwtResponse(String code, String msg, String jwtData) {
        this.code = code;
        this.msg = msg;
        this.jwtData = jwtData;
    }

    public static JwtResponse success(String jwtData) {
        BaseResponse success = BaseResponse.success();
        return new JwtResponse(success.getCode(), success.getMsg(), jwtData);
    }

    public static JwtResponse fail(String jwtData) {
        BaseResponse fail = BaseResponse.fail();
        return new JwtResponse(fail.getCode(), fail.getMsg(), jwtData);
    }
    //构造器、getter、setter方法
}


application.yml
## 用户生成jwt字符串的secretKey
jwt:
  secretKey: ak47