spring boot jwt完整过程

首先源码https://gitee.com/mobile191llx/spring-boot-jwt.git

接着项目目录结构

spring boot jwt完整过程_第1张图片

项目开始

1.新建项目名称为demo、选择maven什么都不要勾选

2.依赖pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.10.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    <!--  阿里镜像  -->
    <repositories>
        <repository>
            <id>aliyun</id>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>aliyun-plugin</id>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>

3.新建数据库

create database store; # 新建数据库
use store; # 选中数据库
create table t_user(   # 新建数据表
	uid varchar(50) not null primary key,
    username varchar(20) not null unique,
    password char(50) not null,
    salt varchar(50),
    phone varchar(11) unique,
    email varchar(30) unique not null,
    gender int(1),
    avatar varchar(50),
    is_delete int(1) not null,
    created_user varchar(20) not null,
    created_time datetime not null,
    modified_user varchar(20) not null,
    modified_time datetime not null
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

4.配置项目application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver   # 驱动
    username: root     #数据库登录名
    password: 123456   #数据库密码
	# 数据库名为store,字符集为utf-8,时区为北京
    url: jdbc:mysql://localhost:3306/store?characterEncoding=utf-8&serverTimezone=GMT%2B8
mybatis:
  mapper-locations: classpath:mapper/*.xml  # mapper.xml扫描路径

5.主启动类、在java路径下创建com.example.demo目录、在demo目录下创建App类

package com.example.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication // 注册为springboot启动类
@MapperScan("com.example.demo.mapper") // 扫描mapper路径也就是dao层
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

6.在demo目录下新建mapper目录,在demo/mapper下新建UserMapper类

package com.example.demo.mapper;

import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Param;

import java.util.Date;

public interface UserMapper {
    /**
     * 插入用户的数据
     *
     * @param user 用户的数据
     * @return 返回Integer
     */
    Integer insert(User user);

    /**
     * 根据用户名来查询用户的数据
     *
     * @param username 用户名
     * @return 返回User或者null
     */
    User findByUsername(String username);

    Integer updatePasswordByUid(@Param("uid") String uid,
                                @Param("password") String password,
                                @Param("modifiedUser") String modifiedUser,
                                @Param("modifiedTime") Date modifiedTime);

    /*** 根据用户id查询用户数据
     * @param uid 用户id
     * @return 匹配的用户数据,如果没有匹配的用户数据,则返回null
     */
    User findByUid(String uid);


}

7 .创建demo/service/ex目录、在ex目录下新建ServiceException异常类

package com.example.demo.Service.ex;

/**
 * 重写异常方法
 */
public class ServiceException extends RuntimeException {
    public ServiceException() {
        super();
    }

    public ServiceException(String message) {
        super(message);
    }

    public ServiceException(String message, Throwable cause) {
        super(message, cause);
    }

    public ServiceException(Throwable cause) {
        super(cause);
    }

    protected ServiceException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

8.创建demo/service/ex目录、在ex目录下新建5个异常类-InsertException、PasswordNotMatchException、UpdateException、UsernameDuplicatedException、UserNotFoundException,这几个类全部继承上面创建的ServiceException类并且insert+alt选择Override Methods选择前4项,大概如下

package com.example.demo.Service.ex;

public class InsertException extends ServiceException {
    public InsertException() {
        super();
    }

    public InsertException(String message) {
        super(message);
    }

    public InsertException(String message, Throwable cause) {
        super(message, cause);
    }

    public InsertException(Throwable cause) {
        super(cause);
    }

    protected InsertException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

9.在service目录下新建IUserService接口

package com.example.demo.Service;

import com.example.demo.entity.User;

public interface IUserService {
    /**
     * 用户注册方法
     * @param user
     */
    void reg(User user);

    /**
     * 用户登录
     * @param username
     * @param password
     * @return
     */
    User login(String username,String password);

    /**
     * 修改密码
     * @param uid 当前登录的id
     * @param username 用户名
     * @param oldPassword
     * @param newPassword
     */
    void changePassword(String uid, String username, String oldPassword, String newPassword);

	/**
     * 根据用户id查询用户信息,有token时才可访问
     * @param uid 当前登录的id
     * @param username 用户名
     * @param oldPassword
     * @param newPassword
     */
    User findByUid(String uid);
}

10.在service目录下创建UserServiceImpl

package com.example.demo.Service.impl;

import com.example.demo.Service.IUserService;
import com.example.demo.Service.ex.PasswordNotMatchException;
import com.example.demo.Service.ex.UpdateException;
import com.example.demo.Service.ex.UserNotFoundException;
import com.example.demo.Service.ex.UsernameDuplicatedException;
import com.example.demo.entity.User;
import com.example.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.Date;
import java.util.UUID;

@Service
public class UserServiceImpl implements IUserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public void reg(User user) { // 用户注册
        // 查重
        User result = userMapper.findByUsername(user.getUsername());
        if (result != null) {
            throw new UsernameDuplicatedException("用户名被占用");
        }
        // 设置id
        user.setUid(UUID.randomUUID().toString().toUpperCase());
        //  设置初始值
        user.setIsDelete(0);
        user.setCreatedUser(user.getUsername());
        user.setModifiedUser(user.getUsername());
        // 时间
        Date date = new Date();
        user.setModifiedTime(date);
        user.setCreatedTime(date);
        // 密码加密
        String oldPassword = user.getPassword();
        String salt = UUID.randomUUID().toString().toUpperCase();
        String md5Password = getMd5Pw(oldPassword, salt);
        // 保存密码
        user.setPassword(md5Password);
        // 保存盐值
        user.setSalt(salt);
        // 注册
        Integer rows = userMapper.insert(user);
        if (rows != 1) {
            throw new UsernameDuplicatedException("在用户注册过程中发生了未知的异常");
        }
    }

    @Override
    public User login(String username, String password) { // 登录
        User result = userMapper.findByUsername(username);
        if (result == null) {
            throw new UserNotFoundException("用户数据不存在的异常");
        }
        if (result.getIsDelete() == 1) {
            throw new UserNotFoundException("用户数据不存在的错误");
        }
        // 获取盐值
        String salt = result.getSalt();
        String md5Pw = getMd5Pw(password, salt);
        if (!result.getPassword().equals(md5Pw)) {
            throw new PasswordNotMatchException("密码验证失败的错误");
        }
        result.setPassword("");
        return result;
    }

    @Override
    public void changePassword(String uid, String username, String oldPassword, String newPassword) { // 修改密码
        User result = userMapper.findByUid(uid);
        if (result == null) {
            throw new UserNotFoundException("用户数据不存在");
        }
        if (result.getIsDelete().equals(1)) {
            throw new UserNotFoundException("用户数据不存在");
        }
        String salt = result.getSalt();
        // 比较密码
        String oldMd5Pw = getMd5Pw(oldPassword, salt);
        if (!result.getPassword().contentEquals(oldMd5Pw)) {
            throw new PasswordNotMatchException("原密码错误");
        }
        // 新密码
        String newMd5Pw = getMd5Pw(newPassword, salt);
        Date now = new Date();
        Integer rows = userMapper.updatePasswordByUid(uid, newMd5Pw, username, now);
        if (rows != 1) {
            throw new UpdateException("更新用户数据时出现未知错误,请联系统管理员");
        }
    }

    @Override
    public User findByUid(String uid) { // 根据uid查询用户资料
        return userMapper.findByUid(uid);
    }


    /**
     * 密码通过md5算法加密
     */
    private String getMd5Pw(String password, String salt) {
        for (int i = 0; i < 3; i++) {
            password = DigestUtils.md5DigestAsHex((salt + password).getBytes()).toUpperCase();
        }
        return password;
    }
}

10.在demo目录下新建controller目录,在controller目录下新建BaseController类用于返回异常结果

package com.example.demo.controll;

import com.example.demo.Service.ex.*;
import com.example.demo.util.JsonResult;
import org.springframework.web.bind.annotation.ExceptionHandler;

/**
 * 控制层基类
 */
public class BaseController {
    // 成功状态码
    public static final int OK = 200;


    // 请求处理方法,这个方法的返回值就是需要传递给前端的数据
    @ExceptionHandler(ServiceException.class)  // 统一处理抛出的异常
    public JsonResult<Void> handlerException(Throwable e) {
        JsonResult<Void> result = new JsonResult<>(e);
        if(e instanceof UsernameDuplicatedException){
            result.setState(400);
            result.setMessage("用户名已经被占用");
        }else if(e instanceof UserNotFoundException){
            result.setState(500);
            result.setMessage("用户不存在");
        }else if(e instanceof PasswordNotMatchException){
            result.setState(600);
            result.setMessage("密码错误");
        }else if(e instanceof InsertException){
            result.setMessage("注册失败");
            result.setState(700);
        }else if(e instanceof UpdateException){
            result.setState(800);
            result.setMessage("修改密码失败");
        }
        return result;
    }
}

11.在controller目录下新建UserController类

package com.example.demo.controll;

import com.example.demo.Service.IUserService;
import com.example.demo.entity.User;
import com.example.demo.util.JWTUtils;
import com.example.demo.util.JsonResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("users")
public class UserController extends BaseController {
    @Autowired
    private IUserService userService;

