spring boot 项目部署到k8s笔记(六)

原创
2020/06/05 13:25
阅读数 408

延续https://my.oschina.net/yjwu/blog/4288146

研究怎么要增加jwt身份验证和网关限流。首先istio好像不支持jwt的HS512,所以考虑把spring boot 工程改造成使用RS512算法 。

先写一个demo生成证书和私钥公钥,得到证书和公钥的jwks字符串

package com.example;

import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;

import java.util.UUID;

public class GenJwks {
    public static void main(String[] args) throws Exception {
//证书生成
        RSAKey jwk = new RSAKeyGenerator(2048)
                .keyUse(KeyUse.SIGNATURE) // indicate the intended use of the key
                .keyID(UUID.randomUUID().toString()) // give the key a unique ID
                .generate();
        System.out.println(jwk.toPublicJWK().toJSONString()); //输出的公钥json字符串
        System.out.println(jwk.toJSONString());//输出的证书字符串

        RSAKey newjwk= RSAKey.parse(jwk.toJSONString());

        System.out.println(newjwk.toPublicJWK().toJSONString());
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>jwks-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>jwks-rsa</artifactId>
            <version>0.11.0</version>
        </dependency>
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>8.17.1</version>
        </dependency>

        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>public</id>
            <name>public</name>
            <url>https://repo.spring.io/libs-milestone/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>


    </repositories>

    <pluginRepositories>

        <pluginRepository>
            <id>public</id>
            <name>public</name>
            <url>https://repo.spring.io/libs-milestone/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

输出的结果

#证书
{"p":"_1SedXZMEbmc4iCVasOu49RYo_q8Kd3PnLUDpfZLRidSF1JlZrx9TphCQW9MObWFn0TS3M8HFAzpN6D3JSly8XdKkvTV07vmPO6xfcyPL1CoNwhYEXeYUs4V5fEqzH0eSByeHbJGMPELCe91BsjFmmjOgAB64T_mPdIhQchEp9E","kty":"RSA","q":"kBBvN5uFCZC7VKU791w-379y1p_U7oR6KfZ0yG4oJxvRVfb1IWoIJhp6UmqiLo2_WdAXL5oBgOsynTxB_HcKwpioEbaN20paKOpAwEJyTd4EPByYwcPTFJNsLx9mRrFLwgX5FlOhmZFxZkhtyYOjw_CwuH2fQwZ5yOwvJXa2VDc","d":"Oee3lrKcybCU8yWPy9OY8Cb1GndZIc_yNR8HKFlZKyfxjlnBDTBbaatlf8ADHJ-CMzBeG_kyeCPVVnN1I1TMPdnwvc1NpX0S4cUqHbU-pjtR0iVspziKxcvho_t-m_2yOebnDfW1hFq2iG3_3KMSzDF_EdIAK3LE-WTOxKHNfgGxoZ6RdnoZpY0OxxnmsnUI-zTcNYFmqkSfm4oHvPn4uSW3XFFeGeNgzJZBBWsQCsk8UnxHWX8rJldyMApM7cwjWlsTPVCDhW3D4j9qqLTRWHXs28QUD8j54YOAhFeZHwE6SacprFQuXlbiMUXQi45PzJESNVIPIy4xXUetNjD8oQ","e":"AQAB","use":"sig","kid":"67decca9-2af0-409a-8355-e88b499ad8a5","qi":"HDGq9ZcE9DAvY-k76qQHX2if0KuaZowll42BzP9QGMHF8JM2KnkQ_F9cDOkKnMHRDCOshO7NGenT4ig46vbU7q5C2Rla5ziweQjT2KFyraGKgy_Cy3NEg5ogNKeGCcIQhDUYsMhwIBruM1rtwmATKsUzW0wfDebYFh0xtebbtlM","dp":"AZPvSsCJZc6k1ozm_3roGMZWKeVBxmx1fP1tYVgtTOivTC-ZKtJdX9_3ANqEORMTGVHej7jDzW7q_goDnPotmKppDpFuxNkzagr7k9BwzhUMhRKLIMiBa3mUjxA3eH4Jct6iZ4KThnfm3o_ZREp3ViXxqL7YA45WiOlEhViMfBE","dq":"RIJsQWeF5rybOW-yirmldMYYmJQ9sIfziI0ZkE9CbQa_kD_25sMDyQsCbLslETp7avyYahy05lfzI-8J-kOqLExocLP91fEP2zE7RbLTpNAV93gp9MtpT_mjku09uBSMUGKCx-lRijQuV1POUex2LrIBeFrVKAymUbRv30MLBhU","n":"j6_9WSDiFLKviU4pyjy0XRcDdG0ZiGFbIjLt6qk74Myuzh-I9ednuWyGEHkmjhDLz3Gkj2juJh2T6D2asnczFA5yvg82agJFkI0W1dbY50Du9t8jlsj-GAGLoBb6XQkkfW_94Dvlsan5YZCDxZl2c0B15e-3PVEfzdi-xVWPinE9E2H57N9xktDWAn4XCXyuSMebFCG5oeBppfiVis2RcnaqPubrKo7nRyo9VDpRLDjqgDYMlSQxkrd8V-RWWQQY6Y7rHxnowUUNsLuzCzX3jurYpUs9Npo_HIvwtvdjZ-WMvIw-YUANTI6bqd2AC2tKi7b7X200ce27imE3Mmah5w"}
#公钥
{"kty":"RSA","e":"AQAB","use":"sig","kid":"67decca9-2af0-409a-8355-e88b499ad8a5","n":"j6_9WSDiFLKviU4pyjy0XRcDdG0ZiGFbIjLt6qk74Myuzh-I9ednuWyGEHkmjhDLz3Gkj2juJh2T6D2asnczFA5yvg82agJFkI0W1dbY50Du9t8jlsj-GAGLoBb6XQkkfW_94Dvlsan5YZCDxZl2c0B15e-3PVEfzdi-xVWPinE9E2H57N9xktDWAn4XCXyuSMebFCG5oeBppfiVis2RcnaqPubrKo7nRyo9VDpRLDjqgDYMlSQxkrd8V-RWWQQY6Y7rHxnowUUNsLuzCzX3jurYpUs9Npo_HIvwtvdjZ-WMvIw-YUANTI6bqd2AC2tKi7b7X200ce27imE3Mmah5w"}

开始改造spring boot的jwt验证方式。

在原来的基础上增加配置项(这里增加了token.jwks 和 token.issuer)

# token配置
token:
    # 令牌自定义标识
    header: Authorization
    # 令牌秘钥
    secret: abcdefghijklmnopqrstuvwxyz
    # 令牌有效期(默认30分钟)
    expireTime: 30
    #RSA jwks json RSA512 2048 证书
    jwks: "{\"p\":\"_1SedXZMEbmc4iCVasOu49RYo_q8Kd3PnLUDpfZLRidSF1JlZrx9TphCQW9MObWFn0TS3M8HFAzpN6D3JSly8XdKkvTV07vmPO6xfcyPL1CoNwhYEXeYUs4V5fEqzH0eSByeHbJGMPELCe91BsjFmmjOgAB64T_mPdIhQchEp9E\",\"kty\":\"RSA\",\"q\":\"kBBvN5uFCZC7VKU791w-379y1p_U7oR6KfZ0yG4oJxvRVfb1IWoIJhp6UmqiLo2_WdAXL5oBgOsynTxB_HcKwpioEbaN20paKOpAwEJyTd4EPByYwcPTFJNsLx9mRrFLwgX5FlOhmZFxZkhtyYOjw_CwuH2fQwZ5yOwvJXa2VDc\",\"d\":\"Oee3lrKcybCU8yWPy9OY8Cb1GndZIc_yNR8HKFlZKyfxjlnBDTBbaatlf8ADHJ-CMzBeG_kyeCPVVnN1I1TMPdnwvc1NpX0S4cUqHbU-pjtR0iVspziKxcvho_t-m_2yOebnDfW1hFq2iG3_3KMSzDF_EdIAK3LE-WTOxKHNfgGxoZ6RdnoZpY0OxxnmsnUI-zTcNYFmqkSfm4oHvPn4uSW3XFFeGeNgzJZBBWsQCsk8UnxHWX8rJldyMApM7cwjWlsTPVCDhW3D4j9qqLTRWHXs28QUD8j54YOAhFeZHwE6SacprFQuXlbiMUXQi45PzJESNVIPIy4xXUetNjD8oQ\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"67decca9-2af0-409a-8355-e88b499ad8a5\",\"qi\":\"HDGq9ZcE9DAvY-k76qQHX2if0KuaZowll42BzP9QGMHF8JM2KnkQ_F9cDOkKnMHRDCOshO7NGenT4ig46vbU7q5C2Rla5ziweQjT2KFyraGKgy_Cy3NEg5ogNKeGCcIQhDUYsMhwIBruM1rtwmATKsUzW0wfDebYFh0xtebbtlM\",\"dp\":\"AZPvSsCJZc6k1ozm_3roGMZWKeVBxmx1fP1tYVgtTOivTC-ZKtJdX9_3ANqEORMTGVHej7jDzW7q_goDnPotmKppDpFuxNkzagr7k9BwzhUMhRKLIMiBa3mUjxA3eH4Jct6iZ4KThnfm3o_ZREp3ViXxqL7YA45WiOlEhViMfBE\",\"dq\":\"RIJsQWeF5rybOW-yirmldMYYmJQ9sIfziI0ZkE9CbQa_kD_25sMDyQsCbLslETp7avyYahy05lfzI-8J-kOqLExocLP91fEP2zE7RbLTpNAV93gp9MtpT_mjku09uBSMUGKCx-lRijQuV1POUex2LrIBeFrVKAymUbRv30MLBhU\",\"n\":\"j6_9WSDiFLKviU4pyjy0XRcDdG0ZiGFbIjLt6qk74Myuzh-I9ednuWyGEHkmjhDLz3Gkj2juJh2T6D2asnczFA5yvg82agJFkI0W1dbY50Du9t8jlsj-GAGLoBb6XQkkfW_94Dvlsan5YZCDxZl2c0B15e-3PVEfzdi-xVWPinE9E2H57N9xktDWAn4XCXyuSMebFCG5oeBppfiVis2RcnaqPubrKo7nRyo9VDpRLDjqgDYMlSQxkrd8V-RWWQQY6Y7rHxnowUUNsLuzCzX3jurYpUs9Npo_HIvwtvdjZ-WMvIw-YUANTI6bqd2AC2tKi7b7X200ce27imE3Mmah5w\"}"
    #issuer
    issuer: "ruoyi@secure.istio.io"

证书的注入 

package com.ruoyi.framework.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.nimbusds.jose.jwk.RSAKey;
import org.springframework.core.env.Environment;

@Configuration
public class RSAConfig {


      // RSA512 2048 证书
    @Bean(name = "jwks")
    public RSAKey jwks( Environment env) throws  Exception{
        String jwks =env.getProperty("token.jwks");

        return RSAKey.parse(jwks);

    }
}

生成jwt和验证jwt都改成了非对称加密形式 (原来代码基础上改了几个方法)

package com.ruoyi.framework.security.service;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;

import com.nimbusds.jose.jwk.RSAKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.utils.IdUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.ip.AddressUtils;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.redis.RedisCache;
import com.ruoyi.framework.security.LoginUser;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

/**
 * token验证处理
 * 
 * @author ruoyi
 */
@Component
public class TokenService
{
    // 令牌自定义标识
    @Value("${token.header}")
    private String header;

    // 令牌秘钥
    @Value("${token.secret}")
    private String secret;

    // 令牌有效期(默认30分钟)
    @Value("${token.expireTime}")
    private int expireTime;

    //证书颁布人
    @Value("${token.issuer}")
    private String issuer;


    protected static final long MILLIS_SECOND = 1000;

    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

    @Autowired
    private RedisCache redisCache;
    // RSA512 2048 证书
    @Autowired
    @Qualifier(value = "jwks")
    private RSAKey rsaKey;

    private static final Logger logger = LoggerFactory.getLogger(TokenService.class.getName());

    /**
     * 获取用户身份信息
     * 
     * @return 用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request)
    {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token))
        {
            Claims claims = parseToken(token);
            // 解析对应的权限以及用户信息
            String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
            String userKey = getTokenKey(uuid);
            LoginUser user = redisCache.getCacheObject(userKey);
            return user;
        }
        return null;
    }

    /**
     * 设置用户身份信息
     */
    public void setLoginUser(LoginUser loginUser)
    {
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken()))
        {
            refreshToken(loginUser);
        }
    }

