token拦截,springboot+vue

设置token拦截的目的是为了让用户必须经过验证(比如账号密码)才能访问内部页面。
比如你写了"/login",“/register”,“/main"几个页面,那从逻辑上来说用户应该要先进行注册登陆才能访问”/main"页面。
但是使用vue写好这三个页面后你会发现,用户直接访问"/main"也是没有任何阻拦的。所以这个时候需要路由卫士和token拦截。
在注册和登陆通过的时候,会根据你的账号或密码生成一串字符串token,然后存在缓存里。
你想访问任何页面之前,会先验证你是否有token,如果没有,就把你送回"/login"页面,如果有,则你想去哪就去哪。

后端

  1. pom.xml里面添加token依赖
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.7.0</version>
        </dependency>
  1. token生成与验证的类
  • 应用方式:
    • 下面的代码就原封不动复制到后端某个地方,可以建个文件夹存放(为了方便这个文件夹就叫token文件夹了)。
  • 作用:
    • 这个是用于生成和验证token的。
    • 生成token:作用在后端的login方法里(下面有举例),你前端的登陆页面发送了登录请求,那么你后端有个登陆方法(比如就叫checkLogin)验证后,返回结果。现在你要在返回的结果里多带一条信息,就是token,那么token怎么来,需要调用这个TokenGenerate().generateToken(account)方法。
    • 验证token:作用在后端的拦截器中(下面有举例)。前端发送的请求要先经过拦截器,然后再到达各个controller。拦截器里会先设置token检验,那么就会用到这个TokenGenerate.verify(token)。
public class TokenGenerate
{

    private static final long EXPIRE_TIME= 15*60*1000;
    private static final String TOKEN_SECRET="tokenqkj";  //密钥盐
    public String generateToken(String username){
        String token = null;
        try{
            Date expiresAt = new Date(System.currentTimeMillis() + EXPIRE_TIME);
            token = JWT.create()
                    .withIssuer("auth0")
                    .withClaim("username", username)
                    .withExpiresAt(expiresAt)
                    .sign(Algorithm.HMAC256(TOKEN_SECRET));
        }catch (Exception e){
            e.printStackTrace();
        }
        return token;
    }
    /**
     * 签名验证
     * @param token
     * @return
     */
    public static boolean verify(String token){
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();
            DecodedJWT jwt = verifier.verify(token);
            System.out.println("认证通过:");
            System.out.println("issuer: " + jwt.getIssuer());
            System.out.println("username: " + jwt.getClaim("username").asString());
            System.out.println("过期时间:      " + jwt.getExpiresAt());
            return true;
        } catch (Exception e){
            System.out.println("token:"+token);
            System.out.println("没通过");
            return false;
        }
    }
}
  1. 拦截器
  • 应用方式:
    • 下面的代码就原封不动复制到刚刚的token文件夹里。
  • 作用:
    • 这个就是上面说的拦截器。里面有一句request.getHeader("token")就是调用了上面的方法进行验证了。
//token拦截器,对拦截下的接口检查其的token是否符合只有
// 在提供一个有效的token时才能通过验证,否则给出认证失败的响应。
@Component
public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception{
        //Axios 发起跨域请求前,浏览器也会首先发起 OPTIONS 预检请求。检查服务器是否允许跨域访问。
        if(request.getMethod().equals("OPTIONS")){
            response.setStatus(HttpServletResponse.SC_OK);
            System.out.println("允许跨域访问");
            return true;
        }

        response.setCharacterEncoding("utf-8");
        String token = request.getHeader("token");
        if(token != null){
            boolean result = TokenGenerate.verify(token);
            if(result){
                System.out.println("通过拦截器");
                return true;
            }
        }
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write("认证失败,错误码:50000");
        return false;
    }
}
  1. 入口拦截
  • 应用方式:
    • 下面的代码就原封不动复制到刚刚的token文件夹里。
    • 然后修改一处地方:注意我下面有两句
      excludePath.add("/registerCheck"); //注册
      excludePath.add("/loginCheck"); //登录
      

      这是我后端中的注册和登陆方法。在这里写上这两句意思就是,当用户访问这两个方法的时候不用验证token。
      这个很好理解,你登陆和注册的时候肯定是没有token的,token是你账号密码验证通过以后才会赋予你的信息。
      这两个要修改成你的注册登陆url。

