JWT令牌

1.普通令牌的问题

        客户端申请到令牌,接下来客户端携带令牌去访问资源,到资源服务器将会校验令牌的合法性。      

  

从第4步开始说明:

1、客户端携带令牌访问资源服务获取资源。

2、资源服务远程请求认证服务校验令牌的合法性

3、如果令牌合法资源服务向客户端返回资源。

这里存在一个问题:

就是校验令牌需要远程请求认证服务,客户端的每次访问都会远程校验,执行性能低。

如果能够让资源服务自己校验令牌的合法性将省去远程请求认证服务的成本,提高了性能。如下图:

         令牌采用JWT格式即可解决上边的问题,用户认证通过后会得到一个JWT令牌,JWT令牌中已经包括了用户相关的信息,客户端只需要携带JWT访问资源服务,资源服务根据事先约定的算法自行完成令牌校验,无需每次都请求认证服务完成授权。

2.什么是JWT令牌

JSON Web Token(JWT)是一种使用JSON格式传递数据的网络令牌技术,它是一个开放的行业标准(RFC 7519),它定义了一种简洁的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任,它可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止内容篡改。官网:JSON Web Tokens - jwt.io

使用JWT可以实现无状态认证,什么是无状态认证?

传统的基于session的方式是有状态认证,用户登录成功将用户的身份信息存储在服务端,这样加大了服务端的存储压力,并且这种方式不适合在分布式系统中应用。

如下图,当用户访问应用服务,每个应用服务都会去服务器查看session信息,如果没有则认证用户没有登录,此时就会重新认证,而解决这个问题的方法是Session复制、Session黏贴。

无状态认证

 如果是基于令牌技术在分布式系统中实现认证则服务端不用存储session,可以将用户身份信息存储在令牌中,用户认证通过后认证服务颁发令牌给用户,用户将令牌存储在客户端,去访问应用服务时携带令牌去访问,服务端从jwt解析出用户信息。这个过程就是无状态认证

 JWT令牌的优点:

  1. 基于json,非常方便解析
  2. 可以在jwt令牌中自定义丰富的内容,易扩展
  3. 通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高
  4. 资源服务使用JWT可不依赖认证服务就完成授权

缺点

JWT令牌较长,占用的存储空间较大

3.jwt结构分析

JWT令牌由三部分组成,每部分中间使用点(.)分隔,比如:xxxxx.yyyyy.zzzzz

  1. Header       

  头部包括令牌的类型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)

  一个例子如下:

  下边是Header部分的内容

JSON
   {
    "alg": "HS256",
    "typ": "JWT"
  }

  将上边的内容使用Base64Url编码,得到一个字符串就是JWT令牌的第一部分。

  1.  Payload

  第二部分是负载,内容也是一个json对象,它是存放有效信息的地方,它可以存放jwt提供的现成字段,比如:iss(签发者),exp(过期时间戳), sub(面向的用户)等,也可自定义字段。

  此部分不建议存放敏感信息,因为此部分可以解码还原原始内容。

  最后将第二部分负载使用Base64Url编码,得到一个字符串就是JWT令牌的第二部分。

  一个例子:

JSON
  {
    "sub": "1234567890",
    "name": "456",
    "admin": true
  }

  1.  Signature

  第三部分是签名,此部分用于防止jwt内容被篡改。

  这个部分使用base64url将前两部分进行编码,编码后使用点(.)连接组成字符串,最后使用header中声明签名算法进行签名。

  一个例子:

JSON
  HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret)

base64UrlEncode(header):jwt令牌的第一部分。

base64UrlEncode(payload):jwt令牌的第二部分。

secret:签名所使用的密钥。

4.为什么JWT可以防止篡改?

第三部分使用签名算法对第一部分和第二部分的内容进行签名,常用的签名算法是 HS256,常见的还有md5,sha 等,签名算法需要使用密钥进行签名,密钥不对外公开,并且签名是不可逆的,如果第三方更改了内容那么服务器验证签名就会失败,要想保证验证签名正确必须保证内容、密钥与签名前一致。

从上图可以看出认证服务和资源服务使用相同的密钥,这叫对称加密,对称加密效率高,如果一旦密钥泄露可以伪造jwt令牌。

JWT还可以使用非对称加密,认证服务自己保留私钥,将公钥下发给受信任的客户端、资源服务,公钥和私钥是配对的,成对的公钥和私钥才可以正常加密和解密,非对称加密效率低但相比对称加密非对称加密更安全一些。

4.测试生成jwt令牌

package com.xuecheng.auth.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import java.util.Arrays;

/**
 * @author Administrator
 * @version 1.0
 **/
