欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第5.1.4 SpringCloud JWT

程序员文章站 2022-06-13 20:13:39
...

JWT遵循RFC 7519,详细协议描述参见rfc7519.txt.pdf,当然有人并不看好它,比如讲真,别再使用JWT了!
先暂且搁置这个问题,毕竟我不是黑客专家,不知道安全这道门到底有多深。先看看如何利用JWT来实现单点登录。我在第4.1.2章 WEB系统最佳实践 单点登录介绍过cas的原理。简单来说有两方面:1、token认证,token的生命周期(生产、流转、销毁等环节)2、重定向,如果token认证失败,则重定向到某个url,要求接入的子系统都需要设置。
看图理解JWT如何用于单点登录,这篇文章也有介绍jwt实现单点登录,也可以看看。
1 jwt是什么
JWT全面解读、使用步骤
也可以看看国服最强JWT生成Token做登录校验讲解,看完保证你学会
以前是cookie+session的方式,难道使用了jwt就能发生革命性的变化吗?如果服务端不留存jwt,那么验证就是客户端来验证的。客户端自验证jwt,那么重要的问题,就是安全问题。
2 jwt怎么用
2.1 pom.xml
版本现在已经0.9了。

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

2.2 统一门户登录时做了什么
关于vue的路由参见:第6.1.3 vue动态路由初探,这里说明一下jwt的身份认证

import router from './index'
import store from '../store'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css'// progress bar style
import { getToken } from '../utils/auth' // getToken from cookie
import { verify } from '@/api/login'
NProgress.configure({ showSpinner: false })// NProgress Configuration

const whiteList = ['/login', '/authredirect']// no redirect whitelist

router.beforeEach((to, from, next) => {
  NProgress.start() // start progress bar
  if (to.path === '/logout') {
    next()
  } else {
    if (getToken()) { // 判读是否有token
      let service = to.query.service
      if (service !== undefined) {
      //  存在service参数,则说明是子系统传递过来的,cas是不需要的。
        let serviceArr = JSON.parse(localStorage.getItem('service'))
        if (serviceArr !== null) {
          serviceArr.push(service.replace('login', 'logout'))
        } else {
          serviceArr = []
          serviceArr.push(service.replace('login', 'logout'))
        }
        localStorage.setItem('service', JSON.stringify(_.uniq(serviceArr)))
       // 校验token,如果校验通过,则进行页面重定向
        verify(getToken()).then(response => {
          window.location.href = service + '?ticket=' + getToken()
        }).catch(error => {
          console.log(error)
          store.dispatch('LogOut').then(() => {
            window.location.href = service
          })
        })
        return
      }
      if (to.path === '/login') {
        next({ path: '/index' })
        NProgress.done()
      } else {
        if (store.getters.user === undefined) {
          store.dispatch('GetInfo').then(info => {
            console.log(info)
            let userId = store.getters.user.id
            store.dispatch('GetQuickEntry', userId).then(info => {
              store.dispatch('GetSystem', userId).then(info => {
                next({ path: '/index' })
              })
            })
          }).catch(() => {
          })
        } else {
          next()
        }
      }
    } else {
      if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
        next()
      } else {
        next('/login') // 否则全部重定向到登录页
        NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
      }
    }
  }
})

router.afterEach(() => {
  NProgress.done() // finish progress bar
})

调试一下,发现跳转的页面是index
第5.1.4 SpringCloud JWT
那么这个index是怎么来的呢,可以在登录代码中找到原来,登录成功后,会通过this.$router.push进行页面跳转。
第5.1.4 SpringCloud JWT
接下来的问题,页面跳转的时候,要进行token验证,那么token是怎么产生的呢?
2.3 token产生
这里先聊一下vuex,理解vuex – vue的状态管理模式vuex是一个状态容器,vuex的action需要通过store.dispatch进行触发,
第5.1.4 SpringCloud JWT
通过上面这段代码,就可以知道登录成功后,将token写入到cookie中了

import Cookies from 'js-cookie'

const TokenKey = 'Authorization'

export function getToken () {
  return Cookies.get(TokenKey)
}

export function setToken (token) {
  return Cookies.set(TokenKey, token)
}

export function removeToken () {
  return Cookies.remove(TokenKey)
}

浏览器中就可以看到对应的cookie值了。
第5.1.4 SpringCloud JWT
java后台侧用的jwt又是如何产生的呢?注意下面的代码使用的是claims记录用户身份信息,参考json web token

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
/**
     * **加密token
     *
     * @param jwtInfo
     * @param priKey
     * @param expire
     * @return
     * @throws Exception
     */
    public static String generateToken(IJWTInfo jwtInfo, byte priKey[], int expire) throws Exception {
        String compactJws = Jwts.builder()
                .setSubject(jwtInfo.getUniqueName())
                .claim(CommonConstants.JWT_KEY_USER_ID, jwtInfo.getId())
                .claim(CommonConstants.JWT_KEY_NAME, jwtInfo.getName())
                .setExpiration(DateTime.now().plusSeconds(expire).toDate())
                .signWith(SignatureAlgorithm.RS256, rsaKeyHelper.getPrivateKey(priKey))
                .compact();
        return compactJws;
    }
public class JWTInfo implements Serializable,IJWTInfo {
    private String username;
    private String userId;
    private String name;

    public JWTInfo(String username, String userId, String name) {
        this.username = username;
        this.userId = userId;
        this.name = name;
    }

    @Override
    public String getUniqueName() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    @Override
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        JWTInfo jwtInfo = (JWTInfo) o;

        if (username != null ? !username.equals(jwtInfo.username) : jwtInfo.username != null) {
            return false;
        }
        return userId != null ? userId.equals(jwtInfo.userId) : jwtInfo.userId == null;

    }

    @Override
    public int hashCode() {
        int result = username != null ? username.hashCode() : 0;
        result = 31 * result + (userId != null ? userId.hashCode() : 0);
        return result;
    }
}

