JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权 。
那么什么是无状态??
微服务集群中的每个服务,对外提供的都是Rest风格的接口。而Rest风格的一个最重要的规范就是:服务的无状态性 。
无状态带来的好处是什么呢?
-
客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
-
服务端的集群和状态对客户端透明
-
服务端可以任意的迁移和伸缩
-
减小服务端存储压力
无状态登录的流程:
-
当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
-
认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
-
以后每次请求,客户端都携带认证的token
-
服务的对token进行解密,判断是否有效。
那么使用token来进行用户信息的传递就必然要确保token的安全性。那么我们采用什么加密算法来确保安全可靠呢?
我们将采用JWT + RSA非对称加密。
JWT包含三部分数据:
-
Header:头部,通常头部有两部分信息:
-
声明类型,这里是JWT
我们会对头部进行base64编码,得到第一部分数据
-
-
Payload:载荷,就是有效数据,一般包含下面信息:
-
用户身份信息(注意,这里因为采用base64编码,可解码,因此不要存放敏感信息)
-
注册声明:如token的签发时间,过期时间,签发人等
这部分也会采用base64编码,得到第二部分数据
-
-
Signature:签名,是整个数据的认证信息。一般根据前两步的数据,再加上服务的的密钥(secret)(不要泄漏,最好周期性更换),通过加密算法生成。用于验证整个数据完整和可靠性
JWT的交互步骤:
-
1、用户登录
-
2、服务的认证,通过后根据secret生成token
-
3、将生成的token返回给浏览器
-
4、用户每次请求携带token
-
5、服务端利用公钥解读jwt签名,判断签名有效后,从Payload中获取用户信息
-
6、处理请求,返回响应结果
因为JWT签发的token中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,完全符合了Rest的无状态规范。
结合Zuul和RSA的鉴权流程:
-
我们首先利用RSA生成公钥和私钥。私钥保存在授权中心,公钥保存在Zuul和各个信任的微服务
-
用户请求登录
-
授权中心校验,通过后用私钥对JWT进行签名加密
-
返回jwt给用户
-
用户携带JWT访问
-
Zuul直接通过公钥解密JWT,进行验证,验证通过则放行
-
请求到达微服务,微服务直接用公钥解析JWT,获取用户信息,无需访问授权中心
那么在项目中怎么配置鉴权中心呢??
一、在application.yml中配置
jd:
jwt:
secret: jd@Login(Auth}*^31)&Demon% # 登录校验的密钥,随便写的
pubKeyPath: C:\\Users\\ASUS\\Desktop\\rsa\\rsa.pub # 公钥地址
priKeyPath: C:\\Users\\ASUS\\Desktop\\rsa\\rsa.pri # 私钥地址
expire: 30 # 过期时间,单位分钟
cookieName: JD_TOKEN
二、获取yml中的配置信息
@ConfigurationProperties(prefix = "jd.jwt")
public class JwtProperties {
private String secret; // 密钥
private String pubKeyPath;// 公钥
private String priKeyPath;// 私钥
private int expire;// token过期时间
private PublicKey publicKey; // 公钥
private PrivateKey privateKey; // 私钥
private String cookieName;
private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);
/**
* @PostContruct:在构造方法执行之后执行该方法
*/
@PostConstruct
public void init(){
try {
File pubKey = new File(pubKeyPath);
File priKey = new File(priKeyPath);
if (!pubKey.exists() || !priKey.exists()) {
// 生成公钥和私钥
RsaUtils.generateKey(pubKeyPath, priKeyPath, secret);
}
// 获取公钥和私钥
this.publicKey = RsaUtils.getPublicKey(pubKeyPath);
this.privateKey = RsaUtils.getPrivateKey(priKeyPath);
} catch (Exception e) {
logger.error("初始化公钥和私钥失败!", e);
throw new RuntimeException();
}
}
//setter和getter方法
}
注:其中的prefix要与yml中的前缀保持一致,才可以获取到数据
三、通过@EnableConfigurationProperties注解获取配置文件中的数据
@RestController
@EnableConfigurationProperties(JwtProperties.class)
public class AuthController {
@Autowired
private AuthService authService;
@Autowired
private JwtProperties prop;
/**
* 登录授权
* @param username
* @param password
* @param request
* @param response
* @return
*/
@PostMapping("accredit")
public ResponseEntity<Void> authentication(
@RequestParam("username") String username,
@RequestParam("password") String password,
HttpServletRequest request, HttpServletResponse response
) {
try {
// 生成token
String token = this.authService.authentication(username, password);
if (StringUtils.isBlank(token)){
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// 放入cookie中,this.prop.getExpire() * 60为设置有效时间,单位秒
CookieUtils.setCookie(request, response, this.prop.getCookieName(), token, this.prop.getExpire() * 60);
return ResponseEntity.ok().build();
} catch (Exception e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
/**
* 验证用户信息
* @param token
* @param request
* @param response
* @return
*/
@GetMapping("verify")
public ResponseEntity<UserInfo> verify(
@CookieValue(value = "JD_TOKEN", required = false)String token,
HttpServletRequest request, HttpServletResponse response
){
try {
// 解析token
UserInfo userInfo = JwtUtils.getInfoFromToken(token, this.prop.getPublicKey());
// 判断userInfo是否为null
if (userInfo == null){
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// 刷新登陆状态
// 刷新jwt的有效时间
token = JwtUtils.generateToken(userInfo, this.prop.getPrivateKey(), this.prop.getExpire());
// 刷新cookie有效时间
CookieUtils.setCookie(request, response, this.prop.getCookieName(), token, this.prop.getExpire() * 60);
return ResponseEntity.ok(userInfo);
} catch (Exception e) {
e.printStackTrace();
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
}