    // 用户注册
    @RequestMapping("/reg")
    public JsonResult<Void> reg(User user) {
        userService.reg(user);
        return new JsonResult<>(OK, "用户注册成功");
    }

    // 用户登录
    @RequestMapping("/login")
    public JsonResult<User> login(String username, String password) {
        User user = userService.login(username, password);
        if (user != null) {
            Map<String,String> map = new HashMap<>();
            map.put("username",user.getUsername());
            map.put("uid",user.getUid());
            String token = JWTUtils.getToken(map);
            user.setToken(token);
        }
        return new JsonResult<>(OK, "登录成功", user);
    }

    // 修改密码
    @RequestMapping("/change_password")
    public JsonResult<Void> changePassword(String uid, String username, String oldPassword, String newPassword) {
        userService.changePassword(uid, username, oldPassword, newPassword);
        return new JsonResult<>(OK, "修改成功");
    }

    // 获取用户信息
    @RequestMapping("/list")
    public JsonResult<User> list(String uid) {
        User user = userService.findByUid(uid);
        return new JsonResult<>(OK,"获取成功",user);
    }

}

12.在demo下新建utils目录,在utils下新建JWTUtils类

package com.example.demo.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

public class JWTUtils {


	// 秘钥
    private static final String  SING = "!Q@W3e4r%T^Y";

    /**
     * 生成token  header.payload.sing
     */
    public static String getToken(Map<String,String> map){

        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.DATE,7);//默认7天过期

        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();

        // 将传进来的用户信息加入到token再生成
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        // 得到token
        String token = builder.withExpiresAt(instance.getTime())//指定令牌过期时间
                .sign(Algorithm.HMAC256(SING));// 机密方式为HMAC256,SING秘钥
        return token;
    }

    /**
     * 验证token 合法性
     *
     */
    public static DecodedJWT verify(String token){
        return JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
    }

}

13.在utils目录下新建JsonResult类用于封装返回结果集

package com.example.demo.util;

import java.io.Serializable;

public class JsonResult<E> implements Serializable {
    // 状态码
    private Integer state;
    // 描述信息
    private String message;
    // 数据
    private E data;

    public JsonResult() {
    }

    public JsonResult(Integer state) {
        this.state = state;
    }

    public JsonResult(Throwable e) {
        this.message = e.getMessage();
    }

    public JsonResult(Integer state, E data) {
        this.state = state;
        this.data = data;
    }

    public Integer getState() {
        return state;
    }

    public void setState(Integer state) {
        this.state = state;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public E getData() {
        return data;
    }

    public void setData(E data) {
        this.data = data;
    }

    public JsonResult(Integer state, String message) {
        this.state = state;
        this.message = message;
    }

    public JsonResult(Integer state, String message, E data) {
        this.state = state;
        this.message = message;
        this.data = data;
    }
}

14.忘了还有两个实体类、在demo下创建entity目录,在entity下创建BaseEntity类

package com.example.demo.entity;

import java.io.Serializable;
import java.util.Date;
import java.util.Objects;

public class BaseEntity implements Serializable {
    private String createdUser;
    private Date createdTime;
    private String modifiedUser;
    private Date modifiedTime;

    public String getCreatedUser() {
        return createdUser;
    }

    public void setCreatedUser(String createdUser) {
        this.createdUser = createdUser;
    }

    public Date getCreatedTime() {
        return createdTime;
    }

    public void setCreatedTime(Date createdTime) {
        this.createdTime = createdTime;
    }

    public String getModifiedUser() {
        return modifiedUser;
    }

    public void setModifiedUser(String modifiedUser) {
        this.modifiedUser = modifiedUser;
    }

    public Date getModifiedTime() {
        return modifiedTime;
    }

    public void setModifiedTime(Date modifiedTime) {
        this.modifiedTime = modifiedTime;
    }

    @Override
    public String toString() {
        return "BaseEntity{" +
                "createdUser='" + createdUser + '\'' +
                ", createdTime=" + createdTime +
                ", modifiedUser='" + modifiedUser + '\'' +
                ", modifiedTime=" + modifiedTime +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof BaseEntity)) return false;
        BaseEntity that = (BaseEntity) o;
        return Objects.equals(getCreatedUser(), that.getCreatedUser()) &&
                Objects.equals(getCreatedTime(), that.getCreatedTime()) &&
                Objects.equals(getModifiedUser(), that.getModifiedUser()) &&
                Objects.equals(getModifiedTime(), that.getModifiedTime());
    }

    @Override
    public int hashCode() {
        return Objects.hash(getCreatedUser(), getCreatedTime(), getModifiedUser(), getModifiedTime());
    }
}

15.entity目录下继续新建User类

package com.example.demo.entity;

import java.util.Objects;

public class User extends BaseEntity {
    private String uid;
    private String username;
    private String password;
    private String salt;
    private String phone;
    private String email;
    private Integer gender;
    private String avatar;
    private Integer isDelete;

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    private String token;

    public String getUid() {
        return uid;
    }

    public void setUid(String uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }

    public String getPhone() {
        return phone;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getGender() {
        return gender;
    }

    public void setGender(Integer gender) {
        this.gender = gender;
    }

    public String getAvatar() {
        return avatar;
    }

    public void setAvatar(String avatar) {
        this.avatar = avatar;
    }

    public Integer getIsDelete() {
        return isDelete;
    }

    public void setIsDelete(Integer isDelete) {
        this.isDelete = isDelete;
    }

    @Override
    public String toString() {
        return "User{" +
                "uid='" + uid + '\'' +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", salt='" + salt + '\'' +
                ", phone='" + phone + '\'' +
                ", email='" + email + '\'' +
                ", gender=" + gender +
                ", avatar='" + avatar + '\'' +
                ", isDelete=" + isDelete +
                ", token='" + token + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        if (!super.equals(o)) return false;
        User user = (User) o;
        return Objects.equals(getUid(), user.getUid()) &&
                Objects.equals(getUsername(), user.getUsername()) &&
                Objects.equals(getPassword(), user.getPassword()) &&
                Objects.equals(getSalt(), user.getSalt()) &&
                Objects.equals(getPhone(), user.getPhone()) &&
                Objects.equals(getEmail(), user.getEmail()) &&
                Objects.equals(getGender(), user.getGender()) &&
                Objects.equals(getAvatar(), user.getAvatar()) &&
                Objects.equals(getIsDelete(), user.getIsDelete()) &&
                Objects.equals(getToken(), user.getToken());
    }

    @Override
    public int hashCode() {
        return Objects.hash(super.hashCode(), getUid(), getUsername(), getPassword(), getSalt(), getPhone(), getEmail(), getGender(), getAvatar(), getIsDelete(), getToken());
    }
}

16.配置拦截规则、在demo下新建interceptor目录,在interceptor新建JWTinterceptor类

package com.example.demo.interceptor;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.example.demo.util.JWTUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;


public class JWTInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Map<String, Object> map = new HashMap<>();
        //获取请求头中令牌
        String token = request.getHeader("token");
        try {
            JWTUtils.verify(token);//验证令牌
            return true;//放行请求
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            map.put("msg","无效签名!");
        }catch (TokenExpiredException e){
            e.printStackTrace();
            map.put("msg","token过期!");
        }catch (AlgorithmMismatchException e){
            e.printStackTrace();
            map.put("msg","token算法不一致!");
        }catch (Exception e){
            e.printStackTrace();
            map.put("msg","token无效!!");
        }
        map.put("state",false);//设置状态
        //将map 专为json  jackson
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;
    }
}

17.在demo下新建config,在config目录下新建InterceptorConfig

package com.example.demo.config;

import com.example.demo.interceptor.JWTInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Configuration
public class InterceptorConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/users/list")         //其他接口token验证
                .excludePathPatterns("/users/login");  //所有用户都放行
    }

    // 最大30线程,外加30列队;【拒绝策略AbortPolicy(默认):丢弃并抛出异常】
    private static ExecutorService executorService = new ThreadPoolExecutor(
            1, 30, 60L,
            TimeUnit.SECONDS, new ArrayBlockingQueue<>(3)
    );


    /**
     * 线程限制
     *
     * @param configurer
     */
    @Override
    public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
        configurer.setTaskExecutor(new ConcurrentTaskExecutor(executorService));
        configurer.setDefaultTimeout(30000);
    }
}

18.运行app、获取users/list不带tokenspring boot jwt完整过程_第2张图片

19.登录一下、返回token和用户信息

spring boot jwt完整过程_第3张图片

20.在访问users/list

spring boot jwt完整过程_第4张图片

Ok,到这里就结束了

你可能感兴趣的:(spring,boot,数据库,java)