使用 Redis 用户登录,整合JWT

1、使用 Redis 用户登录分析

2、使用工具类生成验证码

  • 将随机生成的验证码存放到 redis
    • 使用 for循环随机生成,使用StringBuilder保存4个字符串
  • 使用 HttpServletResponse 将图片响应到浏览器

VerifyCodeController

package com.czxy.controller;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author 庭前云落
 * @Date 2020/3/26 8:58
 * @description
 */
@RestController
@RequestMapping("/verifycode")
public class VerifyCodeController {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @GetMapping
    public void verifyCode(String username, HttpServletResponse response) throws IOException {
        //字体只显示大写,去掉1,0,i,o几个容易混淆的字符
        String VERIFY_CODES = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";

        int IMG_WIDTH = 72;
        int IMG_HEIGTH = 27;

        Random random = new Random();

        //创建图片
        BufferedImage image = new BufferedImage(IMG_WIDTH, IMG_HEIGTH, BufferedImage.TYPE_INT_RGB);

        //画板
        Graphics g = image.getGraphics();

        //填充背景
        g.setColor(Color.WHITE);
        g.fillRect(1, 1, IMG_WIDTH - 2, IMG_HEIGTH - 2);

        //设置字体
        g.setFont(new Font("楷体", Font.BOLD, 25));

        // 使用 StringBuilder 保存字符串
        StringBuilder stringBuilder = new StringBuilder();

        //绘制4个字符
        for (int i = 1; i <= 4; i++) {
            //随机颜色
            g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));

            //随机生成4个字符
            int len = random.nextInt(VERIFY_CODES.length());
            String str = VERIFY_CODES.substring(len, len + 1);

            //存放字符串
            stringBuilder.append(str);
            g.drawString(str, IMG_WIDTH / 6 * i, 22);
        }

        //将验证码存放到redis
        stringRedisTemplate.opsForValue().set("login" + username, stringBuilder.toString(), 5, TimeUnit.MINUTES);

        //生成随机干扰线
        for (int i = 0; i < 30; i++) {
            g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
            int x = random.nextInt(IMG_WIDTH - 1);
            int y = random.nextInt(IMG_HEIGTH - 1);
            int x1 = random.nextInt(12) + 1;
            int y1 = random.nextInt(6) + 1;
            g.drawLine(x, y, x - x1, y - y1);
            //响应到浏览器
            ImageIO.write(image, "jpeg", response.getOutputStream());

        }
    }
}

3、用户登录整合 JWT

3.1、生产 Token

  • 用户登录成功,生成token,并将token响应到浏览器。

  • 1、yml文件里,添加jwt配置信息

    sc:
      jwt:
        secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
        pubKeyPath: D:/rsa/rsa.pub # 公钥地址
        priKeyPath: D:/rsa/rsa.pri # 私钥地址
        expire: 360 # 过期时间,单位分钟
    
  • 2、创建 JwtProperties文件,用于加载 sc.jwt 配置信息

package com.czxy.config;

import com.czxy.utils.RasUtils;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.File;
import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * @author 庭前云落
 * @Date 2020/3/29 13:29
 * @description
 */
@Data
@ConfigurationProperties(prefix = "sc.jwt")
@Component
public class JwtProperties {
    private String secret; // 密钥

    private String pubKeyPath;// 公钥

    private String priKeyPath;// 私钥

    private int expire;// token过期时间

    private PublicKey publicKey; // 公钥

    private PrivateKey privateKey; // 私钥