    /**
     * 删除用户身份信息
     */
    public void delLoginUser(String token)
    {
        if (StringUtils.isNotEmpty(token))
        {
            String userKey = getTokenKey(token);
            redisCache.deleteObject(userKey);
        }
    }

    /**
     * 创建令牌
     * 
     * @param loginUser 用户信息
     * @return 令牌
     */
    public String createToken(LoginUser loginUser)
    {
        String token = IdUtils.fastUUID();
        loginUser.setToken(token);
        setUserAgent(loginUser);
        refreshToken(loginUser);

        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_USER_KEY, token);
        return createToken(claims);
    }

    /**
     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
     * 
     * @param token 令牌
     * @return 令牌
     */
    public void verifyToken(LoginUser loginUser)
    {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
        {
            refreshToken(loginUser);
        }
    }

    /**
     * 刷新令牌有效期
     * 
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser)
    {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }
    
    /**
     * 设置用户代理信息
     * 
     * @param loginUser 登录信息
     */
    public void setUserAgent(LoginUser loginUser)
    {
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOperatingSystem().getName());
    }
    
    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String createToken(Map<String, Object> claims)
    {
        /*
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();

         */
        long endTime = new Date().getTime() + expireTime * MILLIS_MINUTE;
        String token = null;
        try {
            token = Jwts.builder()
                    .setClaims(claims)
                    .setIssuer(issuer)
                    .setExpiration(new Date(endTime))
                    .signWith(SignatureAlgorithm.RS512, rsaKey.toPrivateKey()).compact();
        } catch (Exception e) {
            logger.error("",e);
        }
        return token;
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims parseToken(String token)
    {
        /*
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();*/
        try {
            return Jwts.parser()
                    .setSigningKey(rsaKey.toPublicKey())
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            logger.error("",e);
        }
        return null;
    }

    /**
     * 从令牌中获取用户名
     *
     * @param token 令牌
     * @return 用户名
     */
    public String getUsernameFromToken(String token)
    {
        Claims claims = parseToken(token);
        return claims.getSubject();
    }

    /**
     * 获取请求token
     *
     * @param request
     * @return token
     */
    private String getToken(HttpServletRequest request)
    {
        String token = request.getHeader(header);
        if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX))
        {
            token = token.replace(Constants.TOKEN_PREFIX, "");
        }
        return token;
    }

    private String getTokenKey(String uuid)
    {
        return Constants.LOGIN_TOKEN_KEY + uuid;
    }
}

