SpringBoot集成JWT實(shí)現(xiàn)Token登錄驗(yàn)證的示例代碼
一, JWT是什么?
在JWT官網(wǎng)中可以明確看到關(guān)于它的定義
JSON Web令牌(JWT)是一種開放的標(biāo)準(zhǔn)(RFC 7519),它定義了一種緊湊而獨(dú)立的方式在各方之間安全地傳輸信息為JSON對象。該信息可以被驗(yàn)證和信任,因?yàn)樗菙?shù)字簽名的。JWT可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公開/私有密鑰類型簽名。 雖然JWT可以被加密以提供各方之間的保密,但我們將重點(diǎn)關(guān)注簽名的令牌。被簽名的令牌可以驗(yàn)證包含在其中的聲明的完整性,而加密的令牌對其他各方隱藏這些聲明。當(dāng)使用公鑰/私鑰對簽名時,簽名還證明只有持有私鑰的一方才是簽名方。
1.1 JWT主要使用場景
授權(quán)(Authorization):這是使用JWT最常見的場景。一旦用戶登錄,每個后續(xù)請求都將包括JWT,允許用戶訪問該令牌允許的路由、服務(wù)和資源。
單點(diǎn)登錄(Single Sign On ):單點(diǎn)登錄是當(dāng)今廣泛使用的JWT特性,因?yàn)樗男∫?guī)模和易于跨不同領(lǐng)域使用的能力。
信息交換(lnformation Exchange):信息交換在通信的雙方之間使用JWT對數(shù)據(jù)進(jìn)行編碼是一種非常安全的方式,由于它的信息是經(jīng)過簽名的,可以確保發(fā)送者發(fā)送的信息是沒有經(jīng)過偽造的。
傳輸信息(transmitting information):在各方之間傳輸信息。由于JWT可以簽名--例如,使用公共/私鑰對--您可以確保發(fā)件人是他們所說的發(fā)送者。此外,由于簽名是使用頭和有效載荷計算的,您還可以驗(yàn)證內(nèi)容沒有被篡改。
1.2 JWT請求流程
- 用戶使用賬號和密碼發(fā)出post請求;
- 服務(wù)器使用私鑰創(chuàng)建一個jwt;
- 服務(wù)器返回這個jwt給瀏覽器;
- 瀏覽器將該jwt串在請求頭中像服務(wù)器發(fā)送請求;
- 服務(wù)器驗(yàn)證該jwt;
- 返回響應(yīng)的資源給瀏覽器。
1.3 JWT結(jié)構(gòu)
JWT是由三段信息構(gòu)成的,將這三段信息文本用.連接一起就構(gòu)成了JWT字符串。
就像這樣:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT包含了三部分:
Header 頭部(標(biāo)題包含了令牌的元數(shù)據(jù),并且包含簽名和/或加密算法的類型)
Payload 負(fù)載 (類似于飛機(jī)上承載的物品)
Signature 簽名/簽證
Header
JWT的頭部承載兩部分信息:token類型和采用的加密算法。
{ "alg": "HS256", "typ": "JWT" }
聲明類型:這里是jwt
聲明加密的算法:通常直接使用 HMAC SHA256
加密算法是單向函數(shù)散列算法,常見的有MD5、SHA、HAMC。
MD5(message-digest algorithm 5) (信息-摘要算法)縮寫,廣泛用于加密和解密技術(shù),常用于文件校驗(yàn)。校驗(yàn)?不管文件多大,經(jīng)過MD5后都能生成唯一的MD5值
SHA (Secure Hash Algorithm,安全散列算法),數(shù)字簽名等密碼學(xué)應(yīng)用中重要的工具,安全性高于MD5
HMAC (Hash Message Authentication Code),散列消息鑒別碼,基于密鑰的Hash算法的認(rèn)證協(xié)議。用公開函數(shù)和密鑰產(chǎn)生一個固定長度的值作為認(rèn)證標(biāo)識,用這個標(biāo)識鑒別消息的完整性。常用于接口簽名驗(yàn)證
Payload
載荷就是存放有效信息的地方。
有效信息包含三個部分:
標(biāo)準(zhǔn)中注冊的聲明
公共的聲明
私有的聲明
標(biāo)準(zhǔn)中注冊的聲明 (建議但不強(qiáng)制使用) :
iss: jwt簽發(fā)者
sub: 面向的用戶(jwt所面向的用戶)
aud: 接收jwt的一方
exp: 過期時間戳(jwt的過期時間,這個過期時間必須要大于簽發(fā)時間)
nbf: 定義在什么時間之前,該jwt都是不可用的.
iat: jwt的簽發(fā)時間
jti: jwt的唯一身份標(biāo)識,主要用來作為一次性token,從而回避重放攻擊。
公共的聲明:
公共的聲明可以添加任何的信息,一般添加用戶的相關(guān)信息或其他業(yè)務(wù)需要的必要信息.但不建議添加敏感信息,因?yàn)樵摬糠衷诳蛻舳丝山饷?
私有的聲明:
私有聲明是提供者和消費(fèi)者所共同定義的聲明,一般不建議存放敏感信息,因?yàn)閎ase64是對稱解密的,意味著該部分信息可以歸類為明文信息。
Signature
jwt的第三部分是一個簽證信息
這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串,然后通過header中聲明的加密方式進(jìn)行加鹽secret組合加密,然后就構(gòu)成了jwt的第三部分。最主要的目的:服務(wù)器應(yīng)用在接受到JWT后,會首先對頭部和載荷的內(nèi)容用同一算法再次簽名,如果服務(wù)器應(yīng)用對頭部和載荷再次以同樣方法簽名之后發(fā)現(xiàn),自己計算出來的簽名和接受到的簽名不一樣,那么就說明這個Token的內(nèi)容被別人動過的,我們應(yīng)該拒絕這個Token,返回一個HTTP 401 Unauthorized響應(yīng)。
密鑰secret是保存在服務(wù)端的,服務(wù)端會根據(jù)這個密鑰進(jìn)行生成token和進(jìn)行驗(yàn)證,所以需要保護(hù)好。
這些信息在官網(wǎng)上也有相關(guān)信息的說明:
二,SpringBoot集成JWT具體實(shí)現(xiàn)過程
這里由于只涉及對驗(yàn)證功能的實(shí)現(xiàn),因此其他數(shù)據(jù)庫,業(yè)務(wù)類編寫一概省略,只對相關(guān)步驟做說明。
2.1添加相關(guān)依賴
既然要使用JWT我們肯定需要引入其依賴
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.3</version> </dependency>
2.2自定義跳出攔截器的注解
方便我們后續(xù)不用對在配置攔截器時排除每一個接口,只用自定義注解去標(biāo)記即可。
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @author young * @date 2022/11/24 14:53 * @description: 自定義通過token注解,如果不加該注解直接攔截 */ @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface PassToken { boolean required() default true; }
@Retention注解說明
RetentionPolicy.SOURCE:這種類型的Annotations只在源代碼級別保留,編譯時就會被忽略,在class字節(jié)碼文件中不包含。
RetentionPolicy.CLASS:這種類型的Annotations編譯時被保留,默認(rèn)的保留策略,在class文件中存在,但JVM將會忽略,運(yùn)行時無法獲得。
RetentionPolicy.RUNTIME:這種類型的Annotations將被JVM保留,所以他們能在運(yùn)行時被JVM或其他使用反射機(jī)制的代碼所讀取和使用。
@Document:說明該注解將被包含在javadoc中
@Inherited:說明子類可以繼承父類中的該注解
2.3自定義全局統(tǒng)一返回值方法,異常類及相關(guān)枚舉
定義全局枚舉類
package com.yy.enums; import lombok.AllArgsConstructor; import lombok.Data; import lombok.Getter; import java.util.UUID; /** * @author young * @date 2022/8/19 21:36 * @description: 響應(yīng)結(jié)果枚舉 */ @AllArgsConstructor @Getter public enum ResponseEnum { /**響應(yīng)成功**/ SUCCESS(200, "操作成功"), /**操作失敗*/ FAIL(201,"獲取數(shù)據(jù)失敗"), NO_TOKEN(400,"無token,請重新登錄"), TOKEN_EX(401,"token驗(yàn)證失敗,請重新登錄"), USER_EX(402,"用戶不存在,請重新登錄"), /**錯誤請求**/ ERROR(400,"錯誤請求"); /**響應(yīng)碼**/ private final Integer code; /** 結(jié)果 **/ private final String resultMessage; public static ResponseEnum getResultCode(Integer code){ for (ResponseEnum value : ResponseEnum.values()) { if (code.equals(value.getCode())){ return value; } } return ResponseEnum.ERROR; } /* 簡單測試一下 */ public static void main(String[] args) { ResponseEnum resultCode = ResponseEnum.getResultCode(100); System.out.println(resultCode); } }
定義全局異常類
package com.yy.util; import com.yy.Enums.ResultEnum; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * @author young * @date 2022/9/25 19:15 * @description: 自定義運(yùn)行時異常 */ @Data @ApiModel(value = "自定義全局異常類") public class MyException extends RuntimeException{ @ApiModelProperty(value = "異常狀態(tài)碼") private final Integer code; /** * 通過狀態(tài)碼和異常信息創(chuàng)建異常對象 * @param code * @param message */ public MyException(Integer code,String message) { super(message); this.code = code; } /** * 接受枚舉類型對象 * @param resultEnum */ public MyException(ResponseEnum responseEnum){ super(responseEnum.getMessage()); this.code = responseEnum.getCode(); } }
package com.yy.Config; import com.yy.utils.MyException; import com.yy.utils.R; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * @author young * @date 2022/9/12 15:43 * @description: 自定義異常配置 */ @RestControllerAdvice public class GlobalExceptionConfig{ @ExceptionHandler(MyException.class) public R<MyException> handle(MyException e){ e.printStackTrace(); return R.exception(e.getCode(),e.getMessage()); } }
定義統(tǒng)一返回結(jié)果類
package com.yy.utils; import com.yy.enums.ResponseEnum; import lombok.Data; import java.io.Serializable; import java.util.HashMap; import java.util.Map; /** * @author young * @date 2022/8/19 21:52 * @description: 統(tǒng)一返回結(jié)果的類 */ @Data public class R<T> implements Serializable { private static final long serialVersionUID = 56665257248936049L; /**響應(yīng)碼**/ private Integer code; /**返回消息**/ private String message; /**返回數(shù)據(jù)**/ private T data; private R(){} /** * 操作成功ok方法 */ public static <T> R<T> ok(T data) { R<T> response = new R<>(); response.setCode(ResponseEnum.SUCCESS.getCode()); response.setMessage(ResponseEnum.SUCCESS.getResultMessage()); response.setData(data); return response; } /** * 編譯失敗方法 */ public static <T> R<T> buildFailure(Integer errCode, String errMessage){ R<T> response = new R<>(); response.setCode(errCode); response.setMessage(errMessage); return response; } public static <T> R<T> exception(Integer errCode, String errMessage){ R<T> response = new R<>(); response.setCode(errCode); response.setMessage(errMessage); return response; } }
2.4編寫JWT工具類,用于生成Token令牌
package com.yy.utils; import cn.hutool.core.date.DateUtil; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import java.util.Date; /** * @author young * @date 2022/9/12 14:46 * @description: 整合JWT生成token */ public class JwtTokenUtils { private JwtTokenUtils() { throw new IllegalStateException("Utility class"); } /** * 生成token * @param userId * @param sign * @return */ public static String getToken(String userId,String sign){ return JWT.create() //簽收者 .withAudience(userId) //主題 .withSubject("token") //2小時候token過期 .withExpiresAt(DateUtil.offsetHour(new Date(),2)) //以password作為token的密鑰 .sign(Algorithm.HMAC256(sign)); } }
Algorithm.HMAC256():使用HS256生成token,密鑰則是用戶的密碼,唯一密鑰的話可以保存在服務(wù)端。
withAudience():存入需要保存在token的信息,這里我們把用戶ID存入token中
2.5編寫攔截器并注入容器
package com.yy.Config.inteceptor; import cn.hutool.core.text.CharSequenceUtil; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.JWTVerificationException; import com.yy.enums.ResponseEnum; import com.yy.admin.pojo.Admin; import com.yy.admin.service.Impl.AdminServiceImpl; import com.yy.utils.MyException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Method; /** * @author young * @date 2022/9/12 15:37 * @description: 獲取token并驗(yàn)證 */ @Component public class MyJwtInterceptor implements HandlerInterceptor { @Autowired private AdminServiceImpl adminService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token"); if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) handler; Method method = handlerMethod.getMethod(); //檢查是否通過有PassToken注解 if (method.isAnnotationPresent(PassToken.class)) { //如果有則跳過認(rèn)證檢查 PassToken passToken = method.getAnnotation(PassToken.class); if (passToken.required()) { return true; } } //否則進(jìn)行token檢查 if (CharSequenceUtil.isBlank(token)) { throw new MyException(ResponseEnum.TOKEN_EX.getCode(), ResponseEnum.TOKEN_EX.getResultMessage()); } //獲取token中的用戶id String userId; try { userId = JWT.decode(token).getAudience().get(0); } catch (JWTDecodeException j) { throw new MyException(ResponseEnum.TOKEN_EX.getCode(), ResponseEnum.TOKEN_EX.getResultMessage()); } //根據(jù)token中的userId查詢數(shù)據(jù)庫 Admin user = adminService.getById(userId); if (user == null) { throw new MyException(ResponseEnum.USER_EX.getCode(), ResponseEnum.USER_EX.getResultMessage()); } //驗(yàn)證token JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(user.getPwd())).build(); try { jwtVerifier.verify(token); } catch (JWTVerificationException e) { throw new MyException(406, "權(quán)限驗(yàn)證失敗!"); } return true; } }
這里需要說明一下實(shí)現(xiàn)攔截器的方法,我們只需要實(shí)現(xiàn)HandlerInterceptor接口即可,它主要定義了三個方法:
boolean preHandle ():
預(yù)處理回調(diào)方法,實(shí)現(xiàn)處理器的預(yù)處理,第三個參數(shù)為響應(yīng)的處理器,自定義Controller,返回值為true表示繼續(xù)流程(如調(diào)用下一個攔截器或處理器)或者接著執(zhí)行postHandle()和afterCompletion();false表示流程中斷,不會繼續(xù)調(diào)用其他的攔截器或處理器,中斷執(zhí)行。
void postHandle():
后處理回調(diào)方法,實(shí)現(xiàn)處理器的后處理(DispatcherServlet進(jìn)行視圖返回渲染之前進(jìn)行調(diào)用),此時我們可以通過modelAndView(模型和視圖對象)對模型數(shù)據(jù)進(jìn)行處理或?qū)σ晥D進(jìn)行處理,modelAndView也可能為null。
void afterCompletion():
整個請求處理完畢回調(diào)方法,該方法也是需要當(dāng)前對應(yīng)的Interceptor的preHandle()的返回值為true時才會執(zhí)行,也就是在DispatcherServlet渲染了對應(yīng)的視圖之后執(zhí)行。用于進(jìn)行資源清理。
整個請求處理完畢回調(diào)方法。如性能監(jiān)控中我們可以在此記錄結(jié)束時間并輸出消耗時間,還可以進(jìn)行一些資源清理,類似于try-catch-finally中的finally,但僅調(diào)用處理器執(zhí)行鏈中。
這里我們主要需要調(diào)用預(yù)處理回調(diào)方法即可,如果有其他業(yè)務(wù)需求,也可自行更改。
主要流程:
從 http 請求頭中取出 token,
判斷是否映射到方法
檢查是否有passtoken注釋,有則跳過認(rèn)證
檢查有沒有需要用戶登錄的注解,有則需要取出并驗(yàn)證
認(rèn)證通過則可以訪問,不通過會報相關(guān)錯誤信息
然后通過配置類將我們自定義的攔截類注入到spring容器中,并進(jìn)行攔截配置。
package com.yy.Config.inteceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @author young * @date 2022/9/12 15:36 * @description: JWT攔截配置 */ @Configuration public class InterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor()) //攔截所有請求,通過判斷token來決定是否需要登陸 .addPathPatterns("/**"); } @Bean public MyJwtInterceptor jwtInterceptor(){ return new MyJwtInterceptor(); } }
至此,我們對于JWT在SpringBoot中的基本配置就算完成了。我們只需在controller層在自己想要放行的api接口上添加我們自定義的放行注解,即可實(shí)現(xiàn)對api接口的放行,其他接口均要進(jìn)行Token令牌的驗(yàn)證判斷。如果沒有Token則返回自定義的異常信息。
三,測試
3.1放行一般類接口
這里我們先只對一個業(yè)務(wù)接口進(jìn)行放行。
/** * 查找指定id信息 * * @param id * @return */ @GetMapping("/getOne/{id}") @CostTime @PassToken public R<TestUser> selectOne(@PathVariable Integer id) { TestUser user = testUserService.getById(id); return R.ok(user); }
進(jìn)行接口測試后發(fā)現(xiàn),該接口獲取數(shù)據(jù)正常。
其他沒有加@PassToken的接口由于沒有token進(jìn)行驗(yàn)證,均會被攔截器攔截,并返回我們預(yù)期的異常信息"token驗(yàn)證失敗,請重新登錄"
因此,同理我們只需要在登陸注冊或其他不需要token驗(yàn)證的接口上添加自定義注解即可實(shí)現(xiàn)攔截。
為了達(dá)到業(yè)務(wù)需求,我們需要在用戶登錄成功后獲取到token,然后將token信息存放在每次的接口請求頭(headers)上,這樣就能實(shí)現(xiàn)對用戶接口信息基本保護(hù)了。
3.2放行登錄接口
在業(yè)務(wù)層處理token,將生成的token信息帶到用戶實(shí)體類中,這樣登錄獲取用戶信息時就能讀取到token信息了
package com.yy.admin.service.Impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.yy.admin.pojo.Admin; import com.yy.admin.service.AdminService; import com.yy.admin.dao.AdminDao; import com.yy.utils.JwtTokenUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Optional; /** * @author young * @description 針對表【admin】的數(shù)據(jù)庫操作Service實(shí)現(xiàn) * @createDate 2022-09-05 13:41:54 */ @Service @Slf4j public class AdminServiceImpl extends ServiceImpl<AdminDao, Admin> implements AdminService{ @Resource private AdminDao adminDao; @Override public Admin getMsg(String username, String pwd){ Admin admin = adminDao.selectByUsernameAndPwd(username, pwd); Optional.ofNullable(admin).ifPresent(u->{ //添加token信息設(shè)置到用戶實(shí)體上 String token = JwtTokenUtils.getToken(String.valueOf(admin.getId()),pwd); log.info("token的值為:{}",token); admin.setToken(token); }); return admin; } }
編寫用戶登錄的業(yè)務(wù)接口并放行Token
@PostMapping("/login") @PassToken public Object loginStatus(@RequestParam("username") String username, @RequestParam("password") String password){ JSONObject object = new JSONObject(); Admin admin = adminService.getMsg(username, password); if (admin!=null){ object.put("code",1); object.put("msg","登陸成功"); object.put("success",true); object.put("type","success"); object.put("userMsg",admin); }else { object.put("code",0); object.put("success",false); object.put("msg","用戶名或密碼錯誤"); object.put("type","error"); } return object; }
進(jìn)行接口測試
這樣就可以看到生成的Token信息了,然后我們將token信息設(shè)置在請求頭上對其他接口進(jìn)行測試。
此時就能看到,接口請求成功。
注意:這里的參數(shù)名token對應(yīng)攔截器配置String token=request.getHeader("token")中的getHeader中設(shè)置的的參數(shù)名。
到此這篇關(guān)于SpringBoot集成JWT實(shí)現(xiàn)Token登錄驗(yàn)證的示例代碼的文章就介紹到這了,更多相關(guān)SpringBoot JWT Token登錄驗(yàn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis-Plus的應(yīng)用場景描述及注入SQL原理分析
MyBatis-Plus是一個 MyBatis 的增強(qiáng)工具,在 MyBatis 的基礎(chǔ)上只做增強(qiáng)不做改變,為簡化開發(fā)、提高效率而生,本文重點(diǎn)給大家介紹Mybatis-Plus的應(yīng)用場景及注入SQL原理分析,感興趣的朋友跟隨小編一起學(xué)習(xí)吧2021-05-05如何使用Java模擬退火算法優(yōu)化Hash函數(shù)
為了解決局部最優(yōu)解問題,1983年,Kirkpatrick等提出了模擬退火算法(SA)能有效的解決局部最優(yōu)解問題。模擬退火算法包含兩個部分即Metropolis算法和退火過程。Metropolis算法就是如何在局部最優(yōu)解的情況下讓其跳出來,是退火的基礎(chǔ)2021-06-06在Spring Boot2中使用CompletableFuture的方法教程
這篇文章主要給大家介紹了關(guān)于在Spring Boot2中使用CompletableFuture的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧2019-01-01MyBatis #{}和${} |與數(shù)據(jù)庫連接池使用詳解
本文將為大家說說關(guān)于 #{} 和 ${},這個是 MyBatis 在面試中最常問的面試題,以及數(shù)據(jù)庫連接池相關(guān)的知識,感興趣的朋友跟隨小編一起看看吧2024-01-01Java8?Stream?collect(Collectors.toMap())的使用
這篇文章主要介紹了Java8?Stream?collect(Collectors.toMap())的使用,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-05-05SpringBoot DBUnit 單元測試(小結(jié))
這篇文章主要介紹了SpringBoot DBUnit 單元測試(小結(jié)),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09序列化版本號serialVersionUID的作用_動力節(jié)點(diǎn)Java學(xué)院整理
Java序列化是將一個對象編碼成一個字節(jié)流,反序列化將字節(jié)流編碼轉(zhuǎn)換成一個對象,這篇文章主要介紹了序列化版本號serialVersionUID的作用,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05