网关登录授权实战

本文档详细介绍了使用图形验证码进行用户登录验证的过程,包括生成验证码并存储到Redis,设置Cookie,携带验证码登录,验证码校验,验证用户名和密码,以及生成JWT令牌并保存到Redis和MySQL。此外,还阐述了携带令牌路由转发访问资源的步骤,涉及Nginx配置、服务存在性检查、令牌验证和授权,以及权限校验和请求转发。
摘要由CSDN通过智能技术生成

1 用户名和密码登录获取token

1.1 获取验证码

(1)生成图形验证码,将图形验证码保存到redis(key,value)

public Result<AcCaptchaOutDTO> generateCaptcha() throws ServiceException {
    try {
        AcCaptchaDTO acCaptchaDTO = this.acCaptchaProcessor.generate();
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ImageIO.write(acCaptchaDTO.getImage(), "png", bout);
        bout.close();
        String uuid = AcToolUtils.getUUID();
        this.acCacheProvider.getAcCacheDao().set("VerifyCaptcha:" + uuid, acCaptchaDTO.getCode(), this.acCaptchaProperties.getValidSecond());
        AcCaptchaOutDTO acCaptchaOutDTO = new AcCaptchaOutDTO();
        acCaptchaOutDTO.setCaptchaKey(uuid);
        acCaptchaOutDTO.setImgByteArray(bout.toByteArray());
        return Result.success(acCaptchaOutDTO);
    } catch (Exception var5) {
        this.logger.error("生成图形验证码失败", var5);
        throw new ServiceException("56000081", this.getMessage("56000081"));
    }
}

(2)将验证码对应的key只保存到cookie中

public Result<AcCaptchaOutDTO> captcha(HttpServletResponse response) {
    try {
        Result<AcCaptchaOutDTO> captchaResult = acCaptchaService.generateCaptcha();
        Cookie cookie = new Cookie("VerificationCode", captchaResult.getData().getCaptchaKey());
        cookie.setMaxAge(1800);
        cookie.setSecure(cwosPlatformProperties.getHttpsEnable());
        cookie.setPath("/" + rootPath + "/portal/account");
        response.addCookie(cookie);
        return Result.success(captchaResult.getData());
    } catch (ServiceException e) {
        return Result.fail(e.getCode(), e.getMessage());
    } catch (Exception e) {
        logger.error("获取验证码失败,原因:", e);
        return Result.fail(AuthRespCodeEnum.RESPONSE_GET_CAPTCHA_FAIL.getCode(),
                AuthRespCodeEnum.RESPONSE_GET_CAPTCHA_FAIL.getMessage());
    }
}

1.2 携带验证码登录

(1)验证码的key赋值给request的参数

private boolean setCaptchaKey(LoginParam param, HttpServletRequest request) {
    Cookie[] cookies = request.getCookies();
    String srcUuid = null;
    if (cookies != null) {
        for (Cookie cookie : cookies) {
            if (CAPTCHA_COOKIE_NAME.equals(cookie.getName())) {
                srcUuid = cookie.getValue();
                break;
            }
        }
    }
    if (StringUtils.isBlank(srcUuid)) {
        return true;
    }
    param.setCaptchaKey(srcUuid);
    return false;
}

(2) 验证码校验