运行登录测试OK ,然后增加istio的crds 。

#jwt验证(有authorization头的进行验证,其他没有authorization头的放行)
apiVersion: "security.istio.io/v1beta1"
kind: "RequestAuthentication"
metadata:
  name: "ruoyi-jwt"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  jwtRules:
  - issuer: "ruoyi@secure.istio.io"
    #公钥json 外面套一层 {"keys":[]}
    jwks: "{\"keys\":[{\"kty\":\"RSA\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"67decca9-2af0-409a-8355-e88b499ad8a5\",\"n\":\"j6_9WSDiFLKviU4pyjy0XRcDdG0ZiGFbIjLt6qk74Myuzh-I9ednuWyGEHkmjhDLz3Gkj2juJh2T6D2asnczFA5yvg82agJFkI0W1dbY50Du9t8jlsj-GAGLoBb6XQkkfW_94Dvlsan5YZCDxZl2c0B15e-3PVEfzdi-xVWPinE9E2H57N9xktDWAn4XCXyuSMebFCG5oeBppfiVis2RcnaqPubrKo7nRyo9VDpRLDjqgDYMlSQxkrd8V-RWWQQY6Y7rHxnowUUNsLuzCzX3jurYpUs9Npo_HIvwtvdjZ-WMvIw-YUANTI6bqd2AC2tKi7b7X200ce27imE3Mmah5w\"}]}"
    #继续传递header 
    forwardOriginalToken: true
    #跟spring boot使用token格式一致
    fromHeaders:
    - name: "authorization"
      prefix: "Bearer "
---
#没有jwt头的限流,没有authorization头的不给通过
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: "ruoyi-jwt-policy"
  namespace: istio-system
spec:
  selector:
    matchLabels:
      istio: ingressgateway
  action: DENY
  rules:
  - to:
    - operation:
        paths: ["/ruoyi/prod-api/*"]
        #登录和验证码的除外
        notPaths: ["/ruoyi/prod-api/login","/ruoyi/prod-api/captchaImage"]
    when:
    - key: request.headers[authorization]
      notValues: ["Bearer *"]

helm install ruoyi ruoyi 测试登录查询等功能OK 。

为了测试有没有真的起效果, 把 

jwtRules:
  - issuer: "ruoyi@secure.istio.io"

改成

jwtRules:
  - issuer: "1ruoyi@secure.istio.io"

结果登录报错,的确是issuer错了,恢复后OK

恢复后不加token直接访问 https://127.0.0.1/ruoyi/prod-api/getRouters ,看上去已发挥了限流作用

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部