公司一旧项目权鉴改造
任意一端登录(web、app、h5)后可以携带对应的token 来请求访问后台服务资源。
互联网服务离不开用户认证。一般流程是下面这样:
cookie存储的内容有限制4k
cookie的有效范围是当前域名下,所以在分布式环境下或者前后端分离的项目中都不适用,即使要 用也会很麻烦。
app端内嵌网页互相跳转。如有原生跳到h5,或者 内嵌网页跳到原生等,和服务端交互用非cookie方式,不兼容。(浏览器提供了一种叫 cookie 的机制)。
Session数据共享问题。单机没有问题,如果网站请求流量较大,那么单台 tomcat 设备是无法
承接这些流量的,这个时候就需要开始对服务器做集群。
session sticky (仅供了解)
session replication(仅供了解)
session 统一存储
Cookie Based 方法,简单来说,就是不依赖容器本身的 Session 机制。而是服务端基于一定的算法,生成一个 token 给到客户端, 客户端每次请求,都会携带这个 token。 当服务端收到 token 以后,先验证 token 是否有效,再解 密这个 token 获取关键数据进行处理(处理时可以存储解密出来的信息)。
基于纯 Cookie 的方式,也就是客户端每 次请求都携带身 份信息给到服务端。比较典型的方式是 JWT,全称是 JSON Web Tokens。(JWT 强调的是服务端不对 token 进行存储,而是直接通过签名算法验证并解密 token 得
到相应数据进行处理)https://jwt.io/
1、传统授权流程
2、公钥私钥授权流程
3、根据业务场景验证完token后可以继续解密出来验证用户信息。
之前的代码(验证token):
/** * 用户令牌信息(验证Token) **
@author
* @date 2019-06-26
*/
@GetMapping("/user")
public Object list(Principal user, String sso_cookie)
{
if (verifyCookie) {
String ssoCookie = SpringUtil.getSsoCookie(user);
if (StringUtil.isBlank(sso_cookie) || !sso_cookie.equals(ssoCookie))
{
throw new BusinessException(ResultCode.TOKEN_INVALID);
}
}return user; }
以改为兼容客户端的代码:
1、登录时生成 jwt token 并保存到 redis(可以将唯一值 如org+userId 作为key 保存)
private LoginVo packetTokenVo(LoginVo tokenVo, LoginDto dto) {
if (StringUtils.isNotBlank(dto.getClient())) {
String orgId = dto.getOrgId() == null ? null : dto.getOrgId().toString();
int userType = dto.getUsertype() == null ? 0 : Integer.parseInt(dto.getUsertype());
JwtAccessToken jwtAccessToken = new JwtAccessToken(dto.getUserName(), orgId, dto.getIdCard(), 1, dto.getClient(), userType);
String accessTokenStr = JwtUtil.jwtAccessTokenHM256(jwtAccessToken, JwtUtil.SECRET);
JwtRefreshToken jwtRefreshToken = new JwtRefreshToken(dto.getUserName(), dto.getIdCard(), 1, dto.getClient(), userType, orgId);
String refreshTokenStr = JwtUtil.jwtRefreshTokenHM256(jwtRefreshToken, JwtUtil.SECRET);
//存到redis
cacheAccessTokenAndRefreshToken(atkey, accessTokenStr, rtkey, refreshTokenStr);
tokenVo.setAccessToken(accessTokenStr);
tokenVo.setRefreshToken(refreshTokenStr);
}
return tokenVo;
}
2、验证jwt token,直接 JwtUtil.unJwtToken(jwt) 就可以验证token,这点比较方便,如果需要再进一步验证
token 可以解密出来根据唯一值找数据,再验证一遍。
@GetMapping("/validate")
public Object validate(String jwt, String sso_cookie) {
if (verifyCookie) {
JwtToken rawJwtToken = JwtUtil.unJwtToken(jwt);
if (rawJwtToken == null || StringUtils.isEmpty(rawJwtToken.getClientId())) {
throw new BusinessException(ResultCode.TOKEN_INVALID);
}
//check(clientDetail);
String accessKeyStr = TokenKeyUtil.getAccessTokenKey(verifyJwtToken.getClientId(), verifyJwtToken.getAppType(), verifyJwtToken.getUserId(), verifyJwtToken.getOrgId());
Long validation = (Long) redisTemplate.execute(validateAccessTokenScript, Arrays.asList(accessKeyStr), jwt);
if (!accessKeyStr.equals(jwt)) {
throw new BusinessException(ResultCode.TOKEN_INVALID);
}
}
return jwt;
}
//生成jwt token,可以用非对称加密
public static String jwtAccessTokenHM256(JwtAccessToken jwtAccessToken, String secret) {
try {
Algorithm algorithm = Algorithm.HMAC256(secret);
String token = JWT.create()
.withClaim("username", jwtAccessToken.getUsername()).withClaim("userId", jwtAccessToken.getUserId()).withClaim("orgId", jwtAccessToken.getOrgId()).withClaim("appType", jwtAccessToken.getAppType()).withClaim("clientId", jwtAccessToken.getClientId()).withClaim("tokenType", jwtAccessToken.getTokenType()).withClaim("userType", jwtAccessToken.getUserType()).withIssuer(ISSUER).withIssuedAt(new Date()).sign(algorithm);
return token;
} catch (Exception exception) {
logger.error("jwt处理异常:", exception);
throw new RuntimeException();
}
}
//解密jwt token
public static JwtToken unJwtToken(String token) {
try {
DecodedJWT jwt = JWT.decode(token);
Map claimMap = jwt.getClaims();
String username = getString(claimMap.get("username"));
String userId = claimMap.get("userId").asString();
String orgId = getString(claimMap.get("orgId"));
int appType = claimMap.get("appType").asInt();
String clientId = claimMap.get("clientId").asString();
String tokenType = claimMap.get("tokenType").asString();
int userType = claimMap.get("userType").asInt();
JwtToken jwtToken = null;
if (TokenType.ACCESS_TOKEN.equals(tokenType)) {
jwtToken = new JwtAccessToken(username, orgId, userId, appType, clientId, userType);
} else if (TokenType.REFRESH_TOKEN.equals(tokenType)) {
jwtToken = new JwtRefreshToken(username, userId, appType, clientId, userType, orgId);
}
return jwtToken;
} catch (Exception e) {
logger.error("jwt处理异常:", e);
throw new BusinessException("jwt解析错误");
}
}
jwt 实际上就是定义了一套数据加密以及 验签的算法的规范,根据这个规范来实现单点登录,以及数据传输及验签功能。但是这个方案不能传递敏感信息(所以比如密码一般不放在Jwt中),因为 jwt 中的部分内容可
以解 密,只是不能修改而已。