目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

JWT登录解决方案

session共享、分布式缓存

  • 单机tomcat应用登录检验
    • sesssion保存在浏览器和应用服务器会话之间
    • 用户登录成功,服务端会保存一个session,当然客户端有一个sessionId
    • 客户端会把sessionId保存在cookie中,每次请求都会携带这个sessionId
  • 分布式应用中session共享
    • 真实的应用不可能单节点部署,所以就有个多节点登录session共享的问题需要解决
    • tomcat支持session共享,但是有广播风暴;用户量大的时候,占用资源就严重,不推荐
    • 使用redis存储token:
      • 服务端使用UUID生成随机64位或者128位token,放入redis中,然后返回给客户端并存储在cookie中
      • 用户每次访问都携带此token,服务端去redis中校验是否有此用户即可

image-20200417222658225

分布式应用下登录检验解决方案 JWT

  • 什么是JWT

    • JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名
    • 简单来说: 就是通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息
  • 优点

    • 生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库
    • 存储在客户端,不占用服务端的内存资源
  • 缺点

    • token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如用户权限,密码等
    • 如果没有服务端存储,则不能做登录失效处理,除非服务端改秘钥
      {
                id:888,
                name:'小D',
                expire:10000
            }
      
            funtion 加密(object, appsecret){
                xxxx
                return base64( token);
            }

            function 解密(token ,appsecret){

                xxxx
                //成功返回true,失败返回false
            }
  • JWT格式组成 头部、负载、签名

    • header+payload+signature
      • 头部:主要是描述签名算法
      • 负载:主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户
      • 签名:主要是把前面两部分进行加密,防止别人拿到token进行base解密后篡改token
  • 关于jwt客户端存储

    • 可以存储在cookie,localstorage和sessionStorage里面

JsonWebToken工具类封装

package net.xdclass.online_xdclass.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import net.xdclass.online_xdclass.domain.User;

import java.util.Date;

/**
 * Jwt工具类
 * 注意点:
 * 1、生成的token, 是可以通过base64进行解密出明文信息
 * 2、base64进行解密出明文信息,修改再进行编码,则会解密失败
 * 3、无法作废已颁布的token,除非改秘钥
 */
public class JWTUtils {


    /**
     * 过期时间,一周
     */
    private  static final long EXPIRE = 60000 * 60 * 24 * 7;


    /**
     * 加密秘钥
     */
    private  static final String SECRET = "xdclass.net168";


    /**
     * 令牌前缀
     */
    private  static final String TOKEN_PREFIX = "xdclass";


    /**
     * subject
     */
    private  static final String SUBJECT = "xdclass";


    /**
     * 根据用户信息,生成令牌
     * @param user
     * @return
     */
    public static String geneJsonWebToken(User user){

        String token = Jwts.builder().setSubject(SUBJECT)
                .claim("head_img",user.getHeadImg())
                .claim("id",user.getId())
                .claim("name",user.getName())
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                .signWith(SignatureAlgorithm.HS256,SECRET).compact();

        token = TOKEN_PREFIX + token;

        return token;
    }


    /**
     * 校验token的方法
     * @param token
     * @return
     */
    public static Claims checkJWT(String token){

        try{

            final  Claims claims = Jwts.parser().setSigningKey(SECRET)
                    .parseClaimsJws(token.replace(TOKEN_PREFIX,"")).getBody();

            return claims;

        }catch (Exception e){
            //解密失败返回Null
            return null;
        }

    }

}

测试

src/test/java/net/xdclass/online_xdclass/OnlineXdclassApplicationTests.java

package net.xdclass.online_xdclass;

import io.jsonwebtoken.Claims;
import net.xdclass.online_xdclass.model.entity.User;
import net.xdclass.online_xdclass.utils.JWTUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.util.Assert;

@SpringBootTest
class OnlineXdclassApplicationTests {

	@Test
	public void testGeneJwt(){


		User user = new User();
		user.setId(66);
		user.setName("二当家小D");
		user.setHeadImg("png");

		String token = JWTUtils.geneJsonWebToken(user);

		System.out.println(token);

//		try {
//			Thread.sleep(4000L);
//		} catch (InterruptedException e) {
//			e.printStackTrace();
//		}

		Claims claims = JWTUtils.checkJWT(token);


		System.out.println(claims.get("name"));

	}




}

登录模块整合Json Web Token

Model

在model下增加request包,并创建登录对象 LoginRequest

package net.xdclass.online_xdclass.model.request;

/**
 * 登录 request
 */
public class LoginRequest {

    private String phone;

    private String pwd;

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}

Controller

package net.xdclass.online_xdclass.controller;

import net.xdclass.online_xdclass.model.request.LoginRequest;
import net.xdclass.online_xdclass.service.UserService;
import net.xdclass.online_xdclass.utils.JsonData;
import org.springframework.beans.factory.annotation.Autowired;
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 java.util.Map;

