SpringBoot集成JWT令牌詳細(xì)說(shuō)明
一,傳統(tǒng)Cookie+Session與JWT對(duì)比
1, 在傳統(tǒng)的用戶登錄認(rèn)證中,因?yàn)閔ttp是無(wú)狀態(tài)的,所以都是采用session方式。用戶登錄成功,服務(wù)端會(huì)保證一個(gè)session,當(dāng)然會(huì)給客戶端一個(gè)sessionId,客戶端會(huì)把sessionId保存在cookie中,每次請(qǐng)求都會(huì)攜帶這個(gè)sessionId。
2,cookie+session這種模式通常是保存在內(nèi)存中,而且服務(wù)從單服務(wù)到多服務(wù)會(huì)面臨的session共享問(wèn)題,隨著用戶量的增多,開(kāi)銷就會(huì)越大。而JWT不是這樣的,只需要服務(wù)端生成token,客戶端保存這個(gè)token,每次請(qǐng)求攜帶這個(gè)token,服務(wù)端認(rèn)證解析就可。
3, JWT方式校驗(yàn)方式更加簡(jiǎn)單便捷化,無(wú)需通過(guò)redis緩存,而是直接根據(jù)token取出保存的用戶信息,以及對(duì)token可用性校驗(yàn),單點(diǎn)登錄,驗(yàn)證token更為簡(jiǎn)單。
二,springboot集成jwt
1,jwt的整合依賴
<!-- JWT依賴 --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.4.0</version> </dependency>
2,jwt的自定義配置
server: port: 8080 spring: application: name: springboot-jwt # 自定義配置 config: jwt: # 加密密鑰 secret: abcdefg1234567 # token有效時(shí)長(zhǎng) expire: 3600 # header 名稱 header: token
3,編寫JwtConfig
JwtConfig負(fù)責(zé)
- 生成token
- 獲取token中的注冊(cè)信息
- 驗(yàn)證token是否過(guò)期失效
- 獲取token失效時(shí)間
- 從token中獲取用戶名
- 獲取jwt發(fā)布時(shí)間
package com.ftx.jwt.config; /** * @author FanJiangFeng * @version 1.0.0 * @ClassName JwtConfig.java * @Description TODO * @createTime 2020年06月22日 15:43:00 */ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Date; /** * JWT的token,區(qū)分大小寫 */ @ConfigurationProperties(prefix = "config.jwt", ignoreUnknownFields = true) @Component public class JwtConfig { private String secret; private long expire; private String header; /** * 生成token * @param subject * @return */ public String createToken (String subject){ Date nowDate = new Date(); Date expireDate = new Date(nowDate.getTime() + expire * 1000);//過(guò)期時(shí)間 return Jwts.builder() .setHeaderParam("typ", "JWT") .setSubject(subject) .setIssuedAt(nowDate) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } /** * 獲取token中注冊(cè)信息 * @param token * @return */ public Claims getTokenClaim (String token) { try { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); }catch (Exception e){ // e.printStackTrace(); return null; } } /** * 驗(yàn)證token是否過(guò)期失效 * @param expirationTime * @return */ public boolean isTokenExpired (Date expirationTime) { return expirationTime.before(new Date()); } /** * 獲取token失效時(shí)間 * @param token * @return */ public Date getExpirationDateFromToken(String token) { return getTokenClaim(token).getExpiration(); } /** * 獲取用戶名從token中 */ public String getUsernameFromToken(String token) { return getTokenClaim(token).getSubject(); } /** * 獲取jwt發(fā)布時(shí)間 */ public Date getIssuedAtDateFromToken(String token) { return getTokenClaim(token).getIssuedAt(); } // --------------------- getter & setter --------------------- public String getSecret() { return secret; } public void setSecret(String secret) { this.secret = secret; } public long getExpire() { return expire; } public void setExpire(long expire) { this.expire = expire; } public String getHeader() { return header; } public void setHeader(String header) { this.header = header; } }
4,配置攔截器
在攔截器中對(duì)token進(jìn)行驗(yàn)證。
package com.ftx.jwt.config; import io.jsonwebtoken.Claims; import io.jsonwebtoken.SignatureException; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @author FanJiangFeng * @version 1.0.0 * @ClassName TokenInterceptor.java * @Description TODO * @createTime 2020年06月22日 15:47:00 */ @Component public class TokenInterceptor extends HandlerInterceptorAdapter { @Resource private JwtConfig jwtConfig ; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws SignatureException { /** 地址過(guò)濾 */ String uri = request.getRequestURI() ; if (uri.contains("/login")){ return true ; } /** Token 驗(yàn)證 */ String token = request.getHeader(jwtConfig.getHeader()); if(StringUtils.isEmpty(token)){ token = request.getParameter(jwtConfig.getHeader()); } if(StringUtils.isEmpty(token)){ throw new SignatureException(jwtConfig.getHeader()+ "不能為空"); } Claims claims = null; try{ claims = jwtConfig.getTokenClaim(token); if(claims == null || jwtConfig.isTokenExpired(claims.getExpiration())){ throw new SignatureException(jwtConfig.getHeader() + "失效,請(qǐng)重新登錄。"); } }catch (Exception e){ throw new SignatureException(jwtConfig.getHeader() + "失效,請(qǐng)重新登錄。"); } //該token可用,放行 return true; } }
注冊(cè)攔截器到SpringMvc
package com.ftx.jwt.config; /** * @author FanJiangFeng * @version 1.0.0 * @ClassName WebConfig.java * @Description TODO * @createTime 2020年06月22日 15:53:00 */ import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.validation.MessageCodesResolver; import org.springframework.validation.Validator; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.HandlerMethodReturnValueHandler; import org.springframework.web.servlet.HandlerExceptionResolver; import org.springframework.web.servlet.config.annotation.*; import javax.annotation.Resource; import java.util.List; @Configuration public class WebConfig implements WebMvcConfigurer { @Resource private TokenInterceptor tokenInterceptor ; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(tokenInterceptor).addPathPatterns("/**"); } }
5,編寫測(cè)試controller接口
package com.ftx.jwt.controller; import com.alibaba.fastjson.JSONObject; import com.ftx.jwt.config.JwtConfig; import com.ftx.jwt.util.ResultTool; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; /** * @author FanJiangFeng * @version 1.0.0 * @ClassName TokenController.java * @Description TODO * @createTime 2020年06月22日 16:05:00 */ @RestController public class TokenController { @Resource private JwtConfig jwtConfig ; /** * 用戶登錄接口 * @param userName * @param passWord * @return */ @PostMapping("/login") public JSONObject login (@RequestParam("userName") String userName, @RequestParam("passWord") String passWord){ JSONObject json = new JSONObject(); /** 驗(yàn)證userName,passWord和數(shù)據(jù)庫(kù)中是否一致,如不一致,直接return ResultTool.errer(); 【這里省略該步驟】*/ // 這里模擬通過(guò)用戶名和密碼,從數(shù)據(jù)庫(kù)查詢userId // 這里把userId轉(zhuǎn)為String類型,實(shí)際開(kāi)發(fā)中如果subject需要存userId,則可以JwtConfig的createToken方法的參數(shù)設(shè)置為L(zhǎng)ong類型 String userId = 5 + ""; String token = jwtConfig.createToken(userId) ; if (!StringUtils.isEmpty(token)) { json.put("token",token) ; } return ResultTool.success(json) ; } /** * 需要 Token 驗(yàn)證的接口 */ @PostMapping("/info") public JSONObject info (){ return ResultTool.success("info") ; } /** * 根據(jù)請(qǐng)求頭的token獲取userId * @param request * @return */ @GetMapping("/getUserInfo") public JSONObject getUserInfo(HttpServletRequest request){ String usernameFromToken = jwtConfig.getUsernameFromToken(request.getHeader("token")); return ResultTool.success(usernameFromToken) ; } }
用PostMan測(cè)試工具測(cè)試一下,訪問(wèn)登錄接口,當(dāng)對(duì)賬號(hào)密碼驗(yàn)證通過(guò)時(shí),則返回一個(gè)token給客戶端 說(shuō)明:token是在請(qǐng)求頭處,request.getHeader()得到token。
當(dāng)直接去訪問(wèn)info接口時(shí),會(huì)返回token為空的異常 當(dāng)在請(qǐng)求頭加上正確token時(shí),則攔截器驗(yàn)證通過(guò),可以正常訪問(wèn)到接口
當(dāng)在請(qǐng)求頭加入一個(gè)錯(cuò)誤token,則會(huì)返回token失效的異常 接下來(lái)測(cè)試一下獲取用戶信息,因?yàn)檫@里存的subject為userId,所以直接返回上面寫死的假數(shù)據(jù)5
三,知識(shí)點(diǎn)概述
1,@Resource注解
- @Autowired與@Resource都可以用來(lái)裝配bean. 都可以寫在字段上,或?qū)懺趕etter方法上。
- @Autowired默認(rèn)按類型裝配(這個(gè)注解是屬于spring的),默認(rèn)情況下必須要求依賴對(duì)象必須存在,如果要允許null值,可以設(shè)置它的required屬性為false,如:@Autowired(required=false) 3,@Resource(這個(gè)注解屬于J2EE的),默認(rèn)按照名稱進(jìn)行裝配,名稱可以通過(guò)name屬性進(jìn)行指定,如果沒(méi)有指定name屬性,當(dāng)注解寫在字段上時(shí),默認(rèn)取字段名進(jìn)行安裝名稱查找,如果注解寫在setter方法上默認(rèn)取屬性名進(jìn)行裝配。當(dāng)找不到與名稱匹配的bean時(shí)才按照類型進(jìn)行裝配。但是需要注意的是,如果name屬性一旦指定,就只會(huì)按照名稱進(jìn)行裝配。
2,@ConfigurationProperties注解
@ConfigurationProperties:告訴SpringBoot將本類中的所有屬性和配置文件中相關(guān)的配置進(jìn)行綁定;prefix = "xxx":配置文件中哪個(gè)下面的所有屬性進(jìn)行一一映射
到此這篇關(guān)于SpringBoot集成JWT令牌詳細(xì)說(shuō)明的文章就介紹到這了,更多相關(guān)SpringBoot集成JWT令牌內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
MyBatis中RowBounds實(shí)現(xiàn)內(nèi)存分頁(yè)
RowBounds是MyBatis提供的一種內(nèi)存分頁(yè)方式,適用于小數(shù)據(jù)量的分頁(yè)場(chǎng)景,本文就來(lái)詳細(xì)的介紹一下,具有一定的參考價(jià)值,感興趣的可以了解一下2024-12-12java abstract class interface之間的區(qū)別介紹
含有abstract修飾符的class即為抽象類,abstract 類不能創(chuàng)建的實(shí)例對(duì)象,abstract class類中定義抽象方法必須在具體(Concrete)子類中實(shí)現(xiàn),所以,不能有抽象構(gòu)造方法或抽象靜態(tài)方法2012-11-11SpringBoot動(dòng)態(tài)定時(shí)任務(wù)、動(dòng)態(tài)Bean、動(dòng)態(tài)路由詳解
這篇文章主要介紹了SpringBoot動(dòng)態(tài)定時(shí)任務(wù)、動(dòng)態(tài)Bean、動(dòng)態(tài)路由詳解,之前用過(guò)Spring中的定時(shí)任務(wù),通過(guò)@Scheduled注解就能快速的注冊(cè)一個(gè)定時(shí)任務(wù),但有的時(shí)候,我們業(yè)務(wù)上需要?jiǎng)討B(tài)創(chuàng)建,或者根據(jù)配置文件、數(shù)據(jù)庫(kù)里的配置去創(chuàng)建定時(shí)任務(wù),需要的朋友可以參考下2023-10-10Mybatis之@ResultMap,@Results,@Result注解的使用
這篇文章主要介紹了Mybatis之@ResultMap,@Results,@Result注解的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12SpringBoot居然有44種應(yīng)用啟動(dòng)器,你都知道嗎
很多人都不知道SpringBoot應(yīng)用啟動(dòng)器竟然有44個(gè),本文就一起來(lái)介紹一下,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2021-01-01Java編程打印購(gòu)物小票實(shí)現(xiàn)代碼
這篇文章主要介紹了Java編程打印購(gòu)物小票實(shí)現(xiàn)代碼,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11