JWT+SpringBoot實戰
- 2021 年 12 月 31 日
- 筆記
- spring security
往期內容:JWT – 炒燜煎糖板栗 – 部落格園 (cnblogs.com)
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" "//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;
}
}
登錄操作
訪問://localhost:8989/user/login?username=ylc&password=123456
驗證操作
訪問://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部分不能存放敏感的密碼資訊等等,還可以進行優化。