2.4 token加密
jwt的加密是顺着上面来解释的,
jwt的参数配置,expire的单位是秒,14400标识4个小时,而rsa-secret是公私钥对的密码。

jwt:
  token-header: Authorization
  expire: 14400
  rsa-secret: lm123T12^%8904645

这里用的RS256算法,但io.jsonwebtoken并不仅支持这一种,看源码,可以看到支持

HS256("HS256", "HMAC using SHA-256", "HMAC", "HmacSHA256", true), 

  HS384("HS384", "HMAC using SHA-384", "HMAC", "HmacSHA384", true), 

  HS512("HS512", "HMAC using SHA-512", "HMAC", "HmacSHA512", true), 

  RS256("RS256", "RSASSA-PKCS-v1_5 using SHA-256", "RSA", "SHA256withRSA", true), 

  RS384("RS384", "RSASSA-PKCS-v1_5 using SHA-384", "RSA", "SHA384withRSA", true), 

  RS512("RS512", "RSASSA-PKCS-v1_5 using SHA-512", "RSA", "SHA512withRSA", true), 

  ES256("ES256", "ECDSA using P-256 and SHA-256", "Elliptic Curve", "SHA256withECDSA", false), 

  ES384("ES384", "ECDSA using P-384 and SHA-384", "Elliptic Curve", "SHA384withECDSA", false), 

  ES512("ES512", "ECDSA using P-512 and SHA-512", "Elliptic Curve", "SHA512withECDSA", false), 

  PS256("PS256", "RSASSA-PSS using SHA-256 and MGF1 with SHA-256", "RSA", "SHA256withRSAandMGF1", false), 

  PS384("PS384", "RSASSA-PSS using SHA-384 and MGF1 with SHA-384", "RSA", "SHA384withRSAandMGF1", false), 

  PS512("PS512", "RSASSA-PSS using SHA-512 and MGF1 with SHA-512", "RSA", "SHA512withRSAandMGF1", false);

生成 JWT (jwt-generate),这篇文章提到:

对于算法类型 HS256、HS384 和 HS512,引用的加密对象必须为“共享**”。
对于算法类型 RS256、RS384、RS512、ES256、ES384 和 ES512,引用的加密对象必须为“加***(专用**)”。
加密资料可通过 JSON Web ** (JWK) 提供。
如果指定了加密对象和 JWK,那么加密对象用于对 JWT 进行签名

通过在线生成非对称加密公钥私钥对,但是公钥、私钥的存储是需要加密的。安全级别高的,可以将**由加密机硬件分散出来。普通系统倒不能那么麻烦,生成公私钥对可以将它存储到redis中。
那么公私钥对,又是如何加载的呢?下面代码要用到两个公私钥对,如果发现redis没有,则通过程序RsaKeyHelper.generateKey(keyConfiguration.getUserSecret());产生并写入。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.Map;

@Configuration
public class AuthServerRunner implements CommandLineRunner {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String REDIS_USER_PRI_KEY = "AG:AUTH:JWT:PRI";
    private static final String REDIS_USER_PUB_KEY = "AG:AUTH:JWT:PUB";
    private static final String REDIS_SERVICE_PRI_KEY = "AG:AUTH:CLIENT:PRI";
    private static final String REDIS_SERVICE_PUB_KEY = "AG:AUTH:CLIENT:PUB";

    @Autowired
    private KeyConfiguration keyConfiguration;

    @Override
    public void run(String... args) throws Exception {
        if (redisTemplate.hasKey(REDIS_USER_PRI_KEY)&&redisTemplate.hasKey(REDIS_USER_PUB_KEY)&&redisTemplate.hasKey(REDIS_SERVICE_PRI_KEY)&&redisTemplate.hasKey(REDIS_SERVICE_PUB_KEY)) {
            keyConfiguration.setUserPriKey(RsaKeyHelper.toBytes(redisTemplate.opsForValue().get(REDIS_USER_PRI_KEY).toString()));
            keyConfiguration.setUserPubKey(RsaKeyHelper.toBytes(redisTemplate.opsForValue().get(REDIS_USER_PUB_KEY).toString()));

        } else {
            Map<String, byte[]> keyMap = RsaKeyHelper.generateKey(keyConfiguration.getUserSecret());
            keyConfiguration.setUserPriKey(keyMap.get("pri"));
            keyConfiguration.setUserPubKey(keyMap.get("pub"));
            redisTemplate.opsForValue().set(REDIS_USER_PRI_KEY, RsaKeyHelper.toHexString(keyMap.get("pri")));
            redisTemplate.opsForValue().set(REDIS_USER_PUB_KEY, RsaKeyHelper.toHexString(keyMap.get("pub")));

            redisTemplate.opsForValue().set(REDIS_SERVICE_PRI_KEY, RsaKeyHelper.toHexString(keyMap.get("pri")));
            redisTemplate.opsForValue().set(REDIS_SERVICE_PUB_KEY, RsaKeyHelper.toHexString(keyMap.get("pub")));

        }
    }
}

产生公私钥的java代码

import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

public static Map<String, byte[]> generateKey(String password) throws IOException, NoSuchAlgorithmException {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        SecureRandom secureRandom = new SecureRandom(password.getBytes());
        keyPairGenerator.initialize(1024, secureRandom);
        KeyPair keyPair = keyPairGenerator.genKeyPair();
        byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
        byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
        Map<String, byte[]> map = new HashMap<String, byte[]>();
        map.put("pub", publicKeyBytes);
        map.put("pri", privateKeyBytes);
        return map;
    }
相关标签: jwt