18.JavaWeb-JWT(登录、鉴权)

1.CSRF跨站请求伪造

        跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。

18.JavaWeb-JWT(登录、鉴权)_第1张图片

        为了防止CSRF攻击,产检的防御措施有:生成随即令牌(token)、设置Cookie的SameSite属性为Strict或Lax 、验证Referer字段、双重身份验证等

2.JWT概念

        JWT(JSON Web Token)是一种用于进行身份验证和授权的开放标准(RFC 7519)。它是一种安全的、基于JSON的令牌,用于在客户端和服务器之间传递声明

18.JavaWeb-JWT(登录、鉴权)_第2张图片

 2.1 JWT组成

header 声明类型、声明加密的算法,通常直接使用HMAC SHA256或RSA
payload 也称为JWT Claims,包含用户的一些非隐私数据(秘钥、签发人、签发时间、有效时间等)
Signature 签证信息,由三部分组成:header(base64后的)、payload(base64后的)、secred

3.登录详细流程

3.1 单token验证

3.1.1 登录成功,生成token并返回

        1.生成token

String token = JWTUtil.generateToken(user.getId());

        2.设置响应头

response.setHeader("authorization", token);

        3.暴露响应头

        浏览器不认识自定义的头,如果不暴露浏览器会自动屏蔽

response.setHeader("Access-Control-Expose-Headers","authorization");

3.1.2 前端得到token保存在浏览器本地

        sessionstorage:在会话期间有效(浏览器打开期间)
        localstorage:只要不主动删除、不卸载浏览器,数据一直有效

let token = res.headers.authorization
window.localStorage.setItem("authorization", token)

3.1.3 通过axios拦截器自动携带token

http.interceptors.request.use(
  config =>{
    // 得到token  本地
    let token = window.localStorage.getItem("authorization")
    config.headers.authorization = token
    // 放行请求
    return config
  }
)

3.1.4 自定义过滤器验证token

if (token == null || token.length() == 0 || token.equals("null")){
    // 没登录
    extracted(servletResponse);
    // 终止
    return;
}else {
    // 有token
    if (JWTUtil.verify(token) == TokenEnum.TOKEN_SUCCESS){
        // 合法,放行
        request.getSession().setAttribute("uid", JWTUtil.getuid(token));
        filterChain.doFilter(servletRequest, servletResponse);
    }else {
        // 伪造或者过期,都让登录
        extracted(servletResponse);
        // 终止请求
        return;
    }
}

private static void extracted(ServletResponse servletResponse) throws IOException {
    ResponseResult responseResult = new ResponseResult<>(403,"无法访问此界面,请登录",null);
    //转json
    String json = new ObjectMapper().writeValueAsString(responseResult);
    //设置响应头
    servletResponse.setContentType("application/json;charset=utf-8");
    servletResponse.getWriter().write(json);
} 
  

3.2 双token验证(安全性更高)

3.2.1 登录成功生成两个token

//生成Token令牌
String token = JWTUtil.generateToken(user.getId());
//生成refreshToken
String refreshtoken = UUID.randomUUID().toString();

3.2.2 以refreshtoken作为key,token作为value放入Redis并设置过期时间

redisTemplate.opsForValue().set(refreshtoken,token,JWTUtil.REFRESH_TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);

3.2.3 设置响应头、暴露头

//将token放到响应头中返回给前端(流行做法)
response.setHeader("authorization",token);
response.setHeader("refreshtoken",refreshtoken);
//暴露头,浏览器不认识自定义的头,如果不暴露浏览器会自动屏蔽
response.setHeader("Access-Control-Expose-Headers","authorization,refreshtoken");

3.2.4 前端在响应拦截器上得到两个token放到本地

http.interceptors.response.use(
  response =>{
    // 判断响应中是否有token信息,如果有则将token放到本地
    let token = response.headers.authorization
    if(token){
      // 不为空放本地
      window.localStorage.setItem("authorization", token)
    }
    let refreshtoken = response.headers.refreshtoken
    console.log(refreshtoken)
    if(refreshtoken){
      window.localStorage.setItem("refreshtoken", refreshtoken)
    }
    return response
  }
)

3.2.5 前端在请求拦截将两个token设置到请求头

http.interceptors.request.use(
  config =>{
    // 从浏览器本地得到token
    let token = window.localStorage.getItem("authorization")
    let refreshtoken = window.localStorage.getItem("refreshtoken")
    config.headers.authorization = token
    config.headers.refreshtoken = refreshtoken
    // 放行请求
    return config
  }
)

3.2.6 在AuthFilter校验两个token

//需要登录
//获取token
String token = request.getHeader("authorization");
String refreshtoken = request.getHeader("refreshtoken");

//校验refreshtoken是否过期
if(refreshtoken==null || refreshtoken.length()==0 || refreshtoken.equals("null")||!redisTemplate.hasKey(refreshtoken)){
    //非法、过期   去登陆
    extracted(servletResponse);
    return;
}
//判断token
if(token==null || token.length()==0 || token.equals("null")){
    //没登陆
    extracted(servletResponse);
    return;
}else{
    if(JWTUtil.verify(token) == TokenEnum.TOKEN_SUCCESS){
        //进一步的安全验证
        if(token.equals(redisTemplate.opsForValue().get(refreshtoken))){
            //合法,登录成功
            //过滤器放行:让后面的过滤器 或者 servlet处理这个请求
//                    request.getSession().setAttribute("uid",JWTUtil.getuid("authorization"));
            filterChain.doFilter(servletRequest, servletResponse);
        }else{
            //伪造
            extracted(servletResponse);
            return;
        }
    }else if(JWTUtil.verify(token) == TokenEnum.TOKEN_EXPIRE){
        //过期 重新生成token
        token = JWTUtil.generateToken(JWTUtil.getuid(token));
        //判断token与后台记录的是否一致
        //进一步的安全验证
        if(token.equals(redisTemplate.opsForValue().get(refreshtoken))){
            //修改redis的数据
            redisTemplate.opsForValue().set(refreshtoken,token,JWTUtil.REFRESH_TOKEN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
            //将新的token返回前端
            //将token放到响应头中返回给前端(流行做法)
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            response.setHeader("authorization",token);
            response.setHeader("refreshtoken",refreshtoken);
            //暴露头,浏览器不认识自定义的头,如果不暴露浏览器会自动屏蔽
            response.setHeader("Access-Control-Expose-Headers","authorization");
            filterChain.doFilter(servletRequest, servletResponse);
        }else{
            //伪造
            extracted(servletResponse);
            return;
        }
    }else{
        //伪造
        extracted(servletResponse);
        return;
    }

你可能感兴趣的:(JavaEE,前端)