Cloud-Platform 学习——Part3 gateway 网关与 JWT 鉴权

1.前端发送请求

前端发送请求时有一个全局拦截器,拦截所有请求并配置 Authorization 头部信息,配置token,一共给后台鉴权

// 请求拦截器
service.interceptors.request.use(
  config => {
    if (!permission.access(config, store)) {
      // eslint-disable-next-line no-throw-literal
      throw {
        type: '403',
        config: config
      }
    }
    loading.show(config)
    // 在请求发送之前做一些处理
    if (!(/^https:\/\/|http:\/\//.test(config.url))) {
      const token = util.cookies.get('token')
      if (token && token !== 'undefined') {
        // 让每个请求携带token-- ['Authorization']为自定义key 请根据实际情况自行修改
        config.headers['Authorization'] = 'Bearer ' + token
      }
    }
    return config
  },
  error => {
    // 发送失败
    console.log(error)
    Promise.reject(error)
  }
)

2.后台全局网关过滤器拦截请求

@Configuration
@Slf4j
public class AccessGatewayFilter implements GlobalFilter {

 @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain gatewayFilterChain) {
        log.info("check token and user permission....");
        //获取gateway原先请求路径
        LinkedHashSet requiredAttribute = serverWebExchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
        ServerHttpRequest request = serverWebExchange.getRequest();
        // 获取当前网关访问的URI,即转发后的请求路径
        String requestUri = request.getPath().pathWithinApplication().value(); //获取转发后的uri,如Path=/api/auth/**,http://localhost:8765/api/auth/jwt/token——>jwt/token
        if (requiredAttribute != null) {
            Iterator<URI> iterator = requiredAttribute.iterator();
            while (iterator.hasNext()) {
                URI next = iterator.next();
                if (next.getPath().startsWith(GATE_WAY_PREFIX)) {
                    //如果是 /api开头则获取 /api后面的路径
                    requestUri = next.getPath().substring(GATE_WAY_PREFIX.length());
                }
            }
        }
        final String method = request.getMethod().toString(); //POST/GET
        BaseContextHandler.setToken(null);
        ServerHttpRequest.Builder mutate = request.mutate();
        // 网关不进行拦截的URI配置,常见如验证码、Login接口
        if (isStartWith(requestUri)) {
            ServerHttpRequest build = mutate.build();
            return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build()); //过滤器放行
        }
        IJWTInfo user = null;
        try {
            // 判断用户token,获取用户信息
            user = getJWTUser(request, mutate);
        } catch (Exception e) {
            log.error("用户Token过期异常", e);
            return getVoidMono(serverWebExchange, new TokenForbiddenResponse("User Token Error or Expired!"), HttpStatus.UNAUTHORIZED);
        }
    }

 	private boolean isStartWith(String requestUri) {
        boolean flag = false;
        for (String s : startWith.split(",")) {
            if (requestUri.startsWith(s)) {
                return true;
            }
        }
        return flag;
    }

3.JWT 鉴权并解析用户信息

private IJWTInfo getJWTUser(ServerHttpRequest request, ServerHttpRequest.Builder ctx) throws Exception {
        List<String> strings = request.getHeaders().get(userAuthConfig.getTokenHeader()); //获取请求头 Authorization信息
        String authToken = null;
        if (strings != null) {
            authToken = strings.get(0);
        }
        if (StringUtils.isBlank(authToken)) {
            strings = request.getQueryParams().get("token"); //如果 Authorization信息为空,则获取携带参数 token
            if (strings != null) {
                authToken = strings.get(0);
            }
        }
        IJWTInfo infoFromToken = userAuthUtil.getInfoFromToken(authToken);
        //从redis获取当前会话
        String s = stringRedisTemplate.opsForValue().get(RedisKeyConstant.REDIS_KEY_TOKEN + ":" + infoFromToken.getTokenId());
        if (StringUtils.isBlank(s)) {
            throw new UserTokenException("User token expired!");
        }
        ctx.header(userAuthConfig.getTokenHeader(), authToken);
        BaseContextHandler.setToken(authToken);
        return infoFromToken;
    }

使用工具类解析 token

