项目实训进度记录【4.27-4.28】

摘要

把项目全部改为用token方式验证请求合法性,前端改为用localStorage存储用户信息。

过程

之前我们的策略是登录后在前端用session记录用户的用户名和账号,每次发请求的时候携带用户名发送,注销后将session的用户名和账号置空。为了防止未登录的用户进入网站,我在前端做了前端守卫,即每次要跳转到新页面的时候前端自动检查session的账号是否为空,如果为空,则跳转至登录页面;如果不为空才能正常完成跳转。这种方式可以阻止无登陆状态的用户进入前端页面,但它有一个致命缺陷,就是它无法阻止恶意攻击者构造数据包并将正经用户的用户名携带在包中,直接向后端发送请求并获取信息。就是所谓,所有前端的限制都是可以被绕过的,只有后端的限制才有用。我之前也尝试过在后端用Security,但没有成功。

这次,我根据师哥的建议,改用token方式验证请求合法性。其具体流程如下:
首次登录时,后端服务器判断用户账号密码正确之后,根据用户id、用户名、定义好的秘钥、过期时间生成 token ,返回给前端;
前端拿到后端返回的 token ,存储在 localStorage 和 Vuex 里;
前端每次路由跳转,判断 localStorage 有无 token ,没有则跳转到登录页,有则请求获取用户信息,改变登录状态;
每次请求接口,在 Axios 请求头里携带 token;
后端接口判断请求头有无 token,没有或者 token 过期,返回401;
前端得到 401 状态码,重定向到登录页面。

编程上要做的事情是:(这个项目中,以下方法都放在Util文件夹中,作为工具类)
1.写token的生成器和解析器:这个项目中token生成器是根据用户的身份、id和时间戳生成一个token,解析器可以从token中解析出用户的身份、id和时间戳。在拦截器中需要用到二者,并且在一般的Controller中需要用token解析器获取请求的发起者信息,登录方法中需要用token生成器根据用户的登录信息生成token再返回给用户。

package project.ourcourseassistant.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class TokenUtil {
    @Value("${token.secretKey}")
    private String secretKey;

    /**
     * 加密token.
     */
    public String getToken(String userId, String userRole) {
        //这个是放到负载payLoad 里面,魔法值可以使用常量类进行封装.
        String token = JWT.create().withClaim("userId", userId).withClaim("userRole", userRole).withClaim("timeStamp", System.currentTimeMillis()).sign(Algorithm.HMAC256(secretKey));
        System.out.println("token="+token);
        return token;
    }

    /**
     * 解析token.     * {     * "userId": "weizhong",     * "userRole": "ROLE_ADMIN",     * "timeStamp": "134143214"     * }
     */
    public Map<String, String> parseToken(String token) {
        HashMap<String, String> map = new HashMap<String, String>();
        DecodedJWT decodedjwt = JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token);
        Claim userId = decodedjwt.getClaim("userId");
        Claim userRole = decodedjwt.getClaim("userRole");
        Claim timeStamp = decodedjwt.getClaim("timeStamp");
        map.put("userId", userId.asString());
        map.put("userRole", userRole.asString());
        map.put("timeStamp", timeStamp.asLong().toString());
        return map;
    }
}


2.写一个拦截器:拦截器的作用是对后端受到的每一个请求(除非是对指定接口发起的)检验是否含有有效的token,如果没有,则不能进入方法内部。

/*AuthHandlerInterceptor.java*/
package project.ourcourseassistant.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

@Slf4j
@Component
public class AuthHandlerInterceptor implements HandlerInterceptor {
    @Autowired
    TokenUtil tokenUtil;
    @Value("${token.refreshTime}")
    private Long refreshTime;
    @Value("${token.expiresTime}")
    private Long expiresTime;