@Configuration
public class TokenConfig {


    private String SIGNING_KEY = "mq123";

    @Autowired
    TokenStore tokenStore;


    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(SIGNING_KEY);
        return converter;
    }

    //令牌管理服务
    @Bean(name="authorizationServerTokenServicesCustom")
    public AuthorizationServerTokenServices tokenService() {
        DefaultTokenServices service=new DefaultTokenServices();
        service.setSupportRefreshToken(true);//支持刷新令牌
        service.setTokenStore(tokenStore);//令牌存储策略

        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        service.setTokenEnhancer(tokenEnhancerChain);

        service.setAccessTokenValiditySeconds(7200); // 令牌默认有效期2小时
        service.setRefreshTokenValiditySeconds(259200); // 刷新令牌默认有效期3天
        return service;
    }



}

postMan请求

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsieHVlY2hlbmctcGx1cyJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2NzczNzc3MDQsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6ImRkZGZlOTFkLWJiZTItNGEwOC1iZTI0LTQ4ZmM0NTdmMDIxNyIsImNsaWVudF9pZCI6IlhjV2ViQXBwIn0.8Yn2fGo2qckpgGSD-oOCHgQIIhxb9DfEBdLwB4VKDdE",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsieHVlY2hlbmctcGx1cyJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiJkZGRmZTkxZC1iYmUyLTRhMDgtYmUyNC00OGZjNDU3ZjAyMTciLCJleHAiOjE2Nzc2Mjk3MDQsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6ImI3NzczZjZjLTk5NmUtNGM0Yy04ZWVmLTJjMzZkMjRmZDEyNyIsImNsaWVudF9pZCI6IlhjV2ViQXBwIn0.hI9f5tHEvAE8SE5nLZkYZV_5n2K9DthSrblMoF4CUiE",
    "expires_in": 7198,
    "scope": "all",
    "jti": "dddfe91d-bbe2-4a08-be24-48fc457f0217"
}

1、access_token,生成的jwt令牌,用于访问资源使用。

2、token_type,bearer是在RFC6750中定义的一种token类型,在携带jwt访问资源时需要在head中加入bearer jwt令牌内容

3、refresh_token,当jwt令牌快过期时使用刷新令牌可以再次生成jwt令牌。

4、expires_in:过期时间(秒)

5、scope,令牌的权限范围,服务端可以根据令牌的权限范围去对令牌授权。

6、jti:令牌的唯一标识。

check_token接口校验jwt令牌

localhost:63070/auth/oauth/check_token?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsieHVlY2hlbmctcGx1cyJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJleHAiOjE2NzczNzc3MDQsImF1dGhvcml0aWVzIjpbInAxIl0sImp0aSI6ImRkZGZlOTFkLWJiZTItNGEwOC1iZTI0LTQ4ZmM0NTdmMDIxNyIsImNsaWVudF9pZCI6IlhjV2ViQXBwIn0.8Yn2fGo2qckpgGSD-oOCHgQIIhxb9DfEBdLwB4VKDdE

5.扩展JWT令牌的信息

在验证完毕用户的身份之后,可以将用户的信息转为json格式,作为username字段封装到jwt令牌中。这样做的原因是,避免了再写一个dto类。具体做法见下:

 @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        //查询到相应的数据库数据
        XcUser xcUser = userMapper.selectOne(new LambdaQueryWrapper<XcUser>().eq(XcUser::getUsername, s));
        //根据框架的调用情况,因为这个方法是被DaoAuthentication调用,所以要查看这个类是怎么调用的
        //查询之后发现,调用类在处理返回值时,如果是空,调用类会自己去抛异常
        if(xcUser == null){
            //我们这里直接返回空值,另外注意是有必要判断的,
            // 否则后面会报空指针异常
            return null;
        }
        String username = xcUser.getUsername();
        String password = xcUser.getPassword();
        //创建用户权限集合,如果不加,则会报can not pass a null GrantedAuthority collection
        String[] authorities = {"test"};//随便创建的一个权限集合
//        不能再json中保存密码,所以注释掉密码字段
        xcUser.setPassword(null);
        //此处转换成JSON对象的目的是,下面用user.withUserName的方法加入的信息太少
//        而此时已经校验过了用户的身份,所以将xcuser转换为json格式,保留全部的信息
        String userJson = JSON.toJSONString(xcUser);
        //因为要返回UserDetails类的对象,而这个类的对象创建起来十分麻烦,所以调用工具类创建
        UserDetails build = User.withUsername(userJson).password(password).authorities(authorities).build();

        return build;
    }

反向解析json:

6.网关统一鉴权做什么?

 

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值