實戰SpringBoot集成JWT實現token驗證

JWT可以理解為一個加密的字符串,裡面由三部分組成:頭部(Header)、負載(Payload)、簽名(signature)

由base64加密後的header和payload使用.連接組成的字符串,然後通過header中聲明的加密方式進行加鹽secret組合加密,然後就構成瞭JWT字符串

往期介紹瞭JWT相關概念以及基本操作,接下來介紹如何在SpringBoot中整合JWT實現登陸註冊

環境搭建

1、新建一個SpringBoot項目Jwt-Demo,引入項目後面需要用到的jar包

  <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--引入mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--引入mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>
        <!--引入druid數據庫連接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.1</version>
        </dependency>
        <!--引入lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter-test</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--引入jwt-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.0</version>
        </dependency>
    </dependencies>

2、數據庫結構

有一個JWT庫,裡面還有一個User表

3、配置文件application.properties

server.port=8989

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/JWT?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
spring.datasource.username=root
spring.datasource.password=12345678

#mybatis掃描的包
mybatis.type-aliases-package=com.ylc
#mapper文件路徑
mybatis.mapper-locations=classpath:/**/*.xml

#開啟sql打印日志  logging.level後面是mybatis對應的方法接口所在的包
logging.level.com.ylc.jwtdemo.dao=debug

4、Entity包下新建一個User類

import lombok.Data;

@Data
public class User {
    private  String username;
    private  String password;
    private  int id;
}

5、Dao包下新建一個UserDao

@Mapper
public interface UserDao {

    User login(User user);
}

6、Service包下新建一個USerService

public interface UserService {
    User login(User user);//登錄接口
}

7、UseService的實現類UserServiceImp

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

@Service
public class UserServiceImpI implements UserService {

    @Autowired
    private UserDao userDao;

    @Override
    public User login(User user) {
        User userdb=userDao.login(user);
        if(userdb!=null)
        {
            Map<String,String> map=new HashMap<>();
            map.put("name",userdb.getUsername());
            return userdb;
        }
        throw  new RuntimeException("登錄失敗");
    }
}

8、controller包下新建一個UserController

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/login")
    public Map<String,Object> login(User user)
    {
        log.info("用戶名:"+user.getUsername());
        log.info("密碼:"+user.getPassword());
        Map<String,Object> map=new HashMap<>();
        try {
            userService.login(user);
            map.put("msg","登錄成功");
            map.put("code","200");
        }
        catch (Exception ex)
        {
            map.put("msg","登錄失敗");
        }

        return map;
    }
}

9、在resource文件夾下新建一個Usermapper文件

<?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">
<!--namespace 指的是要配置的全限定類名-->
<mapper namespace="com.ylc.jwtdemo.dao.UserDao">
    <select id="login" parameterType="com.ylc.jwtdemo.entity.User" resultType="com.ylc.jwtdemo.entity.User">
        select *from user where username=#{username} and password=#{password}
    </select>
</mapper>

10、JWT工具類JwtUtils

/**
 * JWT工具類
 * @author yanglingcong
 * @date 2021/12/31 11:24 AM
 */
public class JwtUtils {
    //鑒權 相當於私鑰保存在服務器上
    private  static  final String  secret="##@$%@#S#WS";


    /**
     * 生成token
     * @author yanglingcong
     * @date 2021/12/31 11:23 AM
     * @param map
     * @return String
     */
    public static  String getToken(Map<String,String> map)
    {
        Calendar instance=Calendar.getInstance();
        //默認七天過期
        instance.add(Calendar.DATE,7);
        //創建JWT
        JWTCreator.Builder builder = JWT.create();

        //payload
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        //指定令牌過期時間
        builder.withExpiresAt(instance.getTime());

        String token=builder.sign(Algorithm.HMAC256(secret));
        return token;
    }

    /**
     * 驗證token
     * @author yanglingcong
     * @date 2021/12/31 11:26 AM
     * @param token
     */
    public  static  DecodedJWT  verify(String token) {
        return  JWT.require(Algorithm.HMAC256(secret)).build().verify(token);
    }

}

整個項目概覽

測試驗證是否能夠連通數據庫

訪問:localhost:8989/user/login?username=ylc&password=123456

引入JWT

@Slf4j
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/user/login")
    public Map<String,Object> login(User user)
    {
        log.info("用戶名:"+user.getUsername());
        log.info("密碼:"+user.getPassword());
        Map<String,Object> map=new HashMap<>();
        try {
            userService.login(user);
            map.put("msg","登錄成功");
            map.put("code","200");

            Map<String,String> payload=new HashMap<>();
            payload.put("name",user.getUsername());
            String token= JwtUtils.getToken(payload);
            map.put("token",token);
        }
        catch (Exception ex)
        {
            map.put("msg","登錄失敗");
        }

        return map;
    }

    @PostMapping("/test/verity")
    public  Map<String,String> verityToken(String token)
    {
        Map<String, String> map=new HashMap<>();
        log.info("token為"+token);
        try {
            DecodedJWT verify = JwtUtils.verify(token);
            map.put("msg","驗證成功");
            map.put("state","true");
        }
        catch (Exception exception)
        {
            map.put("msg","驗證失敗");
            exception.printStackTrace();
        }
        return map;
    }
}

登錄操作

訪問:http://localhost:8989/user/login?username=ylc&password=123456

驗證操作

訪問:http://localhost:8989/test/verity

但是我們這樣寫在實際項目中是不合理的,把token生成的代碼放在瞭Controller中,業務邏輯是不能放在Controller層中的。假如很多接口都需要token來進行驗證保護,那每一個接口都需要添加這樣一段代碼,造成代碼冗餘。

程序優化

如果是web項目使用攔截器進行優化,如果是springcloud項目在網關層進行攔截,下面演示如何使用攔截器攔截

最好還把JWT生成token放在http請求頭,這樣就不需要把token當成參數傳遞瞭

新建一個攔截器JwtInterceptor

/**
 * JWT攔截器
 * @author yanglingcong
 * @date 2021/12/31 12:39 PM
 */
public class JwtInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        HashMap<String, String> map=new HashMap<>();
        //從http請求頭獲取token
        String token = request.getHeader("token");
        try {
            //如果驗證成功放行請求
            DecodedJWT verify = JwtUtils.verify(token);
            return true;
        }
        catch (Exception exception)
        {
            map.put("msg","驗證失敗:"+exception);
        }
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json:charset=UTF=8");
        response.getWriter().println(json);
        return false;
    }
}

然後把攔截器註冊到過濾器中,新建一個過濾器InterceptConfig

/**
 * @author yanglingcong
 */
@Configuration
public class InterceptConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加攔截器
        registry.addInterceptor(new JwtInterceptor())
                //攔截的路徑 需要進行token驗證的路徑
                .addPathPatterns("/test/verity")
                //放行的路徑
                .excludePathPatterns("/user/login");
    }
}

登錄是不需要攔截的,其他請求如果有需要驗證token就放入攔截器的路徑

測試驗證

在http請求頭中放入token,會被攔截器攔截驗證token的有效性

總結

這就是SpringBoot整合JWT實際項目一個大概的流程,但是細節方面secret(私鑰)肯定每個用戶都是不一樣的,這裡給寫死瞭,而且私鑰得保存在一個安全的地方。包括payload部分不能存放敏感的密碼信息等等,還可以進行優化。

項目代碼:https://gitee.com/yanglingcong/jwt-demo

到此這篇關於實戰SpringBoot集成JWT實現token驗證的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。

推薦閱讀: