国产成人精品久久免费动漫-国产成人精品天堂-国产成人精品区在线观看-国产成人精品日本-a级毛片无码免费真人-a级毛片毛片免费观看久潮喷

您的位置:首頁技術(shù)文章
文章詳情頁

利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼

瀏覽:4日期:2023-04-03 16:45:53

JSON Web Token是目前最流行的跨域認(rèn)證解決方案,,適合前后端分離項(xiàng)目通過Restful API進(jìn)行數(shù)據(jù)交互時(shí)進(jìn)行身份認(rèn)證

利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼

關(guān)于Shiro整合JWT,可以看這里:Springboot實(shí)現(xiàn)Shiro+JWT認(rèn)證

概述

由于概念性內(nèi)容網(wǎng)上多的是,所以就不詳細(xì)介紹了

具體可以看這里:阮一峰大佬的博客

我總結(jié)幾個(gè)重點(diǎn):

JWT,全稱Json Web Token,是一種令牌認(rèn)證的方式

長(zhǎng)相:

利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼

頭部:放有簽名算法和令牌類型(這個(gè)就是JWT) 載荷:你在令牌上附帶的信息:比如用戶的id,用戶的電話號(hào)碼,這樣以后驗(yàn)證了令牌之后就可以直接從這里獲取信息而不用再查數(shù)據(jù)庫了 簽名:用來加令牌的

安全性:由于載荷里的內(nèi)容都是用BASE64處理的,所以是沒有保密性的(因?yàn)锽ASE64是對(duì)稱的),但是由于簽名認(rèn)證的原因,其他人很難偽造數(shù)據(jù)。不過這也意味著,你不能把敏感信息比如密碼放入載荷中,畢竟這種可以被別人直接看到的,但是像用戶id這種就無所謂了

工作流程登錄階段

用戶首次登錄,通過賬號(hào)密碼比對(duì),判定是否登錄成功,如果登錄成功的話,就生成一個(gè)jwt字符串,然后放入一些附帶信息,返回給客戶端。

利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼

這個(gè)jwt字符串里包含了有用戶的相關(guān)信息,比如這個(gè)用戶是誰,他的id是多少,這個(gè)令牌的有效時(shí)間是多久等等。下次用戶登錄的時(shí)候,必須把這個(gè)令牌也一起帶上。

認(rèn)證階段

這里需要和前端統(tǒng)一約定好,在發(fā)起請(qǐng)求的時(shí)候,會(huì)把上次的token放在請(qǐng)求頭里的某個(gè)位置一起發(fā)送過來,后端接受到請(qǐng)求之后,會(huì)解析jwt,驗(yàn)證jwt是否合法,有沒有被偽造,是否過期,到這里,驗(yàn)證過程就完成了。

利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼

不過服務(wù)器同樣可以從驗(yàn)證后的jwt里獲取用戶的相關(guān)信息,從而減少對(duì)數(shù)據(jù)庫的查詢。

比如我們有這樣一個(gè)業(yè)務(wù):“通過用戶電話號(hào)碼查詢用戶余額”

如果我們?cè)趈wt的載荷里事先就放有電話號(hào)碼這個(gè)屬性,那么我們就可以避免先去數(shù)據(jù)庫根據(jù)用戶id查詢用戶電話號(hào)碼,而直接拿到電話號(hào)碼,然后執(zhí)行接下里的業(yè)務(wù)邏輯。

關(guān)于有效期

由于jwt是直接給用戶的,只要能驗(yàn)證成功的jwt都可以被視作登錄成功,所以,如果不給jwt設(shè)置一個(gè)過期時(shí)間的話,用戶只要存著這個(gè)jwt,就相當(dāng)于永遠(yuǎn)登錄了,而這是不安全的,因?yàn)槿绻@個(gè)令牌泄露了,那么服務(wù)器是沒有任何辦法阻止該令牌的持有者訪問的(因?yàn)槟玫竭@個(gè)令牌就等于隨便冒充你身份訪問了),所以往往jwt都會(huì)有一個(gè)有效期,通常存在于載荷部分,下面是一段生成jwt的java代碼:

return JWT.create().withAudience(userId) .withIssuedAt(new Date()) <---- 發(fā)行時(shí)間 .withExpiresAt(expiresDate) <---- 有效期 .withClaim('sessionId', sessionId) .withClaim('userName', userName) .withClaim('realName', realName) .sign(Algorithm.HMAC256(userId+'HelloLehr'));

