您的当前位置:首页正文

【JAVA】生成accessToken例子

来源:筏尚旅游网

一、前言

JAVA系统或者web系统需要对外提供访问时,都需要首先需要经过鉴权。

比如去调用接口之前我们需要去获取令牌 

@PostMapping("/oauth2/accessToken")
 JSONObject getToken(@RequestParam Map<String, Object> params);

@PostMapping("/oauth2/accessToken")
	JSONObject getToken(@RequestParam Map<String, Object> params);

调用接口时,业务数据需要和token有一起传输

	public Map<String, Object> getSendParam(Map<String, Object> inputParam){
		
		Map<String, Object> params = new LinkedHashMap<>();
		String token = getACToken();
		String timestamp = String.valueOf(System.currentTimeMillis() / 1000);
		String input = JSONObject.toJSONString(inputParam);
		log.info("本次接口发送参数:"+input);
		String inputDES = Des3Utils.get3DESEncryptECB(input, desKey);
		byte[] decoded = Base64.getDecoder().decode(inputDES);
		String inputHex = Des3Utils.byteToHexString(decoded);
		String subString = subSign(input, timestamp, token);
		String sign = MD5Util.getMd5(subString).toUpperCase();
		params.put("token", token);
		params.put("sign", sign);
		params.put("timestamp", timestamp);
		params.put("clientId", clientId);
		params.put("jsonData", inputHex);
		return params;
		
	}

那么这里的token服务端是怎么生成的呢下面介绍例子

1、生成token需要的参数

/**
	 * 组装获取Access Token参数
	 */
	public Map<String, Object> getACTokenParam() {
		Map<String, Object> map = new HashMap<>();
		String timestamp = DateTimeUtils.getDateStr();
		// client_secret+timestamp+client_id+username+password+grant_type+scope+client_secret
		// username使用原文;
		// client_secret(双方约定)、password需要md5加密后的转大写;
		// 将上述拼接的字符串使用MD5加密,加密后的值再转为大写
		String clientSecM = MD5Util.getMd5(clientSecret).toUpperCase();
		String passwordM = MD5Util.getMd5(password).toUpperCase();
		String subString = (clientSecM + timestamp + clientId + userName + passwordM + grantType + scope + clientSecM);
		String sign = MD5Util.getMd5(subString).toUpperCase();
		map.put("grant_type", grantType);
		map.put("client_id", clientId);
		map.put("timestamp", timestamp);
		map.put("username", userName);
		map.put("password", passwordM);
		map.put("scope", scope);
		map.put("sign", sign);
		return map;
	}

2、服务端生成token过程

    public static AccessTokenModel oauth2AccessToken(RequestOAuth2 request) {
        try {

            // 取得参数
            String grant_type = request.getGrantType();
            String client_id = request.getClientId();
            String username = request.getUsername();
            String password = request.getPassword();
            String timestamp = request.getTimestamp();
            String scope = request.getScope();
            String sign = request.getSign();
            logger.info("oauth2AccessToken_params:" + username + "," + password);

            // 参数检查
            if (StringUtils.isBlank(grant_type) || StringUtils.isBlank(client_id) ||
                    StringUtils.isBlank(username) || StringUtils.isBlank(password) ||
                    StringUtils.isBlank(timestamp) || StringUtils.isBlank(sign)) {
                CommonExceptionType.CommonException.throwEx("参数错误: 无法访问");
                return null;
            }
            if (!grant_type.equals("access_token")) {
                CommonExceptionType.CommonException.throwEx("参数错误: 不支持的 grant_type " + grant_type);
                return null;
            }
            if (scope == null) {
                scope = "";
            }
            // 时间戳检查
            try {
                // 格式及过期时间检查
                Date dateTimestamp = new Date((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).parse(timestamp));
                Long timeDifficulty = Duration.between(Instant.now(), Instant.ofEpochMilli(dateTimestamp.getTime())).toHours();
                if (timeDifficulty > 2 || timeDifficulty < -2) {
                    CommonExceptionType.CommonException.throwEx("参数错误: timestamp 已过期");
                    return null;
                }
            } catch (Exception ex) {
                CommonExceptionType.CommonException.throwEx("参数错误: timestamp 格式错误或过期");
                return null;
            }
            // client_id检查
            String storeOuCode = null;
            String deviceCode = null;

            ApiAuthUser user = new ApiAuthUser();
            user.setAuthType(ApiAuthType.OAuth.type);
            String[] clientIds = StringUtils.split(client_id, "_");
            client_id = clientIds[0];
            if (clientIds.length > 2) {
                storeOuCode = clientIds[1];
                deviceCode = clientIds[2];
            }
            user.setClientId(client_id);
            user.load();
            if (user.getIsLoad()) {
                // 启用状态检查
                if ((user.getState() != null) && user.getState().equals("1")) {
                    // User检查
                    String orgPassword = Coder.get3DESDecrypt(user.getPassword());
                    if (username.toLowerCase().equals(user.getUsername().toLowerCase()) && password.equals(Utility.MD5(orgPassword).toUpperCase())) {
                        // 签名校验
                        String clientSecret = Utility.MD5(Coder.get3DESDecrypt(user.getClientSecret())).toUpperCase();
                        String calcSign = clientSecret + timestamp + client_id + username + password + grant_type + scope + clientSecret;
                        calcSign = Utility.MD5(calcSign).toUpperCase();
                        if (!sign.equals(calcSign)) {
                            CommonExceptionType.CommonException.throwEx("访问错误: 签名错误");
                            return null;
                        }
                    } else {
                        CommonExceptionType.CommonException.throwEx("访问错误: 用户名或密码错误");
                        return null;
                    }
                } else {
                    CommonExceptionType.CommonException.throwEx("访问错误: 无法访问");
                    return null;
                }
            } else {
                CommonExceptionType.CommonException.throwEx("访问错误: 无法访问");
                return null;
            }
            JedisClientPool jedisClientPool = JedisClientPool.getInstance();
            // 验证通过,生成accessToken
            AccessTokenModel tokenModel = null;
            String userKey = String.format("%s%s%s", user.getId().toString(),
                    StringUtils.isNotEmpty(storeOuCode) ? "$" + storeOuCode : StringUtils.EMPTY,
                    StringUtils.isNotEmpty(deviceCode) ? "$" + deviceCode : StringUtils.EMPTY);

            String userStr = jedisClientPool.get("API_INTERFACE_AUTH:OAUTH:USER:" + userKey);
            if (StringUtils.isNotBlank(userStr)) {
                tokenModel = JSONUtility.jsonToObject(userStr, AccessTokenModel.class);
            }
            if (tokenModel == null) {
                tokenModel = new AccessTokenModel();
            }
            tokenModel.setUid(user.getId().toString());
            // 删除以前的AccessToken
            if (StringUtils.isNotBlank(tokenModel.getAccess_token())) {
                jedisClientPool.del("API_INTERFACE_AUTH:OAUTH:ACCESSTOKEN:" + tokenModel.getAccess_token());
            }
            // 删除以前的RefreshToken
            if (StringUtils.isNotBlank(tokenModel.getRefresh_token())) {
                jedisClientPool.del("API_INTERFACE_AUTH:OAUTH:REFRESHTOKEN:" + tokenModel.getRefresh_token());
            }
            // 创建AccessToken
            tokenModel.setAccess_token(UUID.randomUUID().toString().replace("-", "").toUpperCase());
            tokenModel.setExpires_in(3600 * 24);                                                        // 24小时后过期
            tokenModel.setRefresh_token(UUID.randomUUID().toString().replace("-", "").toUpperCase());
            tokenModel.setRefresh_token_expires((new Date()).addMonth(1).getTime());                    // 一个月后过期
            tokenModel.setTime((new Date()).getTime());
            // 保存至缓存
            int timeout = (int) ((tokenModel.getRefresh_token_expires() - tokenModel.getTime()) / 1000);
            jedisClientPool.setex("API_INTERFACE_AUTH:OAUTH:USER:" + userKey, timeout, JSONUtility.objectToJson(tokenModel));
            jedisClientPool.setex("API_INTERFACE_AUTH:OAUTH:ACCESSTOKEN:" + tokenModel.getAccess_token(), tokenModel.getExpires_in(), tokenModel.getUid());
            jedisClientPool.setex("API_INTERFACE_AUTH:OAUTH:REFRESHTOKEN:" + tokenModel.getRefresh_token(), timeout, tokenModel.getUid());
            jedisClientPool.setex("API_INTERFACE_AUTH:OAUTH:USER:TOKEN:" + tokenModel.getAccess_token(), timeout, JSONUtility.objectToNotNullJson(user));
            tokenModel.setResult(true);
            tokenModel.setMsg("success");
            return tokenModel;
        } catch (Exception ex) {
            AccessTokenModel tokenModel = new AccessTokenModel();
            tokenModel.setResult(false);
            tokenModel.setMsg(StringUtils.isBlank(ex.getMessage()) ? "系统异常" : ex.getMessage());
            return tokenModel;
        }
    }

3、服务端验证token

public static <T> RequestContext<T> parseCheckSing(HttpServletRequest request, String bodyStry, Class<T> type) {
        if (StringUtils.isBlank(bodyStry)) {
            int length = request.getContentLength();
            byte[] body = new byte[length];
            try {
                DataInputStream in = new DataInputStream(request.getInputStream());
                in.readFully(body);
                in.close();
                bodyStry = new String(body);
            } catch (Exception exc) {
                log.error("读取请求数据失败", exc);
                CtgException.ERR_81092.throwEx();
            }
        }
        String urlUid = request.getAttribute("urlUid") != null ? request.getAttribute("urlUid").toString() : "";
        Preconditions.check(StringUtils.isNotBlank(bodyStry), CtgException.ERR_81093);

        String originalBody = bodyStry;
        log.info("urlUid: {} CTG接口入参:{}", urlUid, bodyStry);
        CtgPublicReq publicReq = JSONUtility.jsonToObject(originalBody, CtgPublicReq.class);
        if (publicReq == null) {
            CtgException.ERR_81092.throwEx();
        }

        Preconditions.check(StringUtils.isNotBlank(publicReq.getSign()), CtgException.ERR_81094);
        Preconditions.check(StringUtils.isNotBlank(publicReq.getClientId()), CtgException.ERR_81097);
        Preconditions.check(StringUtils.isNotBlank(publicReq.getToken()), CtgException.ERR_81096);
        Preconditions.check(publicReq.getTimestamp() != null, CtgException.ERR_81095);
        Preconditions.check(StringUtils.isNotBlank(publicReq.getJsonData()), CtgException.ERR_81092);
        Map<String, String> paramsMap = new HashMap<>();
        paramsMap.put("sign", publicReq.getSign());
        paramsMap.put("clientId", publicReq.getClientId());
        paramsMap.put("token", publicReq.getToken());
        paramsMap.put("timestamp", publicReq.getTimestamp() + "");

        //校验token
        String uid = JedisClientPool.getInstance().get("API_INTERFACE_AUTH:OAUTH:ACCESSTOKEN:" + publicReq.getToken());
        if (StringUtils.isBlank(uid)) {
            CtgException.ERR_81090.throwEx();
        }
        Preconditions.check(StringUtils.isNotBlank(uid), CtgException.ERR_81090);

        //校验时间戳
        long nowTime = System.currentTimeMillis();
        log.info("当前时间戳:{}", nowTime);
        long timeDifficulty = Math.abs((nowTime / 1000) - publicReq.getTimestamp());
        Preconditions.check(timeDifficulty < 600, CtgException.ERR_81089);

        //校验sign
        ApiAuthUser apiAuthUser = new ApiAuthUser();
        apiAuthUser.setId(Integer.valueOf(uid));
        apiAuthUser.load();
        if (!apiAuthUser.getIsLoad()) {
            CtgException.ERR_81099.throwEx();
        }
        if (!apiAuthUser.getClientId().equals(publicReq.getClientId())) {
            CommonExceptionType.SimpleException.throwEx("请求客户端ID异常");
        }
        String clientSecret = apiAuthUser.getClientSecret();
        clientSecret = Coder.get3DESDecrypt(clientSecret);
        String cipherKey = apiAuthUser.getCipherKey();
        String decryptJson = "";
        //解密字符串  Cipher.getInstance("DESede/ECB/PKCS5Padding"); 入参hex 需要转base64
        //获取到json1
        byte[] hexBytes = Hex.decode(publicReq.getJsonData().getBytes(StandardCharsets.UTF_8));
        String baseReqStr = Coder.getBase64Encode(hexBytes);
        decryptJson = Coder.get3DESDecryptECB(baseReqStr, cipherKey);
        Preconditions.check(StringUtils.isNotBlank(decryptJson), CtgException.ERR_81091);
        log.info("解密后参数dataJson---:{}", decryptJson);
        paramsMap.put("jsonData", decryptJson);

        String keySign = YiYeSignUtil.generateSignOpen(paramsMap, publicReq.getClientId(), clientSecret);
        log.info("ctg系统获取到签名 {}", keySign);
        Preconditions.check(StringUtils.isNotBlank(keySign) && keySign.equals(publicReq.getSign()), CtgException.ERR_81088);

        T model = JSONUtility.jsonToObject(decryptJson, type);
        request.setAttribute("appCipherKey", cipherKey);

        return new RequestContext<>(publicReq.getSign(), publicReq.getToken(), model, publicReq.getJsonData(), publicReq.getClientId(), publicReq.getTimestamp());
    }

为什么要用 Token?

而要回答这个问题很简单——因为它能解决问题!

可以解决哪些问题呢?

1. Token 完全由应用管理,所以它可以避开

2. Token 可以避免CSRF 攻击

3. Token 可以是无状态的,可以在多个服务间共享

Token 是在服务端产生的。如果前端使用用户名/密码向服务端请求认证,服务端认证成功,那么在服务端会返回 Token 给前端。前端可以在每次请求的时候带上 Token 证明自己的合法地位。如果这个 Token 在服务端(比如存入数据库),那它就是一个永久的。

于是,又一个问题产生了:需要为 Token 设置有效期吗?

需要设置有效期吗?

对于这个问题,我们不妨先看两个例子。一个例子是登录密码,一般要求定期改变密码,以防止泄漏,所以密码是有有效期的;另一个例子是安全证书。SSL 都有有效期,目的是为了解决吊销的问题,对于这个问题的详细情况,来看看知乎的回答。所以无论是从安全的角度考虑,还是从吊销的角度考虑,Token 都需要设有效期。

那么有效期多长合适呢?

只能说,根据系统的安全需要,尽可能的短,但也不能短得离谱——想像一下手机的自动熄屏时间,如果设置为 10 秒钟无操作自动熄屏,再次点亮需要输入密码,会不会疯?如果你觉得不会,那就亲自试一试,设置成可以设置的最短时间,坚持一周就好(不排除有人适应这个时间,毕竟手机厂商也是有的)。

然后新问题产生了,如果用户在正常操作的过程中,Token 过期失效了,要求用户重新登录……用户体验岂不是很糟糕?

为了解决在操作过程不能让用户感到 Token 失效这个问题,有一种方案是在保存 Token 状态,用户每次操作都会自动刷新(推迟) Token 的过期时间——Session 就是采用这种策略来保持用户登录状态的。然而仍然存在这样一个问题,在前后端分离、单页 App 这些情况下,每秒种可能发起很多次请求,每次都去刷新过期时间会产生非常大的代价。如果 Token 的过期时间被持久化到数据库或文件,代价就更大了。所以通常为了提升效率,减少消耗,会把 Token 的过期时保存在缓存或者内存中。

还有另一种方案,使用 Refresh Token,它可以避免频繁的读写操作。这种方案中,服务端不需要刷新 Token 的过期时间,一旦 Token 过期,就反馈给前端,前端使用 Refresh Token 申请一个全新 Token 继续使用。这种方案中,服务端只需要在客户端请求更新 Token 的时候对 Refresh Token 的有效性进行一次检查,大大减少了更新有效期的操作,也就避免了频繁读写。当然 Refresh Token 也是有有效期的,但是这个有效期就可以长一点了,比如,以天为单位的时间。

 

因篇幅问题不能全部显示,请点此查看更多更全内容