JWT基本概念及入门代码

JWT

1:基本概念

1.1:传统身份验证

http是一种没有状态的协议,也就是它并不知道是谁访问应用,这里我们把用户看成是客户端,客户端使用用户名还有密码通过了身份验证,不过下回这个客户端在发送请求时候,还等在验证一下。

解决的方法就是,当用户登录的时候,如果没有问题,我们在服务端生成一条记录,这个记录里可以说明一下登录的用户是谁,让后把这条记录的id号发送非客户端,客户端收到以后把这个id号存储在cookie里,下次这个用户在像服务端发送请求的时候,可以带着这个cookie,这样服务端会验证一个这个cookie里的消息,看看能不能在服务端这里找到对应的记录,如果可以,说明用户已经通过了身份验证,就把用户请求的数据返回给客户端。

上面说的服务器端存储就是session,我们需要在服务端存储为登录的用户生成的session,这session肯会存储在内存,磁盘,或者数据库里。我们需要在服务端定时的去清理过期的session。

这种认证中出现的问题是:

session:每次认用户发起请求时,服务器需要在创建一个记录去存储信息,当越来越多的用户发请求时,内存的开销也会不断增加。

CORS(跨域资源共享):当我们需要让数据跨多台系统设备上使用时,跨域资源的共享会使一个让人头疼的问题,在使用ajax抓取另一个域的资源,就可以会进制请求的情况。

CSRF(跨站请求伪造):用户在访问银行网站时,他们很容易受到跨站请求的攻击,并且能够在被利用其访问其他的网站。

在这些问题中,可拓展性是最突出的,因此我们需要出追求一种更行之有效的方法。

1.2:Token 身份认证

使用基于Token的身份验证方法,在服务端不需要存储用户的登录记录。大概的流程是这样的:

1:客户端使用用户,密码请求登录

2:服务端收到请求,去验证用户名,密码

3:验证成功后,服务端会签发一个Token,再把这个Token 发送给客户端

4:客户端每次向服务端请求资源的需要带着服务端签发的Token

5:服务端收到请求,让后去验证客户端请求里面带着的Token,如果验证成功,就向客户端返回请求的数据。

使用Token 验证的优势:

无状态,可拓展。

在客户端存储的token是无状态的,并且能够被拓展,基于这种无状态和不存储Session信息,负载均衡器能够将用户信息从一个服务传到其他服务器上。

安全性:

请求中发送token而不是发送cookie更高放置CSRF(跨站请求伪造),及时在客户端使用cookie存储token,

cookie也仅仅是一个存储机制,而不是用户认证,不将信息存储在session中,让我们少了对session的操作。

1.3 JSON web token (jwt)机制

JWT是一种紧凑且字包含的。用于在多方传递json对象的技术,传递的数据可以使用数字签名增加安全性,可以适应HMAC加密算法或者RSA 公钥/私钥加密方式。

紧凑:数据小,可以通过URL,post参数,请求头发送,且数据小代表传输速度快。

自包含:使用payload 数据块记录用户必要且不隐私的数据。可有效的减少数据库访问次数,提高代码性能。

JWT 一般用于处理用户身份验证或者数据信息交换。

1:用户身份验证:一旦用户登录,每个后续请求都将包含JWT,允许用户访问该令牌的路由,服务和资源。单点登录是当今广泛使用的JWT的一项功能,因为他的开销很小,并且能够轻松的跨不通过域使用。

2:数据信息交换:JWT是一种非常方便的使用多方传递数据的载体,因为其可以使用数据加密来保证数据的有效性和安全性。

2:JWT的数据结构

JWT的数据结构是: A.B.C 由字符点 “。” 来分割三部分数据。

A-header 头信息

B-payload (有效荷载)

C-signature 签名

2.1: header

数据结构:{“alg”:“加密算法名称”,”typ“:“JWT”}

alg是加密算法定义内容,如:HMAC SHA256 或RSA

typ是token类型,这里固定为JWT

2.2 payload

在pyload数据块一般使用记录实体(通常为用户信息)或者其他数据。主要分为三个部分,分别是:已注册的信息,公开数据,私用数据。

payload 中常用信息有:iss(发行者),exp(到期时间),sub(主题),aud(受众)等。

前面列举的都是已注册信息。

注意:即使JWT有签名加密机制,但是patload内容都是明文记录,除非记录的是加密数据,否则不排除泄露隐私数据的可能,不推荐在payload中记录任何铭感数据。

2.3 signatrue

