JWT认证原理及与spingboot整合

一.简介

JSON Web Token, 通过JSON形式作为Web应用中的令牌,用于各方之间安全地将信息作为JSON传输对象,在传输过程中完成数据加密签名等相关处理。(使用HMAC/RSA/ECDSA的公钥/私钥进行签名)

 

二.JWT能做什么?

  1. 授权

一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单点登录是当今广泛使用的一项功能,因为它的开销很小并且可以在不同的域中使用。

  1. 信息交换

Jsw是在各方(如两台服务器)之间安全传输信息的解决方式——可以对JWT进行签名(例如,使用公钥/私钥对),可确保调用方是确定的对象(验签)。此外,由于签名是使用标头和有效负载计算的,所以还可以验证内容是否遭到篡改。

 

三.基于传统的Session认证

    1.认证方式

  http协议是一种无状态的协议(如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不知道是哪个用户发出的请求,所以为了让应用能识别出是哪个用户发出的请求,只能在服务器存储一份用户登录的信息,这份登录信息(cookie)会在响应时传递给浏览器,以便下次请求时将cookie发给服务器,这样我们的服务器就能识别请求来自哪个用户了==>传统的基于session认证。

    2.认证流程

  客户端发起请求,认证通过 往session中存储用户信息,sessionid以响应的形式(cookie中记录了sessionId)写到客户端

 

  1. 暴漏问题

①每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录(以便用户下次请求的鉴别,通常而言session都保存在内存中,而随着认证用户的增多,服务端的开销会明显增大)。

②用户认证之后,服务端做认证记录,如果认证的记录保存在内存中的话,这意味着用户下次请求仍要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

③因为基于cookie来进行用户识别的,cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击

④在前后端分离系统中会更加痛苦:

前后端分离在应用解耦后增加了部署的复杂性。通常用户一次请求就要转发多次。如果用session,每次携带sessionId到服务器,服务器还要查询用户信息。同时如果用户很多,这些信息存储到服务器内存中,给服务器增加负担;CSRF(跨站伪造请求攻击),session是基于cookie进行用户识别的,cookie若被截获,用户很容易受到跨站请求伪造的攻击;session就是一个特征值,表达的信息不够丰富,不容易扩展,如果后端应用是多点部署,那么就需要实现session共享机制,不方便集群应用。

(前端web服务可能部署在nginx上,后端代理层路由网关又可能是个服务,路由网关中对应着真实的后端服务节点,用户发起请求会携带sessionId经过前端web,代理层,把网关sessionId传给后端应用的某个节点上,若后端应用是集群部署,还要实现session的共享)

 

 

 

四.基于JWT认证

Jwt完全是基于令牌的验证方式,而且是基于客户端的令牌存储(session是存储在服务器端)

认证流程:

  1. 前端通过Web表单将自己的用户名和密码发送到后端的接口,这一过程一般是一个HTTP POST请求。建议使用SSL加密传输(https协议),从而避免敏感信息被嗅探。
  2. 后端核对用户名和密码成功后,将用户的ID等其他信息作为JWT Playload,将其与头部分别进行Base64编码拼接后签名,形成一个JWT(token)
  3. 后端将JWT字符串作为登录成功的结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时,前端删除保存的JWT即可。
  4. 前端在每次请求时将JWT放入HTTP Head中的Authorization中。
  5. 后端检查是否存在,如存在验证JWT的有效性。如:检查签名是否正确;检查Token是否过期;检查Token的接收方式是否是自己;
  6. 验证通过后,后端使用JWT中包含的用户信息进行其他逻辑操作。返回相应结果。

五.Jwt优势:

  1. 简洁:可以通过URL,POST参数或在HTTP  header中发送,数据量小,传输速度也快。
  2. 自包含:负载中包含了所用用户 需要的信息,避免了多次查询数据库
  3. Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何WEB形式都支持。
  4. 不需要在服务器端保存会话信息,特别适用于分布式微服务。

 

六.JWT的结构:

1.Token组成:header.payload.signature(头.负载.签名)

2.Header由两部分:令牌类型(值为JWT)和所使用的签名算法(HMAC,SHA256,RSA)它会使用Base64编码组成JWT结构的第一部分

{
    “alg”:”HS256”,

     “typ”:”JWT”

}

注:Base64是一种编码,即它是可以被翻译回来的,它不是一种加密过程

3.Payload(有效负载)

其中包含声明。声明是有关实体(通常是用户)和其他数据的声明。同样的,它会使用Base64编码组成JWT结构的第二部分

{

    “sub”:”123456”,

    “name”:”xxx”,

    “role”:”admin”

}

  1. Signature

前面两部分都是使用Base64进行编码的,即前端可以解开知道其中的信息。Signature需要使用编码后的header和payload以及我们体哦那个的一个密钥,然后使用header中指定的签名算法(HS256)进行签名。签名的作用是保证JWT没有被篡改过。

如:

HMACSHA256(base64UrlEncode(header))+”.”+base64UrlEncode(payload),secret);