//入口拦截,设置哪些接口需要拦截或不拦截(保护后端接口 防止未经授权的访问)
//只需要拦截本身就不会携带token的接口(例如登陆注册)
//因为登陆后网页的请求头就会携带token,此时我们需要进行token的验证,防止在未登陆时就可以进行其他操作
@Configuration
public class IntercepterConfig implements WebMvcConfigurer {
    private final TokenInterceptor tokenInterceptor;

//    构造方法
    public IntercepterConfig(TokenInterceptor tokenInterceptor) {
        this.tokenInterceptor = tokenInterceptor;
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //excludePathPatterns用来配置不需要拦截的接口(或者相当于功能)
        List<String> excludePath = new ArrayList<>();//List用来保存所有不需要拦截的路径
        excludePath.add("/registerCheck"); //注册
        excludePath.add("/loginCheck"); //登录
        //在登陆之后的网页中已经携带token,所以只需要放行登陆注册接口,
        //若放行其他接口,那么就相当于不需要登陆就可进行接口的使用



        registry.addInterceptor(tokenInterceptor)//添加名为tokenInterceptor的拦截器
                .addPathPatterns("/**") //指定拦截所有路径
                .excludePathPatterns(excludePath);//排除不需要拦截的路径
        WebMvcConfigurer.super.addInterceptors(registry);

    }
}
  1. 登陆函数
  • 应用方式:
    • 跟着代码里的注释改。
  • 作用:
    • 在用户账号密码验证正确时,返回一个信号并携带token。
	@GetMapping
    public String loginCheck(String account, String password)
    {
    	//用户传入了账号密码
        System.out.println("账号:" + account);
        System.out.println("密码:" + password);
        //在if括号里写你的方法,比如if (account.equals("admin") && password.equals("admin")),或者更复杂的数据库逻辑
        if (账号密码是正确的)
        {
        	//下面这一句用于生成token
            String token = new TokenGenerate().generateToken(account);
            //如果验证通过就返回token
            return token;
        } else
        {
        	//如果失败就没有token了,返回null。
            return null;
        }
    }

前端

  1. axios
  • 应用方式
    • 原封不动复制到前端的某个地方
    • 然后修改一处,注意到我下面有一句,url !== '/registerCheck' &&url !== '/loginCheck' && url !== '/logout',跟上面一样的意思,当用户访问这几个网址的时候不用验证token。即在安全性上排除了这几个网址。我排除了三个,注册,登录,注销。改成你自己的url。就是你后端的注册登陆网址是什么,这里就写什么。
import axios from 'axios'
// 创建axios实例
const instance = axios.create({
    baseURL: 'http://localhost:8082', // 设置后端接口地址(使用nginx代理了)
    timeout: 10000, // 设置请求超时时间
});
// 请求拦截器,在请求头中添加token
instance.interceptors.request.use(config =>
{
    // 获取请求的URL
    const {url} = config;
    // 如果请求的URL不是login、logout或register,则在请求头中添加token
    if (url !== '/initUser' &&url !== 'check' && url !== '/logout' && url !== '/register')
    {
        // 从localStorage中获取token
        const token = localStorage.getItem('token');
        if (token)
        {
            config.headers.Authorization = `Bearer ${token}`;
            config.headers.token = token;
            return config;
        }
    }
    return config;
}, error =>
{
    console.log(error)
    return Promise.reject(error);
});
// 响应拦截器
instance.interceptors.response.use(response =>
{
    // 在这里处理响应结果
    return response;
}, error =>
{
    // 在这里处理响应错误
    return Promise.reject(error);
});
// 导出axios实例
export default instance;

2.前端的注册登陆方法
我举个登陆的例子

//先import刚刚那个文件。"./filterAxios"需要改成你自己存放的位置
import axios from "./filterAxios"
//然后去请求后端的loginCheck
const res = axios.get("/loginCheck", {param:{{account: "xxx", password: "xxx"}}})
if(res.data === null)
	//说明登陆失败了,你可以弹个弹框啥的告诉用户
else
{
	//把token存到本地
	sessionStorage.setItem("token",res.data);
}

  1. 路由卫士
  • 应用方式
    • 原封不动复制到前端的router文件夹的index.js中,复制到最后一行(export default router)的上面!!!。不是最后一行的下面,是上面。
    • 然后修改一处if (to.name === "login" || to.name === "register"),改成你的注册和登陆页面的前端的url。
  • 作用
    • 这个是先确定你要去哪个网页,如果是注册/登陆网页,那你就直接去。如果不是注册登陆,那就得先验证你有没有token。有你才能去,否则你还是会被送回登陆页面。
router.beforeEach((to, from, next) =>
{
    console.log("taken:router:", sessionStorage.getItem('token'))
    if (!sessionStorage.getItem('token'))
    {
        if (to.name === "login" || to.name === "register")
            next();
        else
        {
            router.push('login')
        }
    }
    else
    {
        next();
    }
});

注意点

如果加了token以后前端出现了跨域错误“network error”,也有可能是后端出的错。
一度因为后端没报错,而前端报了错在前端苦苦寻找错误。
后端这家伙不爆错是因为在拦截器那里直接拦截了,直接将response设置为错误,返回给前端。