在實(shí)際的開發(fā)中,令牌的有效期往往是越短越安全,因?yàn)榱钆茣?huì)頻繁變化,即使有某個(gè)令牌被別人盜用,也會(huì)很快失效。但是有效期短也會(huì)導(dǎo)致用戶體驗(yàn)不好(總是需要重新登錄),所以這時(shí)候就會(huì)出現(xiàn)另外一種令牌—refresh token刷新令牌。刷新令牌的有效期會(huì)很長(zhǎng),只要刷新令牌沒有過期,就可以再申請(qǐng)另外一個(gè)jwt而無需登錄(且這個(gè)過程是在用戶訪問某個(gè)接口時(shí)自動(dòng)完成的,用戶不會(huì)感覺到令牌替換),對(duì)于刷新令牌的具體實(shí)現(xiàn)這里就不詳細(xì)講啦(其實(shí)因?yàn)槲乙矝]深入研究過XD…)

對(duì)比Session

在傳統(tǒng)的session會(huì)話機(jī)制中,服務(wù)器識(shí)別用戶是通過用戶首次訪問服務(wù)器的時(shí)候,給用戶一個(gè)sessionId,然后把用戶對(duì)應(yīng)的會(huì)話記錄放在服務(wù)器這里,以后每次通過sessionId來找到對(duì)應(yīng)的會(huì)話記錄。這樣雖然所有的數(shù)據(jù)都存在服務(wù)器上是安全的,但是對(duì)于分布式的應(yīng)用來說,就需要考慮session共享的問題了,不然同一個(gè)用戶的sessionId的請(qǐng)求被自動(dòng)分配到另外一個(gè)服務(wù)器上就等于失效了

而Jwt不但可以用于登錄認(rèn)證,也把相應(yīng)的數(shù)據(jù)返回給了用戶(就是載荷里的內(nèi)容),通過簽名來保證數(shù)據(jù)的真實(shí)性,該應(yīng)用的各個(gè)服務(wù)器上都有統(tǒng)一的驗(yàn)證方法,只要能通過驗(yàn)證,就說明你的令牌是可信的,我就可以從你的令牌上獲取你的信息,知道你是誰了,從而減輕了服務(wù)器的壓力,而且也對(duì)分布式應(yīng)用更為友好。(畢竟就不用擔(dān)心服務(wù)器session的分布式存儲(chǔ)問題了)

整合Springboot導(dǎo)入java-jwt包

導(dǎo)入java-jwt包:

這個(gè)包里實(shí)現(xiàn)了一系列jwt操作的api(包括上面講到的怎么校驗(yàn),怎么生成jwt等等)

如果你是Maven玩家:

pom.xml里寫入

<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt --><dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.3</version></dependency>

如果你是Gradle玩家:

build.gradle里寫入

compile group: ’com.auth0’, name: ’java-jwt’, version: ’3.8.3’

如果你是其他玩家:

maven中央倉庫地址點(diǎn)這里

工具類的編寫