@RestController
@RequestMapping("api/v1/pri/user")
public class UserController {



    @Autowired
    private UserService userService;

    /**
     * 注册接口
     * @param userInfo
     * @return
     */
    @PostMapping("register")
    public JsonData register(@RequestBody Map<String,String> userInfo ){

        int rows = userService.save(userInfo);

        return rows == 1 ? JsonData.buildSuccess(): JsonData.buildError("注册失败,请重试");

    }


    /**
     * 登录接口
     * @param loginRequest
     * @return
     */
    @PostMapping("login")
    public JsonData login(@RequestBody LoginRequest loginRequest){

        String token = userService.findByPhoneAndPwd(loginRequest.getPhone(), loginRequest.getPwd());

        return token == null ?JsonData.buildError("登录失败,账号密码错误"): JsonData.buildSuccess(token);

    }



}

Service

interface

package net.xdclass.online_xdclass.service;

import java.util.Map;

public interface UserService {

    /**
     * 新增用户
     * @param userInfo
     * @return
     */
    int save(Map<String, String> userInfo);


    String findByPhoneAndPwd(String phone, String pwd);
}

impl

package net.xdclass.online_xdclass.service.impl;

import net.xdclass.online_xdclass.model.entity.User;
import net.xdclass.online_xdclass.mapper.UserMapper;
import net.xdclass.online_xdclass.service.UserService;
import net.xdclass.online_xdclass.utils.CommonUtils;
import net.xdclass.online_xdclass.utils.JWTUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Map;
import java.util.Random;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;


    @Override
    public int save(Map<String, String> userInfo) {

        User user = parseToUser(userInfo);
        if( user != null){
           return userMapper.save(user);
        }else {
            return -1;
        }

    }


    @Override
    public String findByPhoneAndPwd(String phone, String pwd) {

        User user = userMapper.findByPhoneAndPwd(phone, CommonUtils.MD5(pwd));

        if(user == null){
            return null;

        }else {
            String token = JWTUtils.geneJsonWebToken(user);
            return token;
        }

    }

    /**
     * 解析 user 对象
     * @param userInfo
     * @return
     */
    private User parseToUser(Map<String,String> userInfo) {

        if(userInfo.containsKey("phone") && userInfo.containsKey("pwd") && userInfo.containsKey("name")){
            User user = new User();
            user.setName(userInfo.get("name"));
            user.setHeadImg(getRandomImg());
            user.setCreateTime(new Date());
            user.setPhone(userInfo.get("phone"));
            String pwd = userInfo.get("pwd");
            //MD5加密
            user.setPwd(CommonUtils.MD5(pwd));

            return user;
        }else {
            return null;
        }

    }

    /**
     * 放在CDN上的随机头像
     */
    private static final String [] headImg = {
            "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/default/head_img/12.jpeg",
            "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/default/head_img/11.jpeg",
            "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/default/head_img/13.jpeg",
            "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/default/head_img/14.jpeg",
            "https://xd-video-pc-img.oss-cn-beijing.aliyuncs.com/xdclass_pro/default/head_img/15.jpeg"
    };

    private String getRandomImg(){
        int size =  headImg.length;
        Random random = new Random();
        int index = random.nextInt(size);
        return headImg[index];
    }

}

Mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="net.xdclass.online_xdclass.mapper.UserMapper">


    <insert id="save" parameterType="User">

        INSERT  INTO user (name, pwd, head_img, phone , create_time)
        values (#{name,jdbcType=VARCHAR}, #{pwd,jdbcType=VARCHAR}, #{headImg,jdbcType=VARCHAR},
        #{phone,jdbcType=VARCHAR},#{createTime,jdbcType=TIMESTAMP})

    </insert>



    <!--根据手机号查询用户信息-->
    <select id="findByPhone" resultType="User">

        select  * from user where phone =#{phone}

    </select>


    <!--根据手机号和密码找用户-->
    <select id="findByPhoneAndPwd" resultType="User">

        select  * from user where phone =#{phone} and pwd = #{pwd}


    </select>
  

</mapper>

测试

localhost:8081/api/v1/pri/user/login

POST:Body->raw->JSON


{
    "phone":"12345678",
    "pwd":"111111"
}


***Response***
{
    "code": 0,
    "data": "xdclasseyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4ZGNsYXNzIiwiaGVhZF9pbWciOiJodHRwczovL3hkLXZpZGVvLXBjLWltZy5vc3MtY24tYmVpamluZy5hbGl5dW5jcy5jb20veGRjbGFzc19wcm8vZGVmYXVsdC9oZWFkX2ltZy8xNS5qcGVnIiwiaWQiOjgsIm5hbWUiOiJhbGljZSIsImlhdCI6MTYxMDUwMzM5NSwiZXhwIjoxNjExMTA4MTk1fQ.Hru8k9bwXd32t2iQX-6br_30M-HwiYKVerfChv_Recw",
    "msg": null
}

作者:Soulboy