使用JWT实现登录认证

一、介绍

1.1、Session、Cookie、Token区别

session:存储再服务端,无法引用与分布式场景,并且需要占用服务端的资源
cookie:存储再客户端,适用于分布式场景,但是存在安全问题,不支持垮域访问
token:存储在localstorage中,更加灵活

1.2、如何实现登录认证

  1. 用户使用账号密码登录成功
  2. 通过JWT生成一串字符串作为Token,返回给前端
  3. 前端每次请求的时候都在请求头中携带上这个Token
  4. 后端每次都使用JWT对该Token进行校验,还原出一些用户信息,以此来判断用户是否登录

1.3、JWT组成

1.3.1、样例

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjpbXSwiaWF0IjoxNjg0ODIzNzIzLCJleHAiOjE2ODQ4NTc1OTksImF1ZCI6IiIsImlzcyI6ImFxaSIsInN1YiI6IiJ9.W306xll5X2lHWL_B0AUZs7nf9e7Zn5QvgoasnviBeaQ

1.3.2、组成

JWT生成的字符串由三个部分组成

第一部分(header:JWT头,该部分只用Base64编码,未加密)
头部由2个属性组成
1、typ:令牌类型,固定设置为JWT
2、alg:加密算法,默认为HS256

第二部分(Payload:有效载荷,该部分只用Base64编码,未加密,避免存放隐私信息)
就是JWT的主体部分
1、issuer:发行者
2、IssuedAt:发布时间
3、expiration:到期时间
4、subject:主题
5、Not Before:生效时间
6、JWT ID:用于标识该 JWT
7、audience:用户

第三部分(Signature:签名,该部分是安全的,无法被解密)
该部分可以设置secret(俗称:加盐)的方式增加该部分的破解难度

1.4、优缺点

1.4.1、优点
  1. 是json格式,跨语言的
  2. 可以利用Payload存储一些非敏感的信息
  3. 不需要存储在服务端,可以用于分布式场景
  4. 一般存储在localStorage中,不存在于Cookie中,避免了一些安全性问题
  5. 便于实现单点登录功能
1.4.2、缺点
  1. 一旦生成就无法修改过期时间,需要搭配缓存来实现过期或者退出效果
  2. 同样Token过期无法进行续签
  3. 不可以在JWT中存储敏感信息

二、使用

2.1、引入POM依赖

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

2.2、编写工具类

package com.xx.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;

/**
 * @author aqi
 * DateTime: 2020/11/9 3:27 下午
 * Description: JWT工具类
 */
public class JwtUtils {

    /**
     * 设置Token过期时间
     */
    public static final long EXPIRE = 1000 * 60 * 60 * 24;

    /**
     * 秘钥
     */
    public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";

    /**
     * 生成Token字符串(Token组成:头+荷载+签名)
     * @param id 用户id
     * @param nickname 用户名称
     * @return Token字符串
     */
    public static String getJwtToken(String id, String nickname){

        return Jwts.builder()
                // 设置JWT的头信息
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", "HS256")

                // 设置Token过期时间
                // 主题
                .setSubject("guli-user")
                // 签发时间
                .setIssuedAt(new Date())
                // 过期时间
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))

                // 设置Token主体部分
                .claim("id", id)
                .claim("nickname", nickname)
                // 签名算法,秘钥
                .signWith(SignatureAlgorithm.HS256, APP_SECRET)
                .compact();
    }

    /**
     * 判断token是否存在与有效
     * @param jwtToken token
     * @return 判断token是否存在与有效
     */
    public static boolean checkToken(String jwtToken) {
        if(StringUtils.isEmpty(jwtToken)) {
            return false;
        }
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 判断token是否存在与有效
     * @param request request
     * @return 判断token是否存在与有效
     */
    public static boolean checkToken(HttpServletRequest request) {
        try {
            String jwtToken = request.getHeader("token");
            if(StringUtils.isEmpty(jwtToken)) {
                return false;
            }
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据token获取会员id
     * @param request request
     * @return 会员id
     */
    public static String getMemberIdByJwtToken(HttpServletRequest request) {
        String jwtToken = request.getHeader("token");
        if(StringUtils.isEmpty(jwtToken)) {
            return "";
        }
        Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);
        Claims claims = claimsJws.getBody();
        return (String)claims.get("id");
    }

}


2.3、登录代码

public String login(UcenterMember member) {
        //获取登录手机号和密码
        String mobile = member.getMobile();
        String password = member.getPassword();

        //手机号和密码非空判断
        if(StringUtils.isEmpty(mobile) || StringUtils.isEmpty(password)) {
            throw new MyException(20001,"登录失败");
        }

        //判断手机号是否正确
        QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>();
        wrapper.eq("mobile",mobile);
        UcenterMember mobileMember = baseMapper.selectOne(wrapper);
        //判断查询对象是否为空
        if(mobileMember == null) {//没有这个手机号
            throw new MyException(ResultCode.CODE_20001);
        }

        //判断密码
        if(!DigestUtils.md5DigestAsHex(password.getBytes()).equals(mobileMember.getPassword())) {
            throw new MyException(ResultCode.CODE_20001);
        }

        //判断用户是否禁用
        if(mobileMember.getIsDisabled()) {
            throw new MyException(ResultCode.CODE_20001);
        }

        //登录成功,生成token字符串
        return JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname());
    }

2.4、使用JWT做拦截

/**
     * 根据token获取用户信息
     */
    @GetMapping("/getMemberInfo")
    public R getMemberInfo(HttpServletRequest request) {
        //调用jwt工具类的方法。根据request对象获取头信息,返回用户id
        String memberId = JwtUtils.getMemberIdByJwtToken(request);
        if (memberId.isEmpty()) {
            return R.error().data("userInfo", "登陆失效,请重新登录");
        }
        //查询数据库根据用户id获取用户信息
        UcenterMember member = memberService.getById(memberId);
        return R.success().data("userInfo",member);
    }

三、续签和过期问题

3.1、思路

将生成的JWT字符串不返回给前端,而是自己生成一个UUID,将该UUID返回给前端,再使用该UUID存储

你可能感兴趣的:(登录认证,java)