SpringCloud(九)之Zuul(实例优化动态配置、jwt、rsa)

转载请注明出处:https://blog.csdn.net/weixin_41133233/article/details/85070323
本文出自 程熙cjp 的博客

继上一篇讲述完Zuul的详解和实例之后,本篇小熙将会讲述Zuul的实例优化动态配置、jwt、rsa。

一. 案例代码

  1. yml添加数据,便于修改和动态获取

    sc:
      jwt:
       secret: sc@Login(Auth}*^31)&chengxi%       # 登录校验的密钥
       pubKeyPath: D:/rsa/rsa.pub               # 公钥地址
       priKeyPath: D:/rsa/rsa.pri                # 私钥地址
       expire: 30                                 # 过期时间,单位分钟
    
  2. Config的LoginFilter类修改之后的

    package com.chengxi.config;
    
    import com.chengxi.jwt.JWTUtil;
    import com.chengxi.jwt.JwtUtils;
    import com.chengxi.ras.RsaUtils;
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import io.jsonwebtoken.Claims;
    import org.bouncycastle.jcajce.provider.asymmetric.rsa.RSAUtil;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.http.HttpStatus;
    import org.springframework.stereotype.Component;
    
    import javax.servlet.http.HttpServletRequest;
    import java.security.PrivateKey;
    import java.security.PublicKey;
    
    /**
     *  编辑ZuulFilter自定义过滤器,用于校验登录
     *  重写zuulFilter类,有四个重要的方法
     *  1.- `shouldFilter`:返回一个`Boolean`值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
     *  2.- `run`:过滤器的具体业务逻辑。
     *  3.- `filterType`:返回字符串,代表过滤器的类型。包含以下4种:
     *      - `pre`:请求在被路由之前执行
     *      - `routing`:在路由请求时调用
     *      - `post`:在routing和errror过滤器之后调用
     *      - `error`:处理请求时发生错误调用
     *  4.- `filterOrder`:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高
     *
     *
     * @author chengxi
     * @date 2018/12/5 17:24
     */
    @Component
    public class LoginFilter extends ZuulFilter {
       
       // 从yml配置文件中获取配置的数据
        @Value("${sc.jwt.secret}")
        private String secret;
    
        @Value("${sc.jwt.pubKeyPath}")
        private String pubKeyPath;
    
        @Value("${sc.jwt.priKeyPath}")
        private String priKeyPath;
    
        @Value("${sc.jwt.expire}")
        private Integer expire;
    
        private PublicKey publicKey;
    
        private PrivateKey privateKey;
    
    
        @Override
        public String filterType() {
            // 登录校验的过滤级别,肯定是第一层过滤
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            // 执行顺序为1,值越小执行顺行越靠前
            return 1;
        }
    
        /**
         * 默认此类过滤器时false,不开启的,需要改为true
         * @return
         */
        @Override
        public boolean shouldFilter() {
            // 登录校验逻辑
            // 1)获取zuul提供的请求上下文对象(即是请求全部内容)
            RequestContext currentContext = RequestContext.getCurrentContext();
            // 2) 从上下文中获取request对象
            HttpServletRequest request = currentContext.getRequest();
            // 3) 从请求中获取url
            String url= request.getRequestURI();
    
            // 4) 判断用户是否是注册请求(因为只是一个简单的案例,auth服务并没有抒写,所以这里简单判断下。这里本应该是由auth服务处理的)
            if(url.indexOf("register") != -1){
                String token = setRegisterToken();
                // 没有token,认为登录校验失败,进行拦截
                currentContext.setSendZuulResponse(false);
                // 生成token返回
                currentContext.setResponseBody(token);
                // 返回201状态码。表示生成token返回
                currentContext.setResponseStatusCode(HttpStatus.CREATED.value());
            }
    
            // 3) 从请求中获取token
            String token = request.getParameter("access-token");
            // 4) 判断(如果没有token,认为用户还没有登录,返回401状态码)
            if(token == null || "".equals(token.trim())){
                // 没有token,认为登录校验失败,进行拦截
                currentContext.setSendZuulResponse(false);
                // 返回401状态码。也可以考虑重定向到登录页
                currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
    
                return true;
            }else{
                getKey();
                System.out.println("privateKey: "+privateKey);
                String claims = null;
                try {
                    claims = JwtUtils.getInfoFromToken(token, publicKey);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("解析token出错");
                }
                System.out.println("claims:"+claims);
    
                // 如果解析成功,则不再进入过滤器,否则进入
                if(claims != null){
                    return false;
                }else{
    
                    // token错误,认为登录校验失败,进行拦截
                    currentContext.setSendZuulResponse(false);
                    // 返回401状态码。也可以考虑重定向到登录页
                    currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
    
                    return true;
                }
            }
        }
    
        /**
         *  登录校验过滤器,执行逻辑的方法
         * @return
         * @throws ZuulException
         */
        @Override
        public Object run() throws ZuulException {
            System.out.println("拦截器run方法");
    
    
    //        // 登录校验逻辑
    //        // 1)获取zuul提供的请求上下文对象(即是请求全部内容)
    //        RequestContext currentContext = RequestContext.getCurrentContext();
    //        // 2) 从上下文中获取request对象
    //        HttpServletRequest request = currentContext.getRequest();
    //        // 3) 从请求中获取token
    //        String token = request.getParameter("access-token");
    //        // 4) 判断(如果没有token,认为用户还没有登录,返回401状态码)
    //        if(token == null || "".equals(token.trim())){
    //            // 没有token,认为登录校验失败,进行拦截
    //            currentContext.setSendZuulResponse(false);
    //            // 返回401状态码。也可以考虑重定向到登录页
    //            currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
    //        }
    
            // 如果校验通过,可以考虑吧用户信息放入上下文,继续向后执行
            return null;
        }
    
        public String setRegisterToken() {
            try {
                // 生成公私秘钥
                RsaUtils.generateKey(pubKeyPath,priKeyPath,secret);
    
                getKey();
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("生成公私秘钥抛错");
            }
    
            //生成token
            String token = null;
            try {
                token = JwtUtils.generateToken(1,"小熙",privateKey,30);
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("生成token报错");
            }
            System.out.println("生成的token:"+token);
    
            return token;
        }
    
    
        public void getKey(){
            // 获取公私秘钥
            try {
                publicKey = RsaUtils.getPublicKey(pubKeyPath);
                System.out.println("publicKey: "+publicKey);
                privateKey = RsaUtils.getPrivateKey(priKeyPath);
                System.out.println("privateKey: "+privateKey);
            } catch (Exception e) {
                e.printStackTrace();
                System.out.println("获取公私钥出错");
            }
        }
    
    
    }
    
    
  3. jwt工具包(生成token和解析token)

    package com.chengxi.jwt;
    
    import com.chengxi.ras.RsaUtils;
    import io.jsonwebtoken.Claims;
    import io.jsonwebtoken.Jws;
    import io.jsonwebtoken.Jwts;
    import io.jsonwebtoken.SignatureAlgorithm;
    import org.joda.time.DateTime;
    import org.springframework.util.ObjectUtils;
    
    import java.security.PrivateKey;
    import java.security.PublicKey;
    
    /**
     * @author: XinQi.Yuan
     *      修改: chengxi
     * @create: 2018-05-26 15:43
     **/
    public class JwtUtils {
        /**
         * 私钥加密token
         *
         * @param userId、userName      载荷中的数据
         * @param privateKey    私钥
         * @param expireMinutes 过期时间,单位秒
         * @return
         * @throws Exception
         */
        public static String generateToken(Integer userId, String userName, PrivateKey privateKey, int expireMinutes) throws Exception {
            return Jwts.builder()
                    .claim("userId", userId)
                    .claim("userName", userName)
                    .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                    .signWith(SignatureAlgorithm.RS256, privateKey)
                    .compact();
        }
    
        /**
         * 私钥加密token
         *
         * @param userId、userName      载荷中的数据
         * @param privateKey    私钥字节数组
         * @param expireMinutes 过期时间,单位秒
         * @return
         * @throws Exception
         */
        public static String generateToken(Integer userId, String userName, byte[] privateKey, int expireMinutes) throws Exception {
            return Jwts.builder()
                    .claim("userId", userId)
                    .claim("userName", userName)
                    .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                    .signWith(SignatureAlgorithm.RS256, RsaUtils.getPrivateKey(privateKey))
                    .compact();
        }
    
        /**
         * 公钥解析token
         *
         * @param token     用户请求中的token
         * @param publicKey 公钥
         * @return
         * @throws Exception
         */
        private static Jws<Claims> parserToken(String token, PublicKey publicKey) {
            return Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
        }
    
        /**
         * 公钥解析token
         *
         * @param token     用户请求中的token
         * @param publicKey 公钥字节数组
         * @return
         * @throws Exception
         */
        private static Jws<Claims> parserToken(String token, byte[] publicKey) throws Exception {
            return Jwts.parser().setSigningKey(RsaUtils.getPublicKey(publicKey))
                    .parseClaimsJws(token);
        }
    
        /**
         * 获取token中的用户信息
         *
         * @param token     用户请求中的令牌
         * @param publicKey 公钥
         * @return 用户信息
         * @throws Exception
         */
        public static String getInfoFromToken(String token, PublicKey publicKey) throws Exception {
            Jws<Claims> claimsJws = parserToken(token, publicKey);
            Claims body = claimsJws.getBody();
            return body+"";
        }
    
        /**
         * 获取token中的用户信息
         *
         * @param token     用户请求中的令牌
         * @param publicKey 公钥
         * @return 用户信息
         * @throws Exception
         */
        public static String getInfoFromToken(String token, byte[] publicKey) throws Exception {
            Jws<Claims> claimsJws = parserToken(token, publicKey);
            Claims body = claimsJws.getBody();
            return body+"";
        }
    }
    
  4. rsa工具包(用于生成公私秘钥)

    package com.chengxi.ras;
    
    import java.io.File;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.security.*;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    
    /***
     *   此类是利用RSA算法生成公私秘钥。(底层是欧拉函数)
     *   在非对称加密中利用私有秘钥进行加密,传输到另一端之后可以使用公有秘钥解密。反之亦然
     *   举例如在: jwt中使用的密钥就可以是该算法生成的公私秘钥
     *              (因为该算法的特殊性,所以加密时使用公钥,解密时用私钥仍可以加解密无误。不用使用同一把钥匙,提高了安全性(非对称加密,消耗性能、降低效率、延缓时间))
     *
     * Created by ace on 2018/5/10.
     * @author XinQi.Yuan
     *       修改:chengxi
     * */
    public class RsaUtils {
    
        /**
         * 从文件中读取公钥
         *
         * @param filename   公钥保存路径,相对于classpath
         * @return           公钥对象
         * @throws Exception
         */
        public static PublicKey getPublicKey(String filename) throws Exception {
    
            byte[] bytes = readFile(filename);
    
            return getPublicKey(bytes);
    
        }
    
    
    
        /**
         * 从文件中读取密钥
         *
         * @param filename      私钥保存路径,相对于classpath
         * @return              私钥对象
         * @throws Exception
         */
        public static PrivateKey getPrivateKey(String filename) throws Exception {
    
            byte[] bytes = readFile(filename);
    
            return getPrivateKey(bytes);
    
        }
    
    
        /**
         * 获取公钥
         *
         * @param bytes     公钥的字节形式
         * @return          公钥对象
         * @throws Exception
         */
        public static PublicKey getPublicKey(byte[] bytes) throws Exception {
    
            X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
    
            KeyFactory factory = KeyFactory.getInstance("RSA");
    
            return factory.generatePublic(spec);
    
        }
    
    
        /**
         * 获取密钥
         *
         * @param bytes         私钥的字节形式
         * @return              私钥对象
         * @throws Exception
         */
        public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {
    
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
    
            KeyFactory factory = KeyFactory.getInstance("RSA");
    
            return factory.generatePrivate(spec);
    
        }
    
    
        /**
         * 根据密文,生存rsa公钥和私钥,并写入指定文件
         *
         * @param publicKeyFilename     公钥文件路径
         * @param privateKeyFilename    私钥文件路径
         * @param secret                生成密钥的明文
         * @throws Exception
         */
        public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {
    
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    
            SecureRandom secureRandom = new SecureRandom(secret.getBytes());
    
            keyPairGenerator.initialize(1024, secureRandom);
    
            KeyPair keyPair = keyPairGenerator.genKeyPair();
    
            // 获取公钥并写出
    
            byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
    
            writeFile(publicKeyFilename, publicKeyBytes);
    
            // 获取私钥并写出
    
            byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
    
            writeFile(privateKeyFilename, privateKeyBytes);
    
        }
    
        private static byte[] readFile(String fileName) throws Exception {
    
            return Files.readAllBytes(new File(fileName).toPath());
    
        }
    
        private static void writeFile(String destPath, byte[] bytes) throws IOException {
    
            File dest = new File(destPath);
    
            if (!dest.exists()) {
    
                dest.createNewFile();
    
            }
    
            Files.write(dest.toPath(), bytes);
    
        }
    
    
        /**
         *  测试使用
         * @param args
         */
        public static void main(String[] args) throws Exception {
            // 调用generateKey方法,输入公私钥的地址,生成秘钥的明文(原料)。即可在指定的地方生成秘钥
            RsaUtils.generateKey("D:/cjp/key.pub","D:/cjp/key.pri","程熙");
    
            // 这是输入地址获取公有秘钥的,还有一个是输入字节对象获取的。下面获取私有秘钥同理
            PublicKey publicKey = RsaUtils.getPublicKey("D:/cjp/key.pub");
            System.out.println("这是公有的秘钥:"+publicKey);
    
            PrivateKey privateKey = RsaUtils.getPrivateKey("D:/cjp/key.pri");
            System.out.println("这是私有的秘钥:"+privateKey);
    
    
            System.out.println("ok");
        }
    
    }
    
    

