JWT学习(二):JWT在分布式SSO中的应用实例
扫描二维码
随时随地手机看文章
上一篇文章讲解了JWT的基本简介,这一篇文章我就来实战一下。介绍一下在分布式单点登录中的使用方法:
首先来看一下Token实体类,
public class Token implements Serializable{ private static final long serialVersionUID = -5391652691006115018L; /** 认证头 **/ private Head head; /** 认证信息有效载荷 **/ private Playload playload; /** 第一部分:base64head头 **/ private String base64Head; /** 第二部分:base64playload荷载 **/ private String base64PlayLoad; /** 第三部分:签证信息 **/ private String signature; /** 最终token字符串 **/ private String tokenStr; /** * Token头 * * */ public static class Head implements Serializable{ private static final long serialVersionUID = -6516084948347601103L; /** token类型 **/ private String typ = "JWT"; /** token算法 默认:HMAC SHA256**/ private String alg = "HS256"; public String getTyp() { return typ; } public void setTyp(String typ) { this.typ = typ; } public String getAlg() { return alg; } public void setAlg(String alg) { this.alg = alg; } } /** * Token有效载荷 * * */ public static class Playload implements Serializable { private static final long serialVersionUID = 3981890375700111920L; /** 该token签发者 **/ private String iss; /** 该token的所有人,可以存放用户名 **/ private String sub; /** 接收token的一方 **/ private String aud; /** token的过期时间(时间戳),必须要大于签发时间;大于等于该时间需要刷新token **/ private long exp; /** token生效的开始时间(时间戳),意味着在这个时间之前验证token是会失败的,默认生成token后立即生效 **/ private long nbf; /** token的签发时间 时间戳**/ private long iat; /** token的唯一身份标识,主要用来作为一次性token,从而回避重放攻击 **/ private String jti; /** token验证宽限时间(时间戳) 超过宽限时间需要重新登录, * 即该token的真正存活时间,宽限时间的加入是为了解决并发token刷新后新token失效问题 * **/ private long gra; /** token类型: 后台登录用户,互联网用户,第三方机构 **/ private String typ; public String getIss() { return iss; } public void setIss(String iss) { this.iss = iss; } public String getSub() { return sub; } public void setSub(String sub) { this.sub = sub; } public String getAud() { return aud; } public void setAud(String aud) { this.aud = aud; } public long getExp() { return exp; } public void setExp(long exp) { this.exp = exp; } public long getNbf() { return nbf; } public void setNbf(long nbf) { this.nbf = nbf; } public long getIat() { return iat; } public void setIat(long iat) { this.iat = iat; } public String getJti() { return jti; } public void setJti(String jti) { this.jti = jti; } public long getGra() { return gra; } public void setGra(long gra) { this.gra = gra; } public String getTyp() { return typ; } public void setTyp(String typ) { this.typ = typ; } } public Head getHead() { return head; } public void setHead(Head head) { this.head = head; } public Playload getPlayload() { return playload; } public void setPlayload(Playload playload) { this.playload = playload; } public String getSignature() { return signature; } public void setSignature(String signature) { this.signature = signature; } public String getBase64Head() { return base64Head; } public void setBase64Head(String base64Head) { this.base64Head = base64Head; } public String getBase64PlayLoad() { return base64PlayLoad; } public void setBase64PlayLoad(String base64PlayLoad) { this.base64PlayLoad = base64PlayLoad; } public String getTokenStr() { return tokenStr; } public void setTokenStr(String tokenStr) { this.tokenStr = tokenStr; } }
可以看到实体类里面包含了head payload signature这三部分。
然后用户登录成功之后创建token的代码如下:
public static Token createToken(String secret,String tokenId,TokenType tokenType,String userName) { try { Token token = new Token(); //创建头 Token.Head head = new Token.Head(); head.setAlg("HS256"); head.setTyp("JWT"); //创建载荷 //签发时间 long iat = System.currentTimeMillis(); //过期时间 20 分钟后过期 long exp = iat + AuthConstants.TOKEN_EXP_TIME ; //最后存活时间 long gra = iat + AuthConstants.TOKEN_GRA_TIME ; Token.Playload playload = new Token.Playload(); playload.setAud("CLIENT"); //接收token的一方 playload.setIat(iat); //签发时间 playload.setExp(exp); playload.setGra(gra); playload.setIss("AUTH_CENTER");//签发者 playload.setJti(tokenId); //token唯一身份标识 playload.setNbf(iat);//生效时间,立即生效 playload.setSub(userName); //token的所属者,可以存放用户名 playload.setTyp(tokenType.toString().toUpperCase()); //创建token String base64Head = Base64Util.encodeStr(JSONUtil.toJson(head) ); String base64Playload = Base64Util.encodeStr(JSONUtil.toJson(playload) ); String signature = HmacUtil.encryptHMACSHA256(base64Head+"."+base64Playload, secret); //token签名 String tokenStr = base64Head+"."+base64Playload+"."+signature; //token字符串 //组装token对象 token.setHead(head); token.setPlayload(playload); token.setBase64Head(base64Head); token.setBase64PlayLoad(base64Playload); token.setSignature(signature); token.setTokenStr(tokenStr); return token; } catch (Exception e) { e.printStackTrace(); logger.error("生成token失败:{}",e.getMessage()); } return null; }
创建成功之后,要把token放到响应头中,setHeader方法name参数要用Authorization,value值要使用
"Bearer "+token。
然后用户访问需要权限的接口都需要在请求头加上token,因为使用了Spring Cloud微服务架构,因此请求
会统一通过API网关访问,所以需要在网关处验证token的合法性,使用下面这个parseToken的方法:
/** * 验证并解析token * @param token token字符串 * @param secret token签名的盐(密钥) * @return 成功返回token ,失败返回ull */ public static Token parseToken(String token,String secret) { try { //判断token是否是合法格式的token串 if(StringUtils.isEmpty(token)) { throw new AuthException("token串为空!"); } if(StringUtils.isEmpty(secret)) { throw new AuthException("解析token时,token密钥为空!"); } String[] tokens = token.split("\."); if(tokens==null || tokens.length!=3) { throw new AuthException("非法格式的token串:"+token); } //token分解 String base64Head = tokens[0].trim(); //token头 String base64Playload = tokens[1].trim(); //token载荷 String signature = tokens[2].trim(); //token签名 //验证签名是否为合法的 String signData = base64Head+"."+base64Playload; String signaturedStr = HmacUtil.encryptHMACSHA256(signData, secret.trim()); //token签名 if(!signature.equals(signaturedStr)) { throw new AuthException("非法的token:解析token时,token验签失败!"); } Token.Head head = JSONUtil.toBean(Base64Util.decodeStr(base64Head), Token.Head.class); Token.Playload playLoad = JSONUtil.toBean(Base64Util.decodeStr(base64Playload), Token.Playload.class); Token rs = new Token(); rs.setHead(head); rs.setPlayload(playLoad); rs.setSignature(signature); return rs; } catch (Exception e) { e.printStackTrace(); logger.error("解析token失败:{}",e.getMessage()); return null; } }
使用JWT进行身份验证的基本方法的实例就到这里,没有接触过JWT的同学可以先看一下JWT