基于Token的API接口认证机制
2017-06-07 本文已影响1986人
FX_SKY
Cookie Auth
Cookie认证机制就是为一次请求认证在服务端创建一个Session对象,同时在客户端的浏览器端创建了一个Cookie对象;通过客户端带上来Cookie对象来与服务器端的session对象匹配来实现状态管理的。默认的,当我们关闭浏览器的时候,cookie会被删除。但可以通过修改cookie 的expire time使cookie在一定时间内有效。
Token Auth的优点
Token机制相对于Cookie机制相比有以下优势:
- 支持跨域访问: Cookie是不允许垮域访问的,这一点对Token机制是不存在的,前提是传输的用户认证信息通过HTTP头传输;
- 无状态(也称:服务端可扩展行):Token机制在服务端不需要存储session信息,因为Token 自身包含了所有登录用户的信息,只需要在客户端的cookie或本地介质存储状态信息;
- 更适用CDN: 可以通过内容分发网络请求你服务端的所有资料(如:javascript,HTML,图片等),而你的服务端只要提供API即可.
去耦: 不需要绑定到一个特定的身份验证方案。Token可以在任何地方生成,只要在你的API被调用的时候,你可以进行Token生成调用即可; - 更适用于移动应用: 当你的客户端是一个原生平台(iOS, Android,Windows 8等)时,Cookie是不被支持的(你需要通过Cookie容器进行处理),这时采用Token认证机制就会简单得多;
- CSRF:因为不再依赖于Cookie,所以你就不需要考虑对CSRF(跨站请求伪造)的防范。
认证过程
下面我们从一个实例来看如何运用JWT机制实现认证:
登录授权
- 第一次登录,用户从浏览器输入用户名/密码,提交后台服务器的登录处理的Action层(Login Action);
- Login Action调用认证服务进行用户名密码认证,如果认证通过,Login Action层调用用户信息服务获取用户信息(包括完整的用户信息及对应权限信息);
- 验证用户信息合法后,Login Action从配置文件中获取Token签名生成的秘钥信息,进行Token的生成(可以调用第三方的JWT Lib生成签名后的JWT Token);
- 完成JWT数据签名后,将其设置到COOKIE/HEADER 对象中,并重定向到首页,完成登录过程;
请求认证
基于Token的认证机制会在每一次请求中都带上完成签名的Token信息,这个Token信息可能在COOKIE
中,也可能在HTTP的Authorization头中。
过程如下:
- 客户端(APP客户端或浏览器)通过GET或POST请求访问资源(页面或调用API);
- 通过统一的Filter或者Interceptor 对请求进行拦截,首先在cookie中查找Token信息,如果没有找到,则在HTTP Authorization Head中查找;
- 如果找到Token信息,则根据配置文件中的签名加密秘钥,调用JWT Lib对Token信息进行解密和解码;
- 完成解码并验证签名通过后,对Token中的exp、nbf、aud等信息进行验证;
- 全部通过后,根据获取的用户的角色权限信息,进行对请求的资源的权限逻辑判断;
- 如果权限逻辑判断通过则通过Response对象返回;否则则返回HTTP 401;
基于JWT的Token认证机制实现
Java中对JWT的支持可以考虑使用JJWT开源库。
maven依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
JWT Token生成&验证代码:
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.crypto.MacProvider;
import org.apache.commons.codec.Charsets;
import org.junit.Before;
import org.junit.Test;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
/**
* ${DESCRIPTION}
*
* @author Ricky Fung
*/
public class JwtTokenTest {
private Key key;
private SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
@Before
public void init() {
String secret = "ricky";
//We will sign our JWT with our ApiKey secret
byte[] apiKeySecretBytes = secret.getBytes(Charsets.UTF_8);
key = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
}
@Test
public void testEncode() {
long ttl = 7200 * 1000;
Date now = new Date();
Date exp = new Date(now.getTime() + ttl);
//Let's set the JWT Claims
JwtBuilder builder = Jwts.builder().setId("ucode")
.setIssuedAt(now)
.setExpiration(exp)
.setSubject("sub")
.setIssuer("iss")
.setAudience("aud")
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.signWith(signatureAlgorithm, key);
String compactJws = builder.compact();
System.out.println(compactJws);
}
@Test
public void testDecode() {
String compactJws = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJ1Y29kZSIsImlhdCI6MTQ5NzIzOTE2NiwiZXhwIjoxNDk3MjQ2MzY2LCJzdWIiOiJzdWIiLCJpc3MiOiJpc3MiLCJhdWQiOiJhdWQifQ.gc0OcIlBQND8TlPN6q1nUQf852aIxK4mgkHBBrYMLZ4";
try {
Claims claims = Jwts.parser().setSigningKey(key).parseClaimsJws(compactJws).getBody();
//OK, we can trust this JWT
System.out.println("ID: " + claims.getId());
System.out.println("Subject: " + claims.getSubject());
System.out.println("Issuer: " + claims.getIssuer());
System.out.println("Audience: " + claims.getAudience());
System.out.println("Expiration: " + claims.getExpiration());
} catch (SignatureException e) {
//don't trust the JWT!
}
}
@Test
public void testSimple() {
Key key = MacProvider.generateKey();
String compactJws = Jwts.builder()
.setSubject("Joe")
.signWith(SignatureAlgorithm.HS512, key)
.compact();
System.out.println(compactJws);
}
}