代碼如下:

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.interfaces.Claim;import com.auth0.jwt.interfaces.DecodedJWT;import java.io.Serializable;import java.util.Calendar;import java.util.Date;/** * @author Lehr * @create: 2020-02-04 */public class JwtUtils { /** 簽發(fā)對(duì)象:這個(gè)用戶的id 簽發(fā)時(shí)間:現(xiàn)在 有效時(shí)間:30分鐘 載荷內(nèi)容:暫時(shí)設(shè)計(jì)為:這個(gè)人的名字,這個(gè)人的昵稱 加密密鑰:這個(gè)人的id加上一串字符串 */ public static String createToken(String userId,String realName, String userName) { Calendar nowTime = Calendar.getInstance(); nowTime.add(Calendar.MINUTE,30); Date expiresDate = nowTime.getTime(); return JWT.create().withAudience(userId) //簽發(fā)對(duì)象 .withIssuedAt(new Date()) //發(fā)行時(shí)間 .withExpiresAt(expiresDate) //有效時(shí)間 .withClaim('userName', userName) //載荷,隨便寫幾個(gè)都可以 .withClaim('realName', realName) .sign(Algorithm.HMAC256(userId+'HelloLehr')); //加密 } /** * 檢驗(yàn)合法性,其中secret參數(shù)就應(yīng)該傳入的是用戶的id * @param token * @throws TokenUnavailable */ public static void verifyToken(String token, String secret) throws TokenUnavailable { DecodedJWT jwt = null; try { JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret+'HelloLehr')).build(); jwt = verifier.verify(token); } catch (Exception e) { //效驗(yàn)失敗 //這里拋出的異常是我自定義的一個(gè)異常,你也可以寫成別的 throw new TokenUnavailable(); } } /** * 獲取簽發(fā)對(duì)象 */ public static String getAudience(String token) throws TokenUnavailable { String audience = null; try { audience = JWT.decode(token).getAudience().get(0); } catch (JWTDecodeException j) { //這里是token解析失敗 throw new TokenUnavailable(); } return audience; } /** * 通過載荷名字獲取載荷的值 */ public static Claim getClaimByName(String token, String name){ return JWT.decode(token).getClaim(name); }}

一點(diǎn)小說明:

關(guān)于jwt生成時(shí)的加密和驗(yàn)證方法:

jwt的驗(yàn)證其實(shí)就是驗(yàn)證jwt最后那一部分(簽名部分)。這里在指定簽名的加密方式的時(shí)候,還傳入了一個(gè)字符串來加密,所以驗(yàn)證的時(shí)候不但需要知道加密算法,還需要獲得這個(gè)字符串才能成功解密,提高了安全性。我這里用的是id來,比較簡(jiǎn)單,如果你想更安全一點(diǎn),可以把用戶密碼作為這個(gè)加密字符串,這樣就算是這段業(yè)務(wù)代碼泄露了,也不會(huì)引發(fā)太大的安全問題(畢竟我的id是誰都知道的,這樣令牌就可以被偽造,但是如果換成密碼,只要數(shù)據(jù)庫沒事那就沒人知道)

關(guān)于獲得載荷的方法:

可能有人會(huì)覺得奇怪,為什么不需要解密不需要verify就能夠獲取到載荷里的內(nèi)容呢?原因是,本來載荷就只是用Base64處理了,就沒有加密性,所以能直接獲取到它的值,但是至于可不可以相信這個(gè)值的真實(shí)性,就是要看能不能通過驗(yàn)證了,因?yàn)樽詈蟮暮灻糠质呛颓懊骖^部和載荷的內(nèi)容有關(guān)聯(lián)的,所以一旦簽名驗(yàn)證過了,那就說明前面的載荷是沒有被改過的。

注解類的編寫

在controller層上的每個(gè)方法上,可以使用這些注解,來決定訪問這個(gè)方法是否需要攜帶token,由于默認(rèn)是全部檢查,所以對(duì)于某些特殊接口需要有免驗(yàn)證注解

免驗(yàn)證注解

@PassToken:跳過驗(yàn)證,通常是入口方法上用這個(gè),比如登錄接口

import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * @author Lehr * @create: 2020-02-03 */@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)public @interface PassToken { boolean required() default true;}攔截器的編寫

