springboot集成JWT實(shí)現(xiàn)身份認(rèn)證(權(quán)鑒)的方法步驟
一、什么是JWT
JSON Web Token (JWT),它是目前最流行的跨域身份驗(yàn)證解決方案?,F(xiàn)在的項(xiàng)目開(kāi)發(fā)一般都是前端端分離,這就涉及到跨域和權(quán)鑒問(wèn)題。
二、JWT組成
由三部分組成:頭部(Header)、載荷(Payload)與簽名(signature)
頭部(Header):
頭部信息由兩部分組成1、令牌的類型,即JWT;2、使用的簽名算法,例如HMASSHA256或RSA;
{ "alg": "HS256", "typ": "JWT" }
這個(gè)json中的typ屬性,用來(lái)標(biāo)識(shí)整個(gè)token字符串是一個(gè)JWT字符串;它的alg屬性,用來(lái)說(shuō)明這個(gè)JWT簽發(fā)的時(shí)候所使用的簽名和摘要算法,typ跟alg屬性的全稱其實(shí)是type algorithm,分別是類型跟算法的意思。之所以都用三個(gè)字母來(lái)表示,也是基于JWT最終字串大小的考慮,同時(shí)也是跟JWT這個(gè)名稱保持一致,這樣就都是三個(gè)字符了…typ跟alg是JWT中標(biāo)準(zhǔn)中規(guī)定的屬性名稱。
載荷(Payload):
payload用來(lái)承載要傳遞的數(shù)據(jù),它的json結(jié)構(gòu)實(shí)際上是對(duì)JWT要傳遞的數(shù)據(jù)的一組聲明,這些聲明被JWT標(biāo)準(zhǔn)稱為claims,它的一個(gè)“屬性值對(duì)”其實(shí)就是一個(gè)claim(要求), 每一個(gè)claim的都代表特定的含義和作用。
我們可以在claim里放一些業(yè)務(wù)信息。
簽名(signature):
簽名是把header和payload對(duì)應(yīng)的json結(jié)構(gòu)進(jìn)行base64url編碼之后得到的兩個(gè)串用 '英文句點(diǎn)號(hào)' 拼接起來(lái),然后根據(jù)header里面alg指定的簽名算法生成出來(lái)的。
算法不同,簽名結(jié)果不同。以alg: HS256為例來(lái)說(shuō)明前面的簽名如何來(lái)得到。
按照前面alg可用值的說(shuō)明,HS256其實(shí)包含的是兩種算法:HMAC算法和SHA256算法,前者用于生成摘要,后者用于對(duì)摘要進(jìn)行數(shù)字簽名。這兩個(gè)算法也可以用HMACSHA256來(lái)統(tǒng)稱
jwt數(shù)據(jù)結(jié)構(gòu)圖:
三、JWT運(yùn)行原理
1.第一次發(fā)送登錄請(qǐng)求,必然會(huì)攜帶用戶信息uname和pwd
2.通過(guò)用戶信息uname和pwd登錄成功,會(huì)將用戶信息通過(guò)jwt工具類生成一個(gè)加密的字符串
3.加密字符串 會(huì)以response header 響應(yīng)頭的形式 相應(yīng)到前端
4.前端服務(wù)器會(huì)有響應(yīng)攔截器攔截,截取到響應(yīng)頭承載的jwt串,又會(huì)放到Vuex中
5.當(dāng)?shù)诙握?qǐng)求,前端服務(wù)器中有一個(gè)請(qǐng)求攔截器,會(huì)將Vuex中的jwt串放入request header 請(qǐng)求當(dāng)中
6.當(dāng)請(qǐng)求通過(guò)跨域的方式到達(dá)后臺(tái)服務(wù)器,后臺(tái)服務(wù)器中又有一個(gè)過(guò)濾器,會(huì)截取到 request header 請(qǐng)求當(dāng)中的jwt串
7.jwt工具類會(huì)對(duì)jwt串進(jìn)行解析,解析成用戶信息,最終進(jìn)行校驗(yàn)
四、springboot集成JWT
整體思路:
當(dāng)前端訪問(wèn)后臺(tái)登錄接口login時(shí),先根據(jù)用戶名和密碼判斷用戶表是否存在該用戶,如果存在該用戶,則生成jwt串,可以在jwt串里添加些業(yè)務(wù)信息(比如用登錄賬號(hào),用戶真實(shí)姓名等),并把jwt串返給前端
當(dāng)前端拿到j(luò)wt串后放到所有請(qǐng)求的header中,比如token=jwt串
后端開(kāi)發(fā)一個(gè)filter,攔截所有請(qǐng)求(除login請(qǐng)求外,因?yàn)閘ogin請(qǐng)求還沒(méi)有生成jwt),并從request的header中獲取jwt(即token的值),對(duì)jwt校驗(yàn)和獲取jwt中的業(yè)務(wù)信息,在把這些業(yè)務(wù)信息放到request的header中,這樣方便后端接口直接從header中獲取
如果filter中jwt過(guò)期,或者校驗(yàn)失敗,則返回給前端提示,前端返回登錄頁(yè)面,讓用戶重新登錄。
1、在pom.xml引入依賴
<!--jwt--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.8.3</version> </dependency>
2、開(kāi)發(fā)jwt生成工具類,代碼如下:
package com.lsl.exam.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.lsl.exam.entity.TabUser; import java.util.Date; import java.util.HashMap; import java.util.Map; public class JwtUtil { private static final long EXPIRE_TIME = 1000 * 60 * 60 *24; //設(shè)置私鑰 private static final String TOKEN_SECRET = "aa082c-66rt89-29sr3t-y9t7b8"; /** * 創(chuàng)建攜帶自定義信息和聲明的自定義私鑰的jwt * @param user 用戶信息表 * @return jwt串 */ public static String creatJwt(TabUser user){ //構(gòu)建頭部信息 Map<String,Object> header = new HashMap<>(); header.put("typ","JWT"); header.put("alg","HS256"); //根據(jù)私鑰構(gòu)建密鑰信息 Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET); //根據(jù)當(dāng)前用戶密碼構(gòu)建密鑰信息 // Algorithm algorithm = Algorithm.HMAC256(user.getUserpwd()); //設(shè)置過(guò)期時(shí)間為當(dāng)前時(shí)間一天后 Date nowDate = new Date(); Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME); String jwt = JWT.create().withHeader(header) .withClaim("account",user.getAccount())//業(yè)務(wù)信息:員工號(hào) .withClaim("username",user.getUsername())//業(yè)務(wù)信息:員工姓名 .withClaim("rolename",user.getRoleName())//業(yè)務(wù)信息:角色 .withIssuer("SERVICE")//聲明,簽名是有誰(shuí)生成 例如 服務(wù)器 .withNotBefore(new Date())//聲明,定義在什么時(shí)間之前,該jwt都是不可用的 .withExpiresAt(expireDate)//聲明, 簽名過(guò)期的時(shí)間 .sign(algorithm);//根據(jù)algorithm生成簽名 return jwt; } }
3、后端login接口邏輯如下:
package com.lsl.exam.controller; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.lsl.exam.entity.TabUser; import com.lsl.exam.entity.backresult.ResultVO; import com.lsl.exam.service.ITabRoleService; import com.lsl.exam.service.IUserService; import com.lsl.exam.utils.Base64Util; import com.lsl.exam.utils.JwtUtil; import com.lsl.exam.utils.ResultVoUtil; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @RestController @RequestMapping("/exam") public class UserController { private static final Logger LOG = org.slf4j.LoggerFactory.getLogger("UserController"); @Autowired IUserService userService; @Autowired ITabRoleService roleService; @PostMapping(value = "login",produces = "application/json;charset=UTF-8") @ResponseBody public ResultVO<?> login(@RequestBody Map params){ Map reuslt = new HashMap(); String account = params.get("account") == null ? "" : params.get("account").toString(); String pwd = params.get("pwd") == null ? "" : params.get("pwd").toString(); if ("".equals(account) || "".equals(pwd)){ return ResultVoUtil.error(30000,"用戶名或者密碼不能為空!"); } //pwd解密 String decodePwd = Base64Util.decode(pwd); if ("".contains(decodePwd)){ return ResultVoUtil.error(30000,"密碼錯(cuò)誤!"); } TabUser user = userService.getOne(new QueryWrapper<TabUser>() .eq("account",account) .eq("userpwd",decodePwd)); if (null == user){ return ResultVoUtil.error(30000,"用戶名或者密碼錯(cuò)誤"); } //獲取當(dāng)前用戶擁有的角色 String userId = user.getId(); Map roleMap = new HashMap(); roleMap.put("userId",userId); List<Map> roleList = roleService.qryRoleInfoByUserId(roleMap); List<String> roleNames = new ArrayList<>(); for(Map role : roleList){ roleNames.add(role.get("role").toString()); } user.setRoleName(JSON.toJSONString(roleNames)); //生成帶有業(yè)務(wù)信息的jwt串 String jwt = JwtUtil.creatJwt(user); //把jwt和當(dāng)前用戶信息返給前端 reuslt.put("jwt",jwt); reuslt.put("roleNames",roleNames); reuslt.put("username",user.getUsername()); reuslt.put("account",user.getAccount()); return ResultVoUtil.success(reuslt); } @PostMapping(value = "qryUser",produces = "application/json;charset=UTF-8") @ResponseBody public Object qryUser(HttpServletRequest request){ //這里header中的信息是filter中放進(jìn)去的 String account = request.getHeader("account"); String username = request.getHeader("username"); String rolename = request.getHeader("rolename"); List<TabUser> list = userService.list(); return ResultVoUtil.success(list); } }
4、開(kāi)發(fā)filter,進(jìn)行jwt校驗(yàn)
package com.lsl.exam.filter; import com.alibaba.fastjson.JSON; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.DecodedJWT; import com.lsl.exam.entity.backresult.ResultVO; import com.lsl.exam.utils.ResultVoUtil; import org.apache.tomcat.util.http.MimeHeaders; import org.springframework.stereotype.Component; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * jwt校驗(yàn)過(guò)濾器 */ @Component @WebFilter(filterName = "jwtFilter",urlPatterns = {"/*"}) public class AuthJwtFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; String url = httpServletRequest.getRequestURL().toString(); //配置不進(jìn)行jwt校驗(yàn)的請(qǐng)求路徑 List<String> urlList = new ArrayList<>(); urlList.add("/exam/login"); boolean flag = false; for (String strUrl : urlList){ if (url.contains(strUrl)){ flag = true; } } try { if (!flag){ String token = httpServletRequest.getHeader("token"); //校驗(yàn)token,jwt過(guò)期有jwt自行校驗(yàn),如果超時(shí)了,會(huì)執(zhí)行catch里代碼 DecodedJWT decodeJwt = JWT.require(Algorithm.HMAC256("aa082c-66rt89-29sr3t-y9t7b8")).build().verify(token); //獲取jwt中的業(yè)務(wù)信息 String account = decodeJwt.getClaim("account").asString(); String username = decodeJwt.getClaim("username").asString(); String rolename = decodeJwt.getClaim("rolename").asString(); Map<String, String> headerMap = new HashMap<>(); headerMap.put("account",account); headerMap.put("username",username); headerMap.put("rolename",rolename); //把業(yè)務(wù)信息添加到request的header addHeader(httpServletRequest,headerMap); // Class<?> superclass = servletRequest.getClass().getSuperclass().getSuperclass(); // Field requestField = superclass.getDeclaredField("request"); // requestField.setAccessible(true); // RequestFacade requestFacadeInstance = (RequestFacade) requestField.get(servletRequest); RequestFacade requestFacadeInstance = (RequestFacade)superclass3; // Field requestField1 = requestFacadeInstance.getClass().getDeclaredField("request"); // requestField1.setAccessible(true); // Object requestInstance = requestField1.get(requestFacadeInstance); // Field coyoteRequestField = requestInstance.getClass().getDeclaredField("coyoteRequest"); // coyoteRequestField.setAccessible(true); // // Object coyoRequestInstance = requestField1.get(requestInstance); // Field headersField = coyoRequestInstance.getClass().getDeclaredField("headers"); // headersField.setAccessible(true); // // MimeHeaders headers = (MimeHeaders) headersField.get(coyoRequestInstance); // headers.removeHeader("token"); // headers.addValue("account").setString(account); // headers.addValue("username").setString(username); // headers.addValue("roleid").setString(roleid); // } } catch (Exception e) { //jwt校驗(yàn)失敗,返給前端的code=1,前端要重定向到登錄頁(yè)面 PrintWriter writer = null; servletResponse.setCharacterEncoding("UTF-8"); servletResponse.setContentType("text/html; charset=utf-8"); try { writer = servletResponse.getWriter(); ResultVO vo = ResultVoUtil.successLogout(); String msg = JSON.toJSONString(vo); writer.println(msg); } catch (IOException ex) { } finally { if (writer != null){ writer.close(); } return; } } filterChain.doFilter(servletRequest,servletResponse); } /** * 向request的header中放業(yè)務(wù)信息 * @param request * @param headerMap */ private void addHeader(HttpServletRequest request, Map<String, String> headerMap) { if (headerMap==null||headerMap.isEmpty()){ return; } Class<? extends HttpServletRequest> c=request.getClass(); //System.out.println(c.getName()); System.out.println("request實(shí)現(xiàn)類="+c.getName()); try{ Field requestField=c.getDeclaredField("request"); requestField.setAccessible(true); Object o=requestField.get(request); Field coyoteRequest=o.getClass().getDeclaredField("coyoteRequest"); coyoteRequest.setAccessible(true); Object o2=coyoteRequest.get(o); System.out.println("coyoteRequest實(shí)現(xiàn)類="+o2.getClass().getName()); Field headers=o2.getClass().getDeclaredField("headers"); headers.setAccessible(true); MimeHeaders mimeHeaders=(MimeHeaders) headers.get(o2); for (Map.Entry<String,String> entry:headerMap.entrySet()){ mimeHeaders.removeHeader(entry.getKey()); mimeHeaders.addValue(entry.getKey()).setString(entry.getValue()); } }catch (Exception e){ e.printStackTrace(); } } @Override public void destroy() { } }
到此這篇關(guān)于springboot集成JWT實(shí)現(xiàn)身份認(rèn)證(權(quán)鑒)的方法步驟的文章就介紹到這了,更多相關(guān)springboot JWT身份認(rèn)證內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Springboot+SpringSecurity+JWT實(shí)現(xiàn)用戶登錄和權(quán)限認(rèn)證示例
- SpringBoot+Vue+JWT的前后端分離登錄認(rèn)證詳細(xì)步驟
- Springboot集成Spring Security實(shí)現(xiàn)JWT認(rèn)證的步驟詳解
- Springboot WebFlux集成Spring Security實(shí)現(xiàn)JWT認(rèn)證的示例
- SpringBoot集成Spring security JWT實(shí)現(xiàn)接口權(quán)限認(rèn)證
- 利用Springboot實(shí)現(xiàn)Jwt認(rèn)證的示例代碼
- SpringBoot+SpringSecurity+JWT實(shí)現(xiàn)系統(tǒng)認(rèn)證與授權(quán)示例
- SpringBoot整合SpringSecurity實(shí)現(xiàn)JWT認(rèn)證的項(xiàng)目實(shí)踐
- 基于Springboot實(shí)現(xiàn)JWT認(rèn)證的示例代碼
相關(guān)文章
MyBatis實(shí)現(xiàn)簡(jiǎn)單的數(shù)據(jù)表分月存儲(chǔ)
本文主要介紹了MyBatis實(shí)現(xiàn)簡(jiǎn)單的數(shù)據(jù)表分月存儲(chǔ),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03基于SpringBoot實(shí)現(xiàn)圖片上傳及圖片回顯
本篇文章主要介紹了SpringBoot如何實(shí)現(xiàn)圖片上傳及圖片回顯,文中通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2022-08-08java虛擬機(jī)深入學(xué)習(xí)之內(nèi)存管理機(jī)制
java虛擬機(jī)在程序運(yùn)行時(shí)將內(nèi)存劃分為多個(gè)區(qū)域,每個(gè)區(qū)域作用,生命周期各不相同,下面這篇文章主要給大家介紹了關(guān)于java虛擬機(jī)深入學(xué)習(xí)之內(nèi)存管理機(jī)制的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-11-11JVM內(nèi)存結(jié)構(gòu):程序計(jì)數(shù)器、虛擬機(jī)棧、本地方法棧
JVM 基本上是每家招聘公司都會(huì)問(wèn)到的問(wèn)題,它們會(huì)這么無(wú)聊問(wèn)這些不切實(shí)際的問(wèn)題嗎?很顯然不是。由 JVM 引發(fā)的故障問(wèn)題,無(wú)論在我們開(kāi)發(fā)過(guò)程中還是生產(chǎn)環(huán)境下都是非常常見(jiàn)的2021-06-06