欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時(shí)間:2020年12月17日 11:26:17   作者:一個(gè)JavaBean  
這篇文章主要介紹了利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

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

image-20200221234116337

關(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)相:

image-20200221230910489

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

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

工作流程

登錄階段

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

image-20200221234215011

這個(gè)jwt字符串里包含了有用戶的相關(guān)信息,比如這個(gè)用戶是誰(shuí),他的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)證過程就完成了。

image-20200221234900958

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

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

如果我們?cè)趈wt的載荷里事先就放有電話號(hào)碼這個(gè)屬性,那么我們就可以避免先去數(shù)據(jù)庫(kù)根據(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)證,就說明你的令牌是可信的,我就可以從你的令牌上獲取你的信息,知道你是誰(shuí)了,從而減輕了服務(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中央倉(cāng)庫(kù)地址點(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是誰(shuí)都知道的,這樣令牌就可以被偽造,但是如果換成密碼,只要數(shù)據(jù)庫(kù)沒事那就沒人知道)

關(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
*/
@Configuration
public 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ù),其中后者需要登錄之后才能使用,大致流程如下:

image-20200222005538848

登錄代碼

/**
 * 用戶登錄:獲取賬號(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)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot項(xiàng)目實(shí)現(xiàn)關(guān)閉數(shù)據(jù)庫(kù)配置和springSecurity

    SpringBoot項(xiàng)目實(shí)現(xiàn)關(guān)閉數(shù)據(jù)庫(kù)配置和springSecurity

    這篇文章主要介紹了SpringBoot項(xiàng)目實(shí)現(xiàn)關(guān)閉數(shù)據(jù)庫(kù)配置和springSecurity的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • java 最新Xss攻擊與防護(hù)(全方位360°詳解)

    java 最新Xss攻擊與防護(hù)(全方位360°詳解)

    這篇文章主要介紹了java 最新Xss攻擊與防護(hù)(全方位360°詳解),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Java實(shí)戰(zhàn)之課程信息管理系統(tǒng)的實(shí)現(xiàn)

    Java實(shí)戰(zhàn)之課程信息管理系統(tǒng)的實(shí)現(xiàn)

    這篇文章主要介紹了如何利用Java實(shí)現(xiàn)課程信息管理系統(tǒng),文中采用到的技術(shù)有:Springboot、SpringMVC、MyBatis、FreeMarker等,感興趣的可以了解一下
    2022-04-04
  • Java 精煉解讀方法的定義與使用

    Java 精煉解讀方法的定義與使用

    Java語(yǔ)言中的“方法”(Method)在其他語(yǔ)言當(dāng)中也可能被稱為“函數(shù)”(Function)。對(duì)于一些復(fù)雜的代碼邏輯,如果希望重復(fù)使用這些代碼,并且做到“隨時(shí)任意使用”,那么就可以將這些代碼放在一個(gè)大括號(hào)“{}”當(dāng)中,并且起一個(gè)名字。使用的時(shí)候,直接找到名字調(diào)用即可
    2022-03-03
  • Spring中基于XML的面向切面編程(AOP)詳解

    Spring中基于XML的面向切面編程(AOP)詳解

    這篇文章主要詳細(xì)介紹了Spring中基于XML的面向切面編程(AOP),文中通過代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-04-04
  • Mybatis Mapper接口工作原理實(shí)例解析

    Mybatis Mapper接口工作原理實(shí)例解析

    這篇文章主要介紹了Mybatis Mapper接口工作原理實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • MyBatis利用MyCat實(shí)現(xiàn)多租戶的簡(jiǎn)單思路分享

    MyBatis利用MyCat實(shí)現(xiàn)多租戶的簡(jiǎn)單思路分享

    這篇文章主要給大家介紹了關(guān)于MyBatis利用MyCat實(shí)現(xiàn)多租戶的簡(jiǎn)單思路的相關(guān)資料,文中的多租戶是基于多數(shù)據(jù)庫(kù)進(jìn)行實(shí)現(xiàn)的,數(shù)據(jù)是通過不同數(shù)據(jù)庫(kù)進(jìn)行隔離,需要的朋友可以參考借鑒,下面來一起看看吧。
    2017-06-06
  • Java多線程 ReentrantLock互斥鎖詳解

    Java多線程 ReentrantLock互斥鎖詳解

    這篇文章主要介紹了Java多線程 ReentrantLock互斥鎖詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • Java的LinkedHashSet解析

    Java的LinkedHashSet解析

    這篇文章主要介紹了Java的LinkedHashSet解析,Set接口的哈希表和鏈表實(shí)現(xiàn),具有可預(yù)測(cè)的迭代順序,此實(shí)現(xiàn)與 HashSet的不同之處在于它維護(hù)一個(gè)雙向鏈表,該列表貫穿其所有條目,這個(gè)鏈表定義了迭代順序,需要的朋友可以參考下
    2023-09-09
  • Java concurrency之互斥鎖_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    Java concurrency之互斥鎖_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理

    本文通過示例代碼給大家介紹了Java concurrency之互斥鎖的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2017-06-06

最新評(píng)論