    /**
     * 权限认证的拦截操作.
     */
    @Override
    public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception {
        log.info("=======进入拦截器========");        // 如果不是映射到方法直接通过,可以访问资源.
        if (!(object instanceof HandlerMethod)) {
            log.info("here");
            return true;
        }        //为空就返回错误
        String token = httpServletRequest.getHeader("token");
        if (null == token || "".equals(token.trim())) {
            log.info("token为空");
            return false;
        }
        log.info("==============token:" + token);
        Map<String, String> map = tokenUtil.parseToken(token);
        String userId = map.get("userId");
        String userRole = map.get("userRole");
        System.out.println("userId="+userId);
        System.out.println("userRole="+userRole);
        System.out.println("map.get('timeStamp')="+map.get("timeStamp"));
        long timeOfUse = System.currentTimeMillis() - Long.parseLong(map.get("timeStamp"));        //1.判断 token 是否过期
        System.out.println(timeOfUse);
        if (timeOfUse < refreshTime) {
            log.info("token验证成功");
            return true;
        }        //超过token刷新时间,刷新 token
        else if (timeOfUse >= refreshTime && timeOfUse < expiresTime) {
            httpServletResponse.setHeader("token", tokenUtil.getToken(userId, userRole));
            log.info("token刷新成功");
            return true;
        }        //token过期就返回 token 无效.
        else {// timeOfUse < expiresTime
            return false;
        }
    }
}

3.配置拦截器:告诉拦截器它应该对哪些请求起作用(不过配置的时候往往给出的都是“对哪些请求不起作用”)

package project.ourcourseassistant.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.ArrayList;
import java.util.List;

@Configuration
public class AuthWebMvcConfigurer implements WebMvcConfigurer {
    @Autowired
    AuthHandlerInterceptor authHandlerInterceptor;

    /**
     * 给除了 /login 的接口都配置拦截器,拦截转向到 authHandlerInterceptor
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        List<String> excludePatterns=new ArrayList<>();
        excludePatterns.add("/student/login");
        excludePatterns.add("/teacher/login");

        registry.addInterceptor(authHandlerInterceptor).addPathPatterns("/**").excludePathPatterns(excludePatterns);
    }
}

另外,前端使用localStorage在页面之间传递用户名、id和token。前端登录的时候,用localStorage.setItem('token','')保存信息,其他页面中可以用user_id: localStorage.getItem("user_id")获取存储在localStorage中的信息。前端向后端发送的每个请求都应该在header中包含token信息:

headers: { 
'token':localStorage.getItem("token") 
},

由于之前纠结过应该用cookie在前端存储用户信息还是用session,所以查了一下相关概念的异同:

一. Cookie
什么是Cookie?
   用我的理解来说,浏览器A在第一次请求服务器B的时候,服务器B会为该浏览器A创建一个Cookie,并在响应时候把这个Cookie一同返回给浏览器A,这样A在请求数据不变的情况下再次请求服务器B的时候会带着这个Cookie,这样服务器B当接收A请求时会辨识出这个Cookie,明白A是访问过B的并可能存储着属于A的数据(在这个过程中存在着session的问题稍后讲解)。
  
Cookie持续多久呢?
可以通过setMaxAge()方法设置Cookie存在时间。
取值说明:
0有效期,单位秒
=0失效
<0内存存储

二. Session
1.什么是Session   
我的理解,Session是为了给浏览器在服务器端储存自己独有数据的一个集合,只是在服务器端。通过setAttribute()方法存数据,getAttribute()方法取数据。

2.Session持续多久呢?

默认不设置的话保持30分钟。 有两种设置Session的方法: (1)setMaxInactiveInterval()方法,单位秒
(2)在web.xml中配置 ,单位分钟
<session-config> <session-timeout>20</session-timeout> </session-config>


Vue中使用vue-session

安装

  npm install vue-session   复制代码

在main.js中引入

  import VueSession from 'vue-session'
  Vue.use(VueSession)   复制代码

使用

  this.$session.set("key",value); //存session
  this.$session.get("key") //获取session   复制代码   二、Vue中使用vue-cookies

安装

  npm install vue-cookies --save   复制代码

在main.js中引入

  import VueCookies from 'vue-cookies'
  Vue.use(VueCookies);   复制代码

使用

  this.$cookies.set("key",value); //存cookies
  this.$cookies.get("key"); //获取cookies

================================================================================================================================================ 讲得很详细的一篇 https://juejin.cn/post/6844903937523482631

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值