签名信息,这是一个由开发者提供的消息。是服务端验证的传递的数据是否有效安全的标准。在生成JWT最终数据的之前。先试用header中定义的加密算法,将header和payload进行加密,并使用点进行连接。如:加密后的head加密后的payload。在使用相同的加密算法。对家后的数据和签名信息进行加密,得到最终结果。

2.4:JWT执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-52fTBHeP-1576024217901)(img/snipaste_20191114_092625.png)]

1:用户携带用户名称和密码进行的登录操作

2:服务端验证用户名称和密码没问题了之后创建JWT

3:服务器端将JWT传递到客户端。

4:客户端将JWT方法请求头中,并且每次请求都将JWT带上

5:服务器端验证JWT的有效性。

6:如果JWT有效就响应结果。

3:基于JWT实现单点登录

3.1:注意事项

1:时效性:

​ 在使用JWT实现单点登录时,需要注意token时效性,token是保存在客户端的令牌数据。

如果永久有效,则有被挟持的可能,token在设计的时候,可以考虑一次性有效,或者一段时间内有效。

如果设置的有效时长,则需要考虑是否需要刷新token有效期问题。

2:token保存位置

​ 在使用JWT技术生产的token,客户端保存的时候可以考虑cookie或者localStroage。cookie保存方式,可以实现跨域传递数据,localStorage是域私有的本地存储,无法实现跨域。

3.2:编写代码

1:思路

1:服务器端自定义注解和拦截器,如果Conntroller中方法中添加了自定义注解,就表示这个需要token验证,那么在拦截器中拦截所有请求,判断请求映射的方法是否带有自定义注解,如果带就拦截验证JWT,如果不存在就放行。

2:客户端携带用户名和密码进行登录操作。

3:服务器端拦截器拦截请求发现是登录请求,就放行。

4:进行用户名和密码的验证,如果通过验证,就根据用户信息创建JWT,并返回到客户端。客户端将jwt放在localStroage里面。

5:下次客户端在发起请求的时候,就会被拦截器拦截,因为不是登录请求,拦截就会拦截判断是否有token,token是否过期,是否含有user信息等,如果验证不通过就抛出异常,让后异常就会被全局异常处理所捕获。

6:如果验证通过,那么就继续给客户端提供服务。

2:添加依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BRuZ2nLn-1576024217903)(img/JWT依赖.png)]

3:编写工具类

1:需要一个设置密钥的方法

2:需要一个创建token的方法

3:需要一个验证token的方法

4:需要一个解析token信息的方法

5:需要将用户对象转换为字符串的方法。

public class JwtUtils {
    //设定服务器端用于加密的Key
    public static final String serverKey = "Mytoken";
    private static ObjectMapper objectMapper = new ObjectMapper();
    
    
    //将加密的KEY进行编码处理
    private static SecretKey generalKey(){
        try {
            byte[] bytes = serverKey.getBytes("UTF-8");
            SecretKey key = new SecretKeySpec(bytes,0,bytes.length,"AES");
            return key;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    //id表示唯一标识,可以使用用户id,或者uuid
    //iss 表示签发者。
    //subject 存储的用户信息。
    //ttlMillis 表示token的有效期
    //生成koten
    public static String createJWT(String id,String iss,String subject,long ttlMillis){
        //声明使用的加密算法
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        //获取当前时间
        long newMellis = System.currentTimeMillis();
        Date newDate = new Date(newMellis);
        SecretKey secretKey = generalKey();

        // 创建JWT的构建器。 就是使用指定的信息和加密算法,生成Token的工具。
        JwtBuilder builder = Jwts.builder()
                .setId(id)  // 设置身份标志。就是一个客户端的唯一标记。 如:可以使用用户的主键,客户端的IP,服务器生成的随机数据。
                .setIssuer(iss)
                .setSubject(subject)
                .setIssuedAt(newDate) // token生成的时间。
                .signWith(signatureAlgorithm, secretKey); // 设定密匙和算法
        if (ttlMillis >= 0) {
            long expMillis = newMellis + ttlMillis;
            Date expDate = new Date(expMillis); // token的失效时间。
            builder.setExpiration(expDate);
        }
        return builder.compact();
    }
    
    //解析token的信息
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();// getBody获取的就是token中记录的payload数据。就是payload中保存的所有的claims。
    }
    
    //验证token
    public static ResponseResult validateJWT(String jwt){
        if(jwt == null){
            return ResponseResult.error(CodeMsg.SERVEREXCEPTION);
        }
        Claims claims = null;
        try {
            claims = parseJWT(jwt);
            return ResponseResult.success(claims);
        } catch (ExpiredJwtException e) { // token超时
            return ResponseResult.error(CodeMsg.JWT_TimeOut);
        } catch (SignatureException e) { // 校验失败
            return ResponseResult.error(CodeMsg.JWT_verification_failed);
        } catch (Exception e) {
            return ResponseResult.error(CodeMsg.SERVEREXCEPTION);
        }
        
    }
    
    //将用户对象转换为JSON字符串
    public static String generalSubject(Object o){
        try {
            return objectMapper.writeValueAsString(o);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            return null;
        }
    }
    
}

4:自定义注解
package com.maven.jwt01.MyAnnocation;
//注解用来测试是否验证token

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Tokenverify {    
    public boolean Tokenverify() default true;   
}
5:自定义拦截器
public class TokenInterceptor implements HandlerInterceptor {
    @Autowired
    private UserService userService;
    