配置類

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 lehr*/@Configurationpublic class JwtInterceptorConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { //默認(rèn)攔截所有路徑 registry.addInterceptor(authenticationInterceptor()) .addPathPatterns('/**'); } @Bean public JwtAuthenticationInterceptor authenticationInterceptor() { return new JwtAuthenticationInterceptor(); }}

攔截器

import com.auth0.jwt.interfaces.Claim;import com.imlehr.internship.annotation.PassToken;import com.imlehr.internship.dto.AccountDTO;import com.imlehr.internship.exception.NeedToLogin;import com.imlehr.internship.exception.UserNotExist;import com.imlehr.internship.service.AccountService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Method;import java.util.Map;/** * @author Lehr * @create: 2020-02-03 */public class JwtAuthenticationInterceptor implements HandlerInterceptor { @Autowired AccountService accountService; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws Exception { // 從請(qǐng)求頭中取出 token 這里需要和前端約定好把jwt放到請(qǐng)求頭一個(gè)叫token的地方 String token = httpServletRequest.getHeader('token'); // 如果不是映射到方法直接通過 if (!(object instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) object; Method method = handlerMethod.getMethod(); //檢查是否有passtoken注釋,有則跳過認(rèn)證 if (method.isAnnotationPresent(PassToken.class)) { PassToken passToken = method.getAnnotation(PassToken.class); if (passToken.required()) { return true; } } //默認(rèn)全部檢查 else { System.out.println('被jwt攔截需要驗(yàn)證'); // 執(zhí)行認(rèn)證 if (token == null) { //這里其實(shí)是登錄失效,沒token了 這個(gè)錯(cuò)誤也是我自定義的,讀者需要自己修改 throw new NeedToLogin(); } // 獲取 token 中的 user Name String userId = JwtUtils.getAudience(token); //找找看是否有這個(gè)user 因?yàn)槲覀冃枰獧z查用戶是否存在,讀者可以自行修改邏輯 AccountDTO user = accountService.getByUserName(userId); if (user == null) { //這個(gè)錯(cuò)誤也是我自定義的 throw new UserNotExist(); } // 驗(yàn)證 token JwtUtils.verifyToken(token, userId) //獲取載荷內(nèi)容 String userName = JwtUtils.getClaimByName(token, 'userName').asString(); String realName = JwtUtils.getClaimByName(token, 'realName').asString(); //放入attribute以便后面調(diào)用 request.setAttribute('userName', userName); request.setAttribute('realName', realName); return true; } return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { }}

這段代碼的執(zhí)行邏輯大概是這樣的:

目標(biāo)方法是否有注解?如果有PassToken的話就不用執(zhí)行后面的驗(yàn)證直接放行,不然全部需要驗(yàn)證 開始驗(yàn)證:有沒有token?沒有?那么返回錯(cuò)誤 從token的audience中獲取簽發(fā)對(duì)象,查看是否有這個(gè)用戶(有可能客戶端造假,有可能這個(gè)用戶的賬戶被凍結(jié)了),查看用戶的邏輯就是調(diào)用Service方法直接比對(duì)即可 檢驗(yàn)Jwt的有效性,如果無效或者過期了就返回錯(cuò)誤 Jwt有效性檢驗(yàn)成功:把Jwt的載荷內(nèi)容獲取到,可以在接下來的controller層中直接使用了(具體使用方法看后面的代碼)接口的編寫

這里設(shè)計(jì)了兩個(gè)接口:登錄和查詢名字,來模擬一個(gè)迷你業(yè)務(wù),其中后者需要登錄之后才能使用,大致流程如下:

利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼

登錄代碼

/** * 用戶登錄:獲取賬號(hào)密碼并登錄,如果不對(duì)就報(bào)錯(cuò),對(duì)了就返回用戶的登錄信息 * 同時(shí)生成jwt返回給用戶 * * @return * @throws LoginFailed 這個(gè)LoginFailed也是我自定義的 */ @PassToken @GetMapping(value = '/login') public AccountVO login(String userName, String password) throws LoginFailed{ try{ service.login(userName,password); } catch (AuthenticationException e) { throw new LoginFailed(); } //如果成功了,聚合需要返回的信息 AccountVO account = accountService.getAccountByUserName(userName); //給分配一個(gè)token 然后返回 String jwtToken = JwtUtils.createToken(account); //我的處理方式是把token放到accountVO里去了 account.setToken(jwtToken); return account; }

業(yè)務(wù)代碼

這里列舉一個(gè)需要登錄,用來測(cè)試用戶名字的接口(其中用戶的名字來源于jwt的載荷部分)

@GetMapping(value = '/username') public String checkName(HttpServletRequest req) { //之前在攔截器里設(shè)置好的名字現(xiàn)在可以取出來直接用了 String name = (String) req.getAttribute('userName'); return name; }

到此這篇關(guān)于利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼的文章就介紹到這了,更多相關(guān)Springboot Jwt認(rèn)證內(nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Spring
相關(guān)文章:
主站蜘蛛池模板: 久久精品视频9 | 久久福利青草免费精品 | 99在线免费视频 | 日韩在线观看中文字幕 | 国产丝袜不卡一区二区 | 精品欧美一区二区在线看片 | 三级黄色片网址 | 国产一区日韩二区欧美三 | 色综合天天综合网看在线影院 | 国产成人精品一区二三区 | 久久精品视频免费 | 一级做a爰片性色毛片中国 一级做a爰性色毛片 | 日本一区二区三区欧美在线观看 | 美女张开腿让人捅 | 免费观看欧美一级高清 | 国产日本欧美高清免费区 | 国产精品亚洲二区在线 | 日本手机在线视频 | 美女黄网站色一级毛片 | 这里只有精品国产 | 亚洲国产大片 | 精品视频免费在线观看 | 香蕉99国内自产自拍视频 | 国产成人一区二区 | 毛片国产 | 黄性色| 亚洲综合久久1区2区3区 | 亚洲一区免费在线观看 | 免费又黄又爽又猛大片午夜 | 免费看欧美xxx片 | 香蕉依依精品视频在线播放 | 真人一级毛片 | 香焦视频在线观看黄 | 日韩欧美在线一区二区三区 | 一级待一黄aaa大片在线还看 | 日本丶国产丶欧美色综合 | 国产在亚洲线视频观看 | 久久99久久成人免费播放 | 欧美一级特黄特色大片免费 | 九九99视频在线观看视频观看 | 国产全部理论片线观看 |