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();
    }
}

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