if (!StringUtils.isBlank(captchaKey) && !StringUtils.isBlank(verifyCode)) {
    String captcha = this.acCacheProvider.getAcCacheDao().get("VerifyCaptcha:" + captchaKey);
    this.acCacheProvider.getAcCacheDao().remove("VerifyCaptcha:" + captchaKey);
    if (!StringUtils.isBlank(captcha) && captcha.equalsIgnoreCase(verifyCode)) {
        UserResultDTO userResult = null;
        boolean needLdap = this.needLdapLogin(loginParam);
        if (needLdap) {
            userResult = this.ldapLogin(loginParam);
            if (null == userResult) {
                this.logger.error("ldap登录返回信息为空");
                throw new ServiceException("56000036", this.getMessage("56000036"));
            }
        }
}

(3)验证用户名和密码

AcAccount acAccount = this.acAccountMapper.login(loginParam.getLoginName(), businessId);
if (acAccount == null) {
    if (!needLdap) {
        throw new ServiceException("56000043", this.getMessage("56000043"));
    }
    acAccount = new AcAccount();
    acAccount.setStatus(AcStatusEnum.STATUS_VALID.getCode());
    acAccount.setId(AcToolUtils.getUUID());
    acAccount.setBusinessId("");
    acAccount.setLoginName(userResult.getAccountName());
    acAccount.setEmail(userResult.getEmail());
    acAccount.setPassword(loginParam.getPassword());
    acAccount = AcEncryptUtils.encryptPassword(acAccount);
    this.acAccountMapper.insertSelective(acAccount);
}
if (!this.ldapProperties.isLogin()) {
    String pwdEncrypt = AcEncryptUtils.encryptPassword(loginParam.getPassword(), acAccount.getSalt());
    if (!acAccount.getPassword().equals(pwdEncrypt)) {
        throw new ServiceException("56000045", this.getMessage("56000045"));
    }
}
acAccount.setUserResult(userResult);
return acAccount;

(4)生成token 并保存到redis和mysql

public String generate(String key, String claims, long accessTokenExp, long refreshTokenExp) {
    TokenInfoDO tokenInfoDO = new TokenInfoDO();
    JSONObject jsonObject = JSON.parseObject(claims);
    if (jsonObject.containsKey("deviceId")) {
        tokenInfoDO = (TokenInfoDO)JSON.parseObject(claims, TokenInfoDO.class);
        JSONObject claimsObject = new JSONObject();
        claimsObject.put("deviceId", jsonObject.get("deviceId"));
        claimsObject.put("apiKey", jsonObject.get("apiKey"));
        claimsObject.put("generateTime", System.currentTimeMillis());
        claims = claimsObject.toJSONString();
    }
    String accessToken = this.createToken(claims, 1);
    String refreshToken = this.createToken(claims, 2);
    tokenInfoDO.setAccessToken(accessToken);
    tokenInfoDO.setRefreshToken(refreshToken);
    tokenInfoDO.setAccessTokenExp(accessTokenExp);
    tokenInfoDO.setRefreshTokenExp(refreshTokenExp);
    tokenInfoDO.setRefreshed(false);
    String result = JSON.toJSONString(tokenInfoDO);
    String tokenKey = String.format("JWTToken:%s", key);
    String refreshTokenKey = String.format("JWTRefreshToken:%s", key);
    return this.tokenDao.set(tokenKey, result, refreshTokenExp) && this.tokenDao.set(refreshTokenKey, result, refreshTokenExp) ? result : null;
}

在这里插入图片描述

2 携带token路由转发访问资源

2.1 nginx配置

将访问服务的路径路由到网关

2.2 检测服务是否存在

网关通过服务名称去查找服务,因此,先要判断服务是否存在。

/**
* 获取服务配置
* @param requestUrl
* @param requestContext
* @return
*/
protected ServiceConfig getServiceConfig(String requestUrl, RequestContext requestContext) {
    // 请求地址为空
    if (StringUtils.isEmpty(requestUrl)) {
        return null;
    }
    if (requestUrl.startsWith("/zuul/")) {
        requestUrl = requestUrl.replace("/zuul/", "");
    }
    int start = 0;
    if (requestUrl.startsWith(ZuulFilterConstant.URL_SLASH)) {
        start = 1;
    }
    int sIndex = requestUrl.indexOf(ZuulFilterConstant.URL_SLASH, start);
    if (sIndex == -1) {
        sIndex = requestUrl.length();
    }
    // 服务别名
    String alias = requestUrl.substring(start, sIndex);
    // 获取微服务配置
    ServiceConfig serviceConfig = serviceConfigServiceImpl.getCacheByAlias(alias);
    if (serviceConfig != null) {
        // 设置请求上下文,和请求路径
        String path = requestUrl.substring(sIndex);
        serviceConfig.setPath(path);
    }
    return serviceConfig;
}

2.3 认证token及授权

(1) 解析token
在保存token的时候,封装了用户ID等信息,现在要把用户ID取出来了。

private AcClaimsDTO getIdByAccessToken(String accessToken) throws ServiceException {
    String claimsByToken = "";
    try {
        claimsByToken = (String)this.tokenProvider.getClaimsByToken(accessToken);
        AcClaimsDTO json = (AcClaimsDTO)JSON.parseObject(claimsByToken, AcClaimsDTO.class);
        String tokenKey = String.format("login:%s:%s", json.getSource(), json.getId());
        if (!StringUtils.isEmpty(claimsByToken) && !StringUtils.isBlank(this.tokenProvider.verify(accessToken, tokenKey, 1))) {
            return json;
        } else {
            throw new ServiceException("56000078", this.getMessage("56000078"));
        }
    } catch (ExpiredJwtException var5) {
        this.logger.info("token取对象异常,过期", var5);
        throw new ServiceException("56000077", this.getMessage("56000077"));
    } catch (ServiceException var6) {
        throw var6;
    } catch (Exception var7) {
        this.logger.info("token取对象异常,无效", var7);
        throw new ServiceException("56000066", this.getMessage("56000066"));
    }
}

(2)根据用户ID获取查询用户信息

Result<List<UserQueryResult>> Result = userManagerService.query(userQueryParam, context);
if (Result == null || CollectionUtils.isEmpty(Result.getData())) {
    return Result.fail(AuthRespCodeEnum.RESPONSE_USER_GET_FAIL.getCode(),
            AuthRespCodeEnum.RESPONSE_USER_GET_FAIL.getMessage());
}
UserQueryResult user = Result.getData().get(0);

(3)获取存储的token的key值(存到redis和数据库中了)

String loginTokenKey = String.format(RedisConstant.LOGIN_TOKEN_KEY, LoginSourceEnum.WEB.getCode(), user.getId());
String value = cacheService.get(loginTokenKey);
if (value == null) {
    value = cacheService.getDB(loginTokenKey);
}
JwtTokenCacheResult jwtTokenCacheResult = JSON.parseObject(value, JwtTokenCacheResult.class);
if (jwtTokenCacheResult == null) {
    loginTokenKey = String.format(RedisConstant.LOGIN_TOKEN_KEY, LoginSourceEnum.APP.getCode(), user.getId());
    value = cacheService.get(loginTokenKey);
    if (value == null) {
        value = cacheService.getDB(loginTokenKey);
    }
    jwtTokenCacheResult = JSON.parseObject(value, JwtTokenCacheResult.class);
    if (jwtTokenCacheResult == null) {
        return Result.fail(AuthRespCodeEnum.RESPONSE_50001224.getCode(), AuthRespCodeEnum.RESPONSE_50001224.getMessage());
    }
}

(4)判断token是否过期

if (cacheService.getExpireTime(loginTokenKey) < EXPIRE_LEFT_TIME) {
    cacheService.extendExpireTime(loginTokenKey, EXTEND_EXPIR_TIME);
}

(5)验证当前用户是否有访问当前API的权限
先根据用户userId查询用户角色,再通过角色查询用户拥有的资源,判断用户是否拥有该接口的使用权限

private List<ApiResult> getApiList(UserApiQueryParam param, CallContext context) throws ServiceException {
    List<ApiResult> apiList = null;
    UserRoleQueryParam userRoleQueryParam = new UserRoleQueryParam();
    userRoleQueryParam.setUserId(param.getUserId());
    List<RoleInfoResult> roleList = this.roleService.queryByUserId(userRoleQueryParam, context);
    List<String> roleIds = Collections3.extractToList(roleList, "id");
    List<ResourceTreeResult> resourceList = null;
    if (!CollectionUtils.isEmpty(roleIds)) {
        RoleResQueryParam roleResQueryParam = new RoleResQueryParam();
        roleResQueryParam.setRoleIds(roleIds);
        resourceList = this.resourceService.queryByRoleIds(roleResQueryParam, context);
        List<ResourceTreeResult> resourceList = CollectionUtils.isEmpty(resourceList) ? new ArrayList() : resourceList;
        List<String> resourceIds = Collections3.extractToList((Collection)resourceList, "id");
        param.setUserId(param.getUserId());
        param.setRoleIds(roleIds);
        param.setResourceIds(resourceIds);
        apiList = this.apiService.queryByRoleIdsAndResourceIds(param, context);
    }
    if (null == apiList) {
        apiList = new ArrayList();
    }
    if (this.rbacApplicationEnabled) {
        List<ApiResult> list = this.apiService.queryByUserIdWithApplication(param);
        if (!CollectionUtils.isEmpty(list)) {
            ((List)apiList).addAll(list);
        }
    }
    return Lists.newArrayList(Sets.newHashSet((Iterable)apiList));
}
@ParamsValidate
public Result<Boolean> auth(UserApiAuthParam param, CallContext context) throws ServiceException {
    ApiQueryDTO apiQueryDTO = new ApiQueryDTO();
    apiQueryDTO.setUri(param.getUrl());
    apiQueryDTO.setExt1(ApiAuthTypeEnum.COMMON.getCode());
    List<Api> commonApiList = this.apiMapper.query(apiQueryDTO);
    if (null != commonApiList && !commonApiList.isEmpty()) {
        return Result.success(true);
    } else {
        UserApiQueryParam userApiQueryParam = new UserApiQueryParam();
        userApiQueryParam.setUserId(param.getUserId());
        List<ApiResult> apiList = this.getApiList(userApiQueryParam, context);
        Boolean authFlag = false;
        Iterator var8 = apiList.iterator();


        while(var8.hasNext()) {
            ApiResult item = (ApiResult)var8.next();
            if (this.matchUrl(item.getUri(), param.getUrl())) {
                authFlag = true;
                break;
            }
        }
        return Result.success(authFlag);
    }
}

在这里插入图片描述

2.4 请求转发

private void preContext(String serviceId, String path, RequestContext requestContext, JSONObject param) {
    String serviceCode = null;
    String deviceId = null;
    if (null != param && null != param.get("json_content")) {
        JSONObject jsonContent = JSONObject.parseObject(param.get("json_content").toString());
        if (null != jsonContent && null != jsonContent.get("device_id") && StringUtils.isNotEmpty(jsonContent.get(
                "device_id").toString())) {
            deviceId = jsonContent.get("device_id").toString();
            CwGeDevice device = DeviceService.getCacheByDeviceCode(deviceId);
            if (device != null) {
                serviceCode = device.getServiceCode();
            }
        }
    }
    UrlForward urlForward = urlForwardServiceImpl.getCacheByUrl(path, serviceCode);
    if (urlForward != null) {
        serviceId = urlForward.getServiceId();
    }
    /*if ("/device/node/recordUploadCompare".equals(path) || "/device/node/pictureRecordUpload".equals(path)) {
        serviceId = "davinci-vip";
    }*/
    log.debug("网关转发服务:serviceID=" + serviceId + ";path=" + path + ";deviceId=" + deviceId + ";serviceCode=" + serviceCode);
    requestContext.set(FilterConstants.SERVICE_ID_KEY, serviceId);
    requestContext.set(FilterConstants.RETRYABLE_KEY, true);
    requestContext.set(FilterConstants.REQUEST_URI_KEY, path);
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值