    private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class);

    @PostConstruct
    public void init() {
        try {
            File pubFile = new File(this.pubKeyPath);
            File priFile = new File(this.priKeyPath);
            if (!pubFile.exists() || !priFile.exists()) {
                RasUtils.generateKey(this.pubKeyPath, this.priKeyPath, this.secret);
            }
            this.publicKey = RasUtils.getPublicKey(this.pubKeyPath);
            this.privateKey = RasUtils.getPrivateKey(this.priKeyPath);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  • 3、注入 JwtProperties,并使用 JwtUtils 生成 token

Controller

package com.czxy.controller;

import com.czxy.config.JwtProperties;
import com.czxy.pojo.User;
import com.czxy.service.AuthService;
import com.czxy.utils.JwtUtils;
import com.czxy.vo.BaseResult;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author 庭前云落
 * @Date 2020/3/27 8:44
 * @description
 */
@RestController
@RequestMapping("/auth")
public class AuthController {

    @Resource
    private AuthService authService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private JwtProperties jwtProperties;

    @PostMapping("/login")
    public BaseResult login(@RequestBody User user) throws Exception {

        //验证码校验
        String code = stringRedisTemplate.opsForValue().get("login" + user.getUsername());
        if(code==null){
          return  BaseResult.error("验证码无效");
        }
        if(!code.equalsIgnoreCase(user.getCode())){
            return BaseResult.error("验证码错误");
        }
        User login = authService.login(user);
        if(login!=null){
          //生成Token
          String token = JwtUtils.generateToken(login,jwtProperties.getExpire(),jwtProperties.getPrivateKey());
         //登录成功删除验证码
         stringRedisTemplate.delete("login"+user.getUsername());
         return BaseResult.ok("登录成功").append("login",login).append("token",token);
        }else{
            return BaseResult.error("用户名或密码不匹配");
        }
    }
}

AuthService

package com.czxy.service;

import com.czxy.feign.UserFeign;
import com.czxy.pojo.User;
import com.czxy.utils.BCrypt;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author 庭前云落
 * @Date 2020/3/27 8:41
 * @description
 */
@Service
public class AuthService {

    @Resource
    private UserFeign userFeign;

    public User login(User user){
        User result = userFeign.findByUsername(user);
        if(result==null){
        return null;
        }
        //校验密码
        boolean checkpw = BCrypt.checkpw(user.getPassword(), result.getPassword());
        if(checkpw){
           return result;
       }
       return null;
    }
}

3.2、Token 校验

  • 在项目网关完成 token 的校验

  • 1、网关 yml 文件 添加 jwt 配置,同时配置白名单

    白名单:不需要拦截的资源在yml中进行配置,在过滤器直接放行

    #新增
    sc:
      jwt:
        secret: sc@Login(Auth}*^31)&czxy% # 登录校验的密钥
        pubKeyPath: D:/rsa/rsa.pub # 公钥地址
        priKeyPath: D:/rsa/rsa.pri # 私钥地址
        expire: 360 # 过期时间,单位分钟
      filter:
        allowPaths:
          - /checkusername
          - /checkmobile
          - /sms
          - /register
          - /login
          - /verifycode
          - /categorys
          - /news
          - /brands
          - /specifications
          - /search
          - /goods
          - /comments
    
  • 2、将 auth_service 里的 JwtProperties 复制到网关配置,同时创建白名单配置文件(存放允许放行的路径)

FilterProperties

package com.czxy.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.List;

/**
 * @author 庭前云落
 * @Date 2020/3/29 15:17
 * @description
 */
@Data
@ConfigurationProperties(prefix = "sc.filter")
@Component
public class FilterProperties {
    //允许访问路径集合
    private List<String> allowPaths;
}

  • 3、编写网关过滤器,对所有路径进行拦截

package com.czxy.filter;

import com.czxy.config.FilterProperties;
import com.czxy.config.JwtProperties;
import com.czxy.pojo.User;
import com.czxy.utils.JwtUtils;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

/**
 * @author 庭前云落
 * @Date 2020/3/29 14:51
 * @description
 */
@Component
//2.1 加载JWT配置类
@EnableConfigurationProperties({JwtProperties.class, FilterProperties.class})
public class LoginFilter extends ZuulFilter {

    //2.2 注入jwt配置类实例
    @Resource
    private JwtProperties jwtProperties;

    @Resource
    private FilterProperties filterProperties;

    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 5;
    }

    @Override
    public boolean shouldFilter() { // 3 当前过滤器是否执行,true执行,false不执行
        //1、获得工具类(请求上下文)
        RequestContext currentContext = RequestContext.getCurrentContext();
        //2、获得请求对象
        HttpServletRequest request = currentContext.getRequest();
        //3、获得请求路径 v3/authservice/login
        String requestURI = request.getRequestURI();
        //3.2 如果路径是 /authservice/login 当前拦截器不执行
        for (String path : filterProperties.getAllowPaths()) {
            //判断包含
            if (requestURI.contains(path)) {
                return false;
            }
        }
        //执行run()方法
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        //1、获得token
        //1.1 获得上下文
        RequestContext currentContext = RequestContext.getCurrentContext();
        //1.2 获得request对象
        HttpServletRequest request = currentContext.getRequest();
        //1.3 获得指定请求头的值
        String token = request.getHeader("Authorization");

        //2 校验 token --- 使用 JWT工具类进行解析
        //2.3 使用工具类 通过公钥获得对应信息
        try {
            JwtUtils.getObjectFromToken(token, jwtProperties.getPublicKey(), User.class);
        } catch (Exception e) {
            //2.4 如果有异常 -- 没有登录(没有权限)
            currentContext.addOriginResponseHeader("content-type", "text/html;charset=UTF-8");
            currentContext.addZuulResponseHeader("content-type", "text/html;charset=UTF-8");
            currentContext.setResponseStatusCode(403);
            currentContext.setResponseBody("token失效或无效");
            currentContext.setSendZuulResponse(false);
        }
        //如果没有异常 放行
        return null;
    }
}