public static IJWTInfo getInfoFromToken(String token, byte[] pubKey) throws Exception {
        if(token.startsWith("Bearer")){
            token = token.replace("Bearer ","");
        }
        Jws<Claims> claimsJws = parserToken(token, pubKey);
        Claims body = claimsJws.getBody();
        return new JWTInfo(body.getSubject(), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_USER_ID)), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_NAME)),StringHelper.getObjectValue(body.get(CommonConstants.JWT_ID)));
    }

JWT 工具类

public class JWTHelper {
    private static RsaKeyHelper rsaKeyHelper = new RsaKeyHelper();
    /**
     * 密钥加密token
     *
     * @param jwtInfo
     * @param priKeyPath
     * @param expire
     * @return
     * @throws Exception
     */
    public static String generateToken(IJWTInfo jwtInfo, String priKeyPath, 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(priKeyPath))
                .compact();
        return compactJws;
    }

    /**
     * 生成密钥加密的 token
     * @param jwtInfo 包含用户名、用户id、用户名称、tokenId
     * @param priKey 密钥
     * @param expire token过期时间
     * @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())
                .claim(CommonConstants.JWT_ID, jwtInfo.getTokenId())
                .setExpiration(DateTime.now().plusSeconds(expire).toDate())
                .signWith(SignatureAlgorithm.RS256, rsaKeyHelper.getPrivateKey(priKey))
                .compact();
        return compactJws;
    }

    /**
     * 公钥解析token
     *
     * @param token
     * @return
     * @throws Exception
     */
    public static Jws<Claims> parserToken(String token, String pubKeyPath) throws Exception {
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(rsaKeyHelper.getPublicKey(pubKeyPath)).parseClaimsJws(token);
        return claimsJws;
    }
    /**
     * 公钥解析token
     *
     * @param token
     * @return
     * @throws Exception
     */
    public static Jws<Claims> parserToken(String token, byte[] pubKey) throws Exception {
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(rsaKeyHelper.getPublicKey(pubKey)).parseClaimsJws(token);
        return claimsJws;
    }
    /**
     * 获取token中的用户信息
     *
     * @param token
     * @param pubKeyPath
     * @return
     * @throws Exception
     */
    public static IJWTInfo getInfoFromToken(String token, String pubKeyPath) throws Exception {
        Jws<Claims> claimsJws = parserToken(token, pubKeyPath);
        Claims body = claimsJws.getBody();
        return new JWTInfo(body.getSubject(), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_USER_ID)), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_NAME)));
    }
    /**
     * 获取token中的用户信息
     *
     * @param token
     * @param pubKey
     * @return
     * @throws Exception
     */
    public static IJWTInfo getInfoFromToken(String token, byte[] pubKey) throws Exception {
        if(token.startsWith("Bearer")){
            token = token.replace("Bearer ","");
        }
        Jws<Claims> claimsJws = parserToken(token, pubKey);
        Claims body = claimsJws.getBody();
        return new JWTInfo(body.getSubject(), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_USER_ID)), StringHelper.getObjectValue(body.get(CommonConstants.JWT_KEY_NAME)),StringHelper.getObjectValue(body.get(CommonConstants.JWT_ID)));
    }
}

4.检查权限

   Mono<CheckPermissionInfo> checkPermissionInfoMono = webClientBuilder.build().
                get().uri("http://ace-admin/api/user/{username}/check_permission?requestMethod=" + method + "&requestUri=" + requestUri, user.getUniqueName()).header(userAuthConfig.getTokenHeader(), BaseContextHandler.getToken()).retrieve().bodyToMono(CheckPermissionInfo.class);

        IJWTInfo finalUser = user;
        return checkPermissionInfoMono.flatMap(checkPermissionInfo -> {
            // 当前用户具有访问权限
            if (checkPermissionInfo.getIsAuth()) {
                if (checkPermissionInfo.getPermissionInfo() != null) {
                    // 若资源存在则请求设置访问日志
                    setCurrentUserInfoAndLog(serverWebExchange, finalUser, checkPermissionInfo.getPermissionInfo());
                }
                ServerHttpRequest build = mutate.build();
                return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());
            } else {
                // 当前用户不具有访问权限
                return getVoidMono(serverWebExchange, new TokenForbiddenResponse("Forbidden!Does not has Permission!"), HttpStatus.FORBIDDEN);
            }
        });

获取所有权限控制