二. 测试运行

  1. 没有去注册也没有token,请求出错返回自定的401未授权状态码。

    如图:
    未授权状态

  2. 去访问注册路径模拟注册,返回注册成功的token

    如图:
    注册返回token

  3. 根据返回的token,携带在请求头上继续请求访问用户(注意这里的token估计少些几个,演示错误信息)

    如图(解析token出错):

    解析token出错

    返回的还是小熙自定义的未授权401,方便省事而已。

  4. 使用返回的正确token,携带在请求头上请求访问用户信息

    如图(正确返回):
    正确响应

在这里小熙优化了动态配置和讲说了jwt、rsa,下面小熙将讲说简写zuul映射路径。

三. 优化zuul映射服务路径

上面所抒写的是正常抒写,下面介绍简写的方式。

  1. 修改yml文件

    zuul:
      prefix: /api   # 访问网关路径的前缀(在映射路径的前面,一般用于区别开发的版本)
      routes:
        user-service: /user-service-zuul/**    #前面的是eureka服务注册列表中的名称,后面的是映射路径的名称
    
  2. 运行测试

    如图:
    简化映射路径

嗯,至此小熙关于案例的优化就说完了,由于这是入门案例还不健全,诚然代码还有诸多粗糙的地方但重在使用。

下一篇小熙将会讲述SpringCloudConfig配置文件的讲解和实例。

猜你喜欢

转载自blog.csdn.net/weixin_41133233/article/details/85177994
今日推荐