使用JWT实现Token认证
为什么使用JWT?
json Web Token(缩写JWT)是目前最流行的跨域认证解决方案。
session登录的认证方案是看,用户从客户端传递用户名和密码登录信息,服务端认证后将信息储存在session中,将session_id放入cookie中,以后访问其他页面,浏览器都会带着cookie,服务端会自动从cookie中获取session_id,在从session中获取认证信息。
JWT的解决方案是,将认证信息返回个客户端,储存在客户端,下次访问其他页面,需要从客户端传递认证信息回服务器端。
JWT应用场景:
下列场景中使用JSON Web Token是很有用的:
- Authorization (授权) : 这是使用JWT的最常见场景。一旦用户登录,后续每个请求都将包含JWT,允许用户访问该令牌允许的路由、服务和资源。单点登录是现在广泛使用的JWT的一个特性,因为它的开销很小,并且可以轻松地跨域使用。
- Information Exchange (信息交换) : 对于安全的在各方之间传输信息而言,JSON Web Tokens无疑是一种很好的方式。因为JWTs可以被签名,例如,用公钥/私钥对,你可以确定发送人就是它们所说的那个人。另外,由于签名是使用头和有效负载计算的,您还可以验证内容没有被篡改。
优点:
1.简洁(Compact): 可以通过URL
,POST
参数或者在HTTP header
发送,因为数据量小,传输速度也很快
2.自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
3.因为Token
是以JSON
加密的形式保存在客户端的,所以JWT
是跨语言的,原则上任何web形式都支持。
4.不需要在服务端保存会话信息,特别适用于分布式微服务。
JSON Web Token的结构是什么样的
JSON Web Token由三部分组成,它们之间用圆点(.)连接。这三部分分别是:
- Header
- Payload
- Signature
因此,一个典型的JWT看起来是这个样子的:
xxxxx.yyyyy.zzzzz
接下来,具体看一下每一部分:
Header
header典型的由两部分组成:token的类型(“JWT”)和算法名称(比如:HMAC SHA256或者RSA等等)。
例如:
然后,用Base64对这个JSON编码就得到JWT的第一部分
Payload
JWT的第二部分是payload,它包含声明(要求)。声明是关于实体(通常是用户)和其他数据的声明。声明有三种类型: registered, public 和 private。
- Registered claims : 这里有一组预定义的声明,它们不是强制的,但是推荐。比如:iss (issuer), exp (expiration time), sub (subject), aud (audience)等。
- Public claims : 可以随意定义。
- Private claims : 用于在同意使用它们的各方之间共享信息,并且不是注册的或公开的声明。
下面是一个例子:
对payload进行Base64编码就得到JWT的第二部分
注意,不要在JWT的payload或header中放置敏感信息,除非它们是加密的。
Signature
为了得到签名部分,你必须有编码过的header、编码过的payload、一个秘钥,签名算法是header中指定的那个,然对它们签名即可。
例如:
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
签名是用于验证消息在传递过程中有没有被更改,并且,对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。
看一张官网的图就明白了:
JSON Web Tokens是如何工作的
- 应用(或者客户端)想授权服务器请求授权。例如,如果用授权码流程的话,就是/oauth/authorize
- 当授权被许可以后,授权服务器返回一个access token给应用
- 应用使用access token访问受保护的资源(比如:API)
maven依赖:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
JWT工具类:
用于生成Token,和Token验证
import com.demo.model.CheckResult; import com.sun.org.apache.xerces.internal.impl.dv.util.Base64; import io.jsonwebtoken.*; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Date; import java.util.UUID; public class JwtUtil { private static final String JWT_SECERT="zjn162"; /** * 签发JWT * @param subject 可以是JSON数据 尽可能少 * @param ttlMillis * @return String * */ public static String createJWT( String subject,String jsonData, long ttlMillis) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); SecretKey secretKey = generalKey(); JwtBuilder builder = Jwts.builder() .setId(UUID.randomUUID().toString()) .setSubject(subject) // 主题 .setIssuer("zjn") // 签发者 .setIssuedAt(now) // 签发时间 .setPayload(jsonData)//添加自定义数据 .signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); builder.setExpiration(expDate); // 过期时间 } return builder.compact(); } /** * 验证JWT * * @param jwtStr * @return */ public static CheckResult validateJWT(String jwtStr) { CheckResult checkResult = new CheckResult(); Claims claims = null; try { claims = parseJWT(jwtStr); checkResult.setResult(true); checkResult.setClaims(claims); } catch (ExpiredJwtException e) { checkResult.setMsg("token expired."); checkResult.setResult(false); } catch (Exception e) { checkResult.setMsg("token check failed."); checkResult.setResult(false); } return checkResult; } public static SecretKey generalKey() { byte[] encodedKey = Base64.decode(JWT_SECERT); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * * 解析JWT字符串 * * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody(); } }
import io.jsonwebtoken.Claims; import lombok.Data; @Data public class CheckResult { private Claims claims; private boolean result; private String msg; }
如何使用?
代码实例:
public class LoginController { @Autowired UserRepository userRepository; @RequestMapping(value="login",method = RequestMethod.POST) public ReturnVo login(String username, String password,HttpServletResponse response) { User user = userRepository.findByUsername(username); if(user!=null){ if(user.getPassword().equals(password)){ //把token返回给客户端-->客户端每次请求附带cookie参数 String JWT = JwtUtil.createJWT("login",username, SystemConstant.JWT_TTL); return ReturnVo.ok(JWT); }else{ return ReturnVo.error(); } }else{ return ReturnVo.error(); } }
@RequestMapping(value="description",method = RequestMethod.POST) public ReturnVo description(String username) { User user = userRepository.findByUsername(username); return ReturnVo.ok(user.getDescription()); } }