    private ObjectMapper objectMapper = new ObjectMapper();    

    //用来验证token
    @Override
    public boolean preHandle(HttpServletRequest request, 
                             HttpServletResponse response, 
                             Object object) throws Exception {
        //首先从请求头中获取token
        String token = request.getHeader("Authorization");
        //如果不是映射到Controller 的方法直接放行
        if (!(object instanceof HandlerMethod)){
            return true;
        }
        HandlerMethod handlerMethod = (HandlerMethod) object;
        Method method = handlerMethod.getMethod();
        //检查方法上是否有token验证注解
        if (method.isAnnotationPresent(Tokenverify.class)){
            //判断是否需要验证
            Tokenverify tokenverify = method.getAnnotation(Tokenverify.class);
            if(tokenverify.Tokenverify() == true ){
                if(token == null || token.equals("null") || token.trim() == "" ){
                    throw new RuntimeException(CodeMsg.JWT_ISNULL.getMessage());
                }
                //判断token的合法性
                ResponseResult responseResult = JwtUtils.validateJWT(token);
                if(responseResult.getData()!=null){
                    //获取用户信息
                    Claims claims = (Claims) responseResult.getData();
                    String subject = claims.getSubject();
                    User user = null;
                    try {
                        user = objectMapper.readValue(subject, User.class);
                    } catch (JsonProcessingException e) {
                        throw new RuntimeException(CodeMsg.JWT_ERRO.getMessage());
                    }
                }
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

6:全局异常处理
//全局的异常处理
@RestControllerAdvice
public class MyControllerAdvice {
    
    private Logger log = LoggerFactory.getLogger(MyControllerAdvice.class);
    
    @ExceptionHandler(Exception.class)
    public ResponseResult handlerException(Exception e){
        log.info("erro this {} ",e.getMessage());
        e.printStackTrace();
        CodeMsg codeMsg = new CodeMsg(500,e.getMessage());
        return ResponseResult.error(codeMsg);
    }   
    
}

7:Controller
@RestController
@RequestMapping("index")
public class WebController {
    
    private ObjectMapper objectMapper = new ObjectMapper();
    private Logger log = LoggerFactory.getLogger(WebController.class);
    
    @Autowired
    private UserService userService;
    
    //用户登录的方法
    @RequestMapping("login")
    public ResponseResult login(String data){
        User user =null;
        try {
            user = objectMapper.readValue(data, User.class);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        //1:验证登录
        User login_user = userService.login(user);
        //2:登录成功创建token
        if(login_user!=null){
            String jwt = JwtUtils.createJWT("" + login_user.getId(), JwtUtils.serverKey,
                    JwtUtils.generalSubject(login_user), 30 * 60 * 1000);
            return ResponseResult.success(jwt);
        }else{
            return ResponseResult.error(CodeMsg.SERVEREXCEPTION);
        }
    }
    
    //获取token中存放的用户信息的方法
    @Tokenverify
    @RequestMapping(value="getUserBytoken")
    public ResponseResult getUserBytoken(HttpServletRequest request){
        String token = request.getHeader("Authorization");
        ResponseResult responseResult = JwtUtils.validateJWT(token);
        if(responseResult.getData()!=null){
            //获取用户信息
            Claims claims = (Claims) responseResult.getData();
            String subject = claims.getSubject();
            User user =null;
            try {
                user = objectMapper.readValue(subject, User.class);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return ResponseResult.success(user);
        }
        return responseResult;
    }
    
}

8:Service
@Component
public class UserService {     
    //登录验证返回用户对象
    public User login(User user){
        if (user.getUsername().equals("admin") && user.getPassword().equals("admin")){
            user.setId(1);
            user.setPassword(null);
            return user;
        }
        return null;
    }    
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值