public List<PermissionInfo> getAllPermission() {
        String key = RedisKeyConstant.REDIS_KEY_ALL_PERMISISON;
        String s = stringRedisTemplate.opsForValue().get(key);
        if (s == null || org.apache.commons.lang.StringUtils.isBlank(s)) {
            List<Menu> menus = menuBiz.selectListAll();//获取菜单权限
            List<PermissionInfo> result = new ArrayList<PermissionInfo>();
            menu2permission(menus, result);
            List<Element> elements = elementBiz.getAllElementPermissions();//获取操作权限
            element2permission(result, elements);//封装在 PermissionInfo中
            s = JSON.toJSONString(result);
            stringRedisTemplate.opsForValue().set(key, s, 12, TimeUnit.HOURS); //放到 redis中
        }
        List<PermissionInfo> permissionInfos = JSON.parseArray(s, PermissionInfo.class);
        return permissionInfos;
    }

判断当前请求是否需要做权限控制

// 判断当前请求是否需要做权限控制
List<PermissionInfo> matchPermission = allPermission.parallelStream().filter(permissionInfo -> {
    String uri = permissionInfo.getUri();
    if (uri.indexOf("{") > 0) {
        uri = uri.replaceAll("\\{\\*\\}", "[a-zA-Z\\\\d]+");
    }
    String regEx = "^" + uri + "$";
    return (Pattern.compile(regEx).matcher(requestUri).find())
            && requestMethod.equals(permissionInfo.getMethod());
}).collect(Collectors.toList());
// 说明当前请求不做权限控制
if (matchPermission.size() == 0) {
    checkPermissionInfo.setIsAuth(true);
    return Mono.just(checkPermissionInfo);
}

获取当前用户拥有的权限

public List<PermissionInfo> getPermissionByUsername(String username) {
    String key = String.format(RedisKeyConstant.REDIS_KEY_USER_PERMISISON, username);
    String s = stringRedisTemplate.opsForValue().get(key);
    if (s == null || org.apache.commons.lang.StringUtils.isBlank(s)) {
        User user = userBiz.getUserByUsername(username);
        List<Menu> menus = menuBiz.getUserAuthorityMenuByUserId(user.getId());
        List<PermissionInfo> result = new ArrayList<PermissionInfo>();
        menu2permission(menus, result);
        List<Element> elements = elementBiz.getAuthorityElementByUserId(user.getId() + "");
        element2permission(result, elements);
        stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(result), 12, TimeUnit.HOURS);

        return result;
    }
    List<PermissionInfo> permissionInfos = JSON.parseArray(s, PermissionInfo.class);
    return permissionInfos;
}

判断用户拥有的权限里面有没有当前请求需要的权限

// 判断当前用户是否拥有该访问资源的权限
List<PermissionInfo> permissions = this.getPermissionByUsername(username);
PermissionInfo current = null;
for (PermissionInfo info : permissions) {
    //判断用户拥有的权限里面有没有当前请求需要的权限
    boolean anyMatch = matchPermission.parallelStream().anyMatch(permissionInfo -> permissionInfo.getCode().equals(info.getCode()));
    if (anyMatch) {
        current = info;
        break;
    }
}
if (current == null) {
    // 当前用户不拥有该权限
    checkPermissionInfo.setIsAuth(false);
} else {
    // 当前用户拥有该资源的访问权限
    checkPermissionInfo.setIsAuth(true);
    checkPermissionInfo.setPermissionInfo(current);
}
return Mono.just(checkPermissionInfo);

根据以上的判断返回用户信息

return checkPermissionInfoMono.flatMap(checkPermissionInfo -> {
            // 当前用户具有访问权限
            if (checkPermissionInfo.getIsAuth()) {
                if (checkPermissionInfo.getPermissionInfo() != null) {
                    // 若资源存在则请求设置访问日志
                    setCurrentUserInfoAndLog(serverWebExchange, finalUser, checkPermissionInfo.getPermissionInfo());
                }
                ServerHttpRequest build = mutate.build();
                return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());
            } else {
                // 当前用户不具有访问权限
                return getVoidMono(serverWebExchange, new TokenForbiddenResponse("Forbidden!Does not has Permission!"), HttpStatus.FORBIDDEN);
            }
        });

5.大致流程图

在这里插入图片描述

posted @ 2023-12-11 22:57  Acegzx  阅读(80)  评论(0)    收藏  举报  来源