前端Vue,后端Spring Boot + JWT,实现登录拦截功能

作为一名只会点点点的苦逼测试,肯定都是向往自动化的,利用闲暇时间,自己写了一个简单的接口测试平台,但是既然是一个web网站,必不可少的是登录,还有登录拦截的东西,今天简单总结了下一个比较简单的方案

整体思路

首先在前端,登录页面,用户登录成功后,后端负责用uid生成对应的token,并返回给前端,前端将token存储到localStorage中,在axios中拦截request和response,拦截request,是将localStorage中的token塞到Header中的Authorization(写在这里,可以避免CROS跨域的问题)中,然后后端拦截器从Header中取出token,做校验,校验通过则放行,不通过则返回未登录的错误信息,前端拦截response。如果是未登录的错误码,则做路由跳转

准备

后端,导包:

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

1、后端(Spring Boot)

① 首先添加一个JWT实体,因为是私人项目,此处使用lombok,所以不需要写get/set,团队项目不建议使用lombok
package platform.app.entity;

import lombok.Data;
@Data
public class JWTInfo {
    private String uid;

    public JWTInfo(String uid) {
        this.uid = uid;
    }
}
②添加JWTTokenUtils
package platform.app.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
import platform.app.entity.JWTInfo;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;

/**
 * @description: JWTTokne验证
 * @author: hmj
 * @create: 2020-04-13 10:50
 **/
public class JwtTokenUtils {
//    TOKEN 7天过期
    public static final int EXPIRE = 7;
    public static final int FLAG = "uid";
    private static Key getKeyInstance(){
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        byte[] bytes = DatatypeConverter.parseBase64Binary("autoTest-user");
        return new SecretKeySpec(bytes,signatureAlgorithm.getJcaName());
    }

    /**
     * 生成token的方法
     */
    public static String generatorToken(JWTInfo jwtInfo){
        return Jwts.builder().claim(FLAG ,jwtInfo.getUid())
                .setExpiration(DateTime.now().plusDays(EXPIRE).toDate())
                .signWith(SignatureAlgorithm.HS256,getKeyInstance()).compact();
    }

    /**
     * 根据token获取token中的信息
     */
    public static JWTInfo getTokenInfo(String token){
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return new JWTInfo(claims.get(FLAG).toString());
    }
}
③ 添加拦截器,如果拦截到未登录或者其他异常情况,返回错误信息,如果已登录则放行
package platform.app.config;

import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.springframework.beans.factory.annotation.Autowired;
import platform.app.entity.JWTInfo;
import platform.app.entity.ResponseBean;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import platform.app.utils.JwtTokenUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

public class LoginHandlerInterceptor implements HandlerInterceptor {
    private static Logger logger = LoggerFactory.getLogger(LoginHandlerInterceptor.class);


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {


    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//        放行Vue传过来一个OPTIONS请求
        if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) {
//            logger.info("OPTIONS请求,放行");
            return true;
        }
        String token = request.getHeader("Authorization");
//        不等于空
        if (StringUtils.isNotBlank(token)){
            try {
                JWTInfo jwtInfo = JwtTokenUtils.getTokenInfo(token);
                logger.info("id:{},正在登录",jwtInfo.getUid());
            }catch (SignatureException e){
//                未登录
                ResponseBean responseBean = new ResponseBean();
                responseBean.setCode(40009);
                responseBean.setMsg("未登录");
                returnJson(response,JSON.toJSONString(responseBean));
            }catch (ExpiredJwtException e){
                ResponseBean responseBean = new ResponseBean();
                responseBean.setCode(40009);
                responseBean.setMsg("登录信息已过期,请重新登录!");
                returnJson(response,JSON.toJSONString(responseBean));
            }
            return true;
        }
        ResponseBean responseBean = new ResponseBean();
        responseBean.setCode(40004);
        responseBean.setMsg("未登录");
        returnJson(response,JSON.toJSONString(responseBean));
        return false;
    }


    /*
    *  出现错误或者未登录,响应错误信息
    **/
    private void returnJson(HttpServletResponse response, String json) throws Exception {
        PrintWriter writer = null;
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html; charset=utf-8");

        try {
            writer = response.getWriter();
            writer.print(json);

        } catch (IOException e) {
            logger.error("response error", e);
        } finally {
            if (writer != null)
                writer.close();
        }
    }
}

④ 配置拦截器,将之前写好的拦截器。配置在addInterceptors方法中,并添加拦截路径和排除的拦截路径
package platform.app.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginHandlerInterceptor())
                .addPathPatterns("/autoTest/platform/*")
                .excludePathPatterns("/autoTest/platform/login");
    }

⑤ login控制层,登录成功,将token返回,可以在响应头,响应体,或者其他方式返回,这里直接在响应体中返回了
    @RequestMapping("/login")
    public ResponseBean login(@RequestBody User user){
        logger.info("登录信息:username={} , password={}",user.getUsername(),user.getPassword());
        User resultUser = userService.getUserLogin(user);
        ResponseBean responseBean = new ResponseBean();
        if ( resultUser != null && CommonUtils.verifyPassword(user.getPassword(),resultUser.getPassword())){
            logger.info("登录成功,登录信息:" + resultUser.toString());
            String token = JwtTokenUtils.generatorToken(new JWTInfo(resultUser.getId()+""));
            responseBean.setMsg("登录成功");
            responseBean.setCode(10000);
            responseBean.setExtend(token);
            return responseBean;
        }
        logger.info("登录失败");
        responseBean.setMsg("登陆失败,请检查用户名和密码");
        responseBean.setCode(9999);
        return responseBean;
    }

2、前端(Vue)

① 登录页,将返回的token存储到localStorage中
login() {
      this.$refs.loginFormRef.validate(async valid => {
        if (valid) {
          const result = await this.$http.post("/login", this.form);
          // console.log(result);
          const data = result.data;
          if (data.code == 10000) {
            this.$message.success(data.msg);
            // console.log("token : " + data.data.id);
            window.localStorage.setItem("token", data.extend);
            window.localStorage.setItem("customerName", data.data.customerName);
            this.$router.push("/home");
          } else {
            this.$message.error(data.msg);
          }
        } else {
        }
      });
    }
② main.js中,请求、响应拦截
axios.interceptors.request.use(request => {
  /*判断token存在   登录拦截*/
  if(window.localStorage.getItem("token")){
    /*设置统一的header*/
    request.headers.Authorization  = window.localStorage.getItem("token");
  }
  return request;
});
// 响应拦截
axios.interceptors.response.use((response) =>{
  if(response.data.code !== 10000){
    if(response.data.code === 40009){
      // 跳转到登录页面
      vm.$router.push('/login');
    }
  }
  return response;
})

总结:

由于时间紧迫,不想在登录上面浪费太多时间,很多功能化繁为简,只写了个临时版本,等其他功能弄的差不多,再对登录拦截进行优化

坑:如果你的项目中已经导入了spring-security-crypto,那么在导入io.jsonwebtoken时,就要注意了,这两个会有冲突,导致Spring找不到默认的jar,根据实际情况选择性排除即可

<dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-crypto</artifactId>
            <version>5.2.1.RELEASE</version>
            <exclusions>
                <exclusion>
                    <artifactId>jjwt</artifactId>
                    <groupId>io.jsonwebtoken</groupId>
                </exclusion>
            </exclusions>
</dependency>
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值