4、前端

绑定事件,切换验证码

4.1、使用 Token

  • 1、登录成功后保存token(login.vue)
<template>
<!-- 省略 -->
</template>

<script>
export default {
  data() {
    return {
      imgSrc: "",
      errorMsg: "",
      user: {
        username: "",
        password: ""
      }
    };
  },
  methods: {
    changeVerifyCode() {
      //判断必须要输入用户名
      if (this.user.username) {
        //切换图片路径
        this.imgSrc = `http://localhost:10010/v3/cgwebservice/verifycode?t=${new Date()}&username=${
          this.user.username
        }`;
      } else {
        this.errorMsg = "用户名不能为空";
      }
    },
    async login() {
      let { data } = await this.$request.login(this.user);
      if (data.code == 1) {
        //成功
        sessionStorage.setItem("user", JSON.stringify(data.other.login));
        //保存token
        sessionStorage.setItem("token", data.other.token);
        //this.$router.push("/"); 为了首页的asyncData数据获取,选择重定向
        location.href='/'
      } else {
        this.erroMsg = data.message;
      }
    }
  },
  watch: {
    user: {
      handler(v) {
        if (v) {
          //如果user数据发生改变,修改提示信息
          this.errorMsg = "";
        }
      },
      deep: true
    }
  }
};
</script>
  • 2、请求自动携带 token,即将 token 添加到请求头(plugins/api.js)

  • 同时添加处理 403 异常的方法

    var axios = null
    export default ({ $axios }, inject) => {
        //参考 https://axios.nuxtjs.org/helpers
        let token = sessionStorage.getItem('token')
        if (token) {
            $axios.setToken(token)
        }
    
        //处理响应异常
        $axios.onError(error => {
            //token失效 服务器响应403
            if (error.response.status === 403) {
                console.error(error.response.data)
                redirect('/login')
            }
        })
    
    
        //3) 保存内置的axios
        axios = $axios
    
        //4) 将自定义函数交于nuxt
        // 使用方式1:在vue中,this.$request.xxx()
        // 使用方式2:在nuxt的asyncData中,content.app.$request.xxx()
    
        inject('request', request)
    }
    
  • 3、修改 nuxt.conf.js,将 api.js 插件模式 改成"client"

  • client:仅前端客户端有效,默认:客户端服务端都执行

    • 否则抛异常 sessionStorage is not defined

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值