签名的目的:

最后一步签名的过程,是对头部以及负载内容进行签名,如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务端会判断出新的头部和负载形成的签名,其与JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时使用的密钥的话,得出来的签名也是不一样的。

信息安全问题:

Base64是一种编码,是可逆的,那么信息不就被暴露了吗?

-是的,所以在JWT中,不应该在负载中加入任何敏感数据,如密码,否则怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。

 

  • JWT与springboot整合利用到实战中

1.所需pom依赖

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>

2.封装JWT工具类

public class JWTUtils {

    private static final String SING = "!Q@W3e4r";
    //生成Token head.payload.sing
    public static String getToken(Map<String,String> map){
        Calendar instance = Calendar.getInstance();
        //过期时间
        instance.add(Calendar.SECOND,30);

        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();
        //payload
        map.forEach((k,v) ->{
           builder.withClaim(k,v);
        });
//        String token = JWT.create()
//                .withClaim("userId","001")
//                .withClaim("username","szh")//payload 负载
//                .withExpiresAt(instance.getTime())//指定令牌过期时间
//                .sign(Algorithm.HMAC256(SING));//签名
        String token = builder.withExpiresAt(instance.getTime())
                .sign(Algorithm.HMAC256(SING));
        return token;
    }

    //验证token合法性
    public static void verify(String token){
        JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
    }

    //验证后获取token中的信息
    public static DecodedJWT getTokenInfo(String token){
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
        return verify;
    }
}

2.编写controller

@RestController
@RequestMapping(path = "/user")
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping(path = "/login")
    public Map<String,Object> login(User user){
        Map<String,Object> map = new HashMap<>();
        try{
            User userDB =  userService.login(user);
            Map<String,String> payload = new HashMap<>();
            payload.put("id",userDB.getId());
            payload.put("name",userDB.getName());
            String token = JWTUtils.getToken(payload);
            map.put("state",true);
            map.put("msg","认证成功");
            map.put("token",token);
        }catch (Exception e){
            map.put("state",false);
            map.put("msg",e.getMessage());
        }
        return map;
    }

    @PostMapping("/test")
    public Map<String,Object> test(HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        //处理自己的业务逻辑
        String token = request.getHeader("token");
        DecodedJWT verify = JWTUtils.getTokenInfo(token);
        System.out.println("用户ID:"+verify.getClaim("id").asString());
        System.out.println("用户name:"+verify.getClaim("name").asString());
        map.put("state",true);
        map.put("msg","请求成功");
        return map;
    }
}

3.实现类

@Service
public class UserServiceImpl implements UserService {
    @Override
    public User login(User user) {
        //模拟数据库的用户 若数据库中存在与客户端传来的对象,则认证成功
        User userDB = new User("001","szh","admin");
        return userDB;
//      else  throw new RuntimeException("登录失败");
    }
}

3.配置JWT拦截器(从httpservletquest中拿到token,用于token验证)

public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler) throws Exception{
        Map<String,Object> map = new HashMap<>();
        //jwt藏在请求头中获取
        String token = request.getHeader("token");
        try{
            DecodedJWT tokenInfo = JWTUtils.getTokenInfo(token);
            return true;
        }catch (SignatureVerificationException e){
            e.printStackTrace();
            map.put("msg","无效签名");
        }catch (TokenExpiredException e){
            e.printStackTrace();
            map.put("msg","token过期");
        }catch (AlgorithmMismatchException e){
            e.printStackTrace();
            map.put("msg","toekn算法不一致");
        }catch (Exception e){
            e.printStackTrace();
            map.put("msg","token无效");
        }
        map.put("state",false);
        //将map转为json
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;
    }
}

4.添加 Interceptor配置类(实现WebMvcConfigurer配置类中的addInterceptorRegistry方法,用于添加拦截器,拦截路径,可放行路径)

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry){
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/user/test")//拦截(进行token验证)
                .excludePathPatterns("/user/login");//放行
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值