SpringBoot整合JWT實(shí)戰(zhàn)教程
SpringBoot整合JWT實(shí)戰(zhàn)詳解
jwt 介紹就不多說(shuō)了,下面通過(guò)代碼演示開(kāi)發(fā)過(guò)程中jwt 的使用。
(1)在pom.xml中引入對(duì)應(yīng)的jar
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.7.0</version> </dependency>
(2)引入jwt 工具類:token的生成以及獲取對(duì)應(yīng)的token信息
/** * @author : wl * @Description : * @date : 2020/7/3 13:25 */ public class JwtUtil { public static final String AUTHORIZATION_SECRET = "wlcoder"; private static final String UID = "uid"; private static final String USERNAME = "username"; private static final String PASSWORD = "password"; private static final String STATUS = "status"; //創(chuàng)建秘鑰 public static Key getKeyInstance() { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; byte[] bytes = DatatypeConverter.parseBase64Binary(AUTHORIZATION_SECRET); return new SecretKeySpec(bytes, signatureAlgorithm.getJcaName()); } /** * 生成token的方法 * * @param user * @param expire * @return */ public static String generatorToken(SysUser user, int expire) { return Jwts.builder().claim(UID, user.getId()) .claim(USERNAME, user.getUsername()) .claim(PASSWORD, user.getPassword()) .claim(STATUS, user.getStatus()) .setExpiration(DateTime.now().plusSeconds(expire).toDate()) .signWith(SignatureAlgorithm.HS256, getKeyInstance()) .compact(); } /** * 根據(jù)token獲取token中的信息 * * @param token * @return */ public static SysUser getTokenInfo(String token) { Jws<Claims> claimsJws = Jwts.parser().setSigningKey(getKeyInstance()).parseClaimsJws(token); Claims claims = claimsJws.getBody(); return SysUser.builder().id((Integer) claims.get(UID)) .username((String) claims.get(USERNAME)) .password((String) claims.get(PASSWORD)) .status((Integer) claims.get(STATUS)) .build(); } }
(3)添加注解 @NeedToken,@SkipToken ,加在方法上靈活處理對(duì)應(yīng)的請(qǐng)求
/** * @author : wl * @Description : 需要token 驗(yàn)證 * @date : 2020/7/3 11:40 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface NeedToken { boolean required() default true; }
/** * @author : wl * @Description :跳過(guò)token 驗(yàn)證 * @date : 2020/7/3 11:39 */ @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface SkipToken { boolean required() default true; }
(4)添加攔截器,驗(yàn)證前端請(qǐng)求是否需要token
/** * @author : wl * @Description :方法請(qǐng)求攔截 * @date : 2020/7/3 11:47 */ @Slf4j public class AuthenticationInterceptor implements HandlerInterceptor { @Autowired private SysUserService userService; @Autowired private RedisUtil redisUtil; @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws ServletException, IOException { String token = httpServletRequest.getHeader("token"); if (!(object instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod = (HandlerMethod) object; Method method = handlerMethod.getMethod(); //檢查有沒(méi)有跳過(guò)token的注解 if (method.isAnnotationPresent(SkipToken.class)) { SkipToken skipToken = method.getAnnotation(SkipToken.class); if (skipToken.required()) { log.info("該請(qǐng)求無(wú)須token驗(yàn)證。。。"); return true; } } //檢查有沒(méi)有需要token的注解 if (method.isAnnotationPresent(NeedToken.class)) { NeedToken needToken = method.getAnnotation(NeedToken.class); if (needToken.required()) { log.info("該請(qǐng)求需要token驗(yàn)證。。。"); if (Objects.isNull(token)) { throw new BaseException("無(wú)token,請(qǐng)重新登錄"); } try { JwtUtil.getTokenInfo(token); } catch (ExpiredJwtException e) { throw new BaseException("token超時(shí)"); } // SysUser user = userService.findUser(sysUser.getUsername(), sysUser.getPassword()); // if (Objects.isNull(user)) { // throw new BaseException("用戶不存在,請(qǐng)重新登錄"); // } if (!Objects.equals(token, redisUtil.get("ms_notify_token"))) { throw new BaseException("token異常,請(qǐng)重新登錄"); } } } 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 { } }
(5)添加攔截器異常處理 :出現(xiàn)異常直接跳轉(zhuǎn)到登錄頁(yè)面 ,這里有個(gè)坑,前端為ajax請(qǐng)求時(shí)候 使用轉(zhuǎn)發(fā)或者重定向會(huì)失效。因此處理為 判斷請(qǐng)求為ajax請(qǐng)求則設(shè)置返回一個(gè)狀態(tài)碼如:httpServletResponse.setStatus(666); 前端jquery.js中統(tǒng)一判斷處理。
/** * @author : wl * @Description : 攔截異常處理 * @date : 2020/7/5 15:30 */ @Slf4j public class MyWebHandlerException implements HandlerExceptionResolver { @SneakyThrows @Override public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) { log.info("請(qǐng)求出現(xiàn)異常:" + e.getMessage()); e.printStackTrace(); // return new ModelAndView("redirect:/login"); ModelAndView modelAndView = new ModelAndView(); String type = httpServletRequest.getHeader("X-Requested-With"); if (Objects.equals(type, "XMLHttpRequest")) { //是ajax請(qǐng)求 httpServletResponse.setStatus(666); httpServletResponse.setContentType("text/javascript; charset=utf-8"); httpServletResponse.getWriter().write(e.getMessage()); return modelAndView; } else { modelAndView.setViewName("/login"); return modelAndView; } } }
jquery.js中添加對(duì)應(yīng)狀態(tài)碼處理:
//自定義異常信息: 跳轉(zhuǎn)到登錄頁(yè)面 jQuery.ajaxSetup({ statusCode:{ 666:function(data){ alert(data.responseText); window.location.href="/login"; } } })
(6) 添加webConfig配置 實(shí)現(xiàn)WebMvcConfigurer,引入自定義的token攔截以及異常處理
/** * @author : wl * @Description :web攔截器 * @date : 2020/7/3 11:46 */ @Configuration public class webConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authenticationInterceptor()).addPathPatterns("/**"); } @Bean public AuthenticationInterceptor authenticationInterceptor() { return new AuthenticationInterceptor(); } @Bean public WebMvcConfigurer webMvcConfigurer() { WebMvcConfigurer adapter = new WebMvcConfigurer() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("login"); registry.addViewController("/index.html").setViewName("login"); } }; return adapter; } /* * 異常攔截處理 * */ @Override public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) { resolvers.add(new MyWebHandlerException()); } }
到這里基本配置基本差不多了,web應(yīng)用中怎么處理呢?例如登錄功能:
登錄后端代碼:
/** * @author : wl * @Description : * @date : 2020/7/2 16:46 */ @Controller public class Login { @Autowired private SysUserService sysUserService; @Autowired private RedisUtil redisUtil; @SkipToken @RequestMapping("/login") public String toLogin() { System.out.println("跳轉(zhuǎn)到登錄頁(yè)面"); return "login"; } @SkipToken @RequestMapping("/index") public String toIndex() { System.out.println("跳轉(zhuǎn)到主頁(yè)面"); return "index"; } @SkipToken @ResponseBody @RequestMapping("/loginIn") public ResultUtil loginIn(String username, String password) { try { SysUser user = sysUserService.findUser(username, password); if (null != user) { user.setPassword(password); String token = JwtUtil.generatorToken(user, 60*60); //token 保存在redis中 redisUtil.set("ms_notify_token", token); return ResultUtil.ok().data("msg", token).message("登錄成功"); } else { return ResultUtil.error().data("msg", "error").message("用戶不存在"); } } catch (BaseException e) { return ResultUtil.error().data("msg", e.getMessage()).message("登陸失敗"); } } }
登錄前端代碼:這里需要注意的是 配置window.location.href 不生效 需要檢查是否設(shè)置為form 表單。
前端接受到token存儲(chǔ)在localStorage: localStorage.setItem("token",data.data.msg);
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>登錄</title> <link href="css/bootstrap.min.css" rel="stylesheet"> <link href="css/signin.css" rel="stylesheet"> </head> <body class="text-center"> <div class="form-signin"> <img class="mb-4" th:src="@{img/my.svg}" alt="" width="80" height="80"> <h1 class="h3 mb-3 font-weight-normal">請(qǐng)登錄</h1> <label class="sr-only">用戶名</label> <input type="text" name="username" id="username" class="form-control" th:placeholder="用戶名" required="required" autofocus=""> <p class=""></p> <label class="sr-only">密碼</label> <input type="password" name="password" id="password" class="form-control" th:placeholder="密碼" required="required" > <button class="btn btn-lg btn-primary btn-block" th:onclick="loginIn()">登錄</button> </div> </body> <script type="text/javascript" src="/js/jquery-3.5.1.js"></script> <script type="text/javascript" src="/js/bootstrap.min.js"></script> <script> function loginIn() { debugger; var username = $('#username').val(); var password = $('#password').val(); if(""==username || ""==password){ alert("用戶信息不完整,請(qǐng)檢查!"); return; } $.ajax({ url: '/loginIn', type: "post", data: {'username': username, 'password': password}, success: function (data) { if (data.success) { //alert(data.message); localStorage.setItem("token",data.data.msg); location.href = "/index"; // window.location.href 不生效 檢查是否為form 表單 } else { alert(data.message) } }, error: function () { alert("登錄失敗") } }); } </script> </html>
若是需要token驗(yàn)證,前端對(duì)應(yīng)的ajax 請(qǐng)求需要加上headers 如:
//禁用,啟用 function disable_config(nid, status) { $.ajax({ url: '/notify/updateStatus', data: {'nid': nid, 'status': status}, type: "post", headers: {"token": localStorage.getItem("token")}, success: function (data) { if (data.success) { location.reload(); alert(data.message); } else { alert(data.message + ":" + data.data.msg) } } }); }
后端對(duì)應(yīng)方法上需要添加注解@NeedToken 如:
/** * 禁用 、啟用 */ @NeedToken @ResponseBody @SysLogAnnotation("禁用 、啟用 配置") @RequestMapping(value = "/updateStatus") public ResultUtil updateStatus(HttpServletRequest request, String nid, int status) { String config_status = (status == 1 ? "啟用" : "禁用"); try { notifyConfigService.updateStatus(nid, status); } catch (BaseException e) { return ResultUtil.error().data("msg", e.getMessage()).message(config_status + "配置失敗"); } return ResultUtil.ok().data("msg", "success").message(config_status + "配置成功"); }
設(shè)計(jì)流程基本如上述代碼所示,詳細(xì)代碼可以參考 github地址 : https://github.com/wlcoder/ms-notify
SpringBoot 中使用 JWT案例
JWT 簡(jiǎn)介
JWT(JSON Web Token)是一種用于身份驗(yàn)證和授權(quán)的開(kāi)放標(biāo)準(zhǔn)(RFC 7519),它使用JSON格式傳輸信息,可以在不同系統(tǒng)之間安全地傳遞數(shù)據(jù)。JWT由三部分組成:頭部、載荷和簽名。頭部包含算法和類型信息,載荷包含用戶信息和其他元數(shù)據(jù),簽名用于驗(yàn)證JWT的真實(shí)性和完整性。JWT的優(yōu)點(diǎn)包括可擴(kuò)展性、跨平臺(tái)、無(wú)狀態(tài)和安全性高等。它被廣泛應(yīng)用于Web應(yīng)用程序、移動(dòng)應(yīng)用程序和API等領(lǐng)域。
JWT 身份認(rèn)證流程
- 客戶端向服務(wù)器發(fā)送用戶名和密碼,通常使用POST請(qǐng)求方式,將用戶名和密碼作為請(qǐng)求體發(fā)送給服務(wù)器。
- 服務(wù)器驗(yàn)證用戶名和密碼的正確性,如果驗(yàn)證通過(guò),生成一個(gè)JWT令牌,并將令牌返回給客戶端。JWT令牌包括三部分:頭部、載荷和簽名。頭部包含令牌類型和加密算法,載荷包含用戶信息和過(guò)期時(shí)間等信息,簽名用于驗(yàn)證令牌的真實(shí)性。
- 客戶端將JWT令牌保存在本地,通常使用localStorage或sessionStorage等方式保存。
- 客戶端向服務(wù)器發(fā)送請(qǐng)求,請(qǐng)求頭部包含JWT令牌。通常使用Authorization頭部字段,格式為Bearer <token>,其中<token>為JWT令牌。
- 服務(wù)器驗(yàn)證JWT令牌的真實(shí)性,通常使用JWT庫(kù)進(jìn)行驗(yàn)證。驗(yàn)證過(guò)程包括以下步驟:解析JWT令牌,驗(yàn)證頭部和載荷的簽名是否正確,驗(yàn)證令牌是否過(guò)期,驗(yàn)證令牌是否被篡改等。如果驗(yàn)證通過(guò),返回請(qǐng)求結(jié)果;否則返回錯(cuò)誤信息。
案例分享
下面是一個(gè)使用Spring Boot和JWT進(jìn)行身份認(rèn)證的示例:
1、后端代碼
1.1 添加依賴
在pom.xml文件中添加以下依賴:
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-core</artifactId> <version>2.3.0.1</version> </dependency> <dependency> <groupId>com.sun.xml.bind</groupId> <artifactId>jaxb-impl</artifactId> <version>2.3.1</version> </dependency>
1.2 添加 ShiroConfig 配置類
添加一個(gè) ShiroConfig 類,用于配置Shiro框架的安全管理器和過(guò)濾器。
其中包含了三個(gè)方法:shiroFilterFactoryBean、defaultWebSecurityManager和realm。
- shiroFilterFactoryBean方法用于配置Shiro的過(guò)濾器,包括設(shè)置攔截規(guī)則、放行請(qǐng)求和設(shè)置默認(rèn)的登錄頁(yè)等。
- defaultWebSecurityManager方法用于配置Shiro的安全管理器,包括設(shè)置記住我功能和將自定義域?qū)ο蠼唤oSpring管理等。
- realm方法用于創(chuàng)建自定義域?qū)ο?,包括設(shè)置憑證匹配器、開(kāi)啟緩存和將認(rèn)證和授權(quán)緩存寫入Redis等。
整個(gè)類的作用是為Shiro框架提供安全管理和過(guò)濾器的配置。
代碼如下:
package zk.gch.temperature.shiro; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import zk.gch.temperature.filter.JwtAuthenticationFilter; import java.util.LinkedHashMap; import javax.servlet.Filter; import java.util.HashMap; import java.util.Map; @Configuration public class ShiroConfig { // shiro中的過(guò)濾器 交給spring容器管理 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { System.out.println("securityManager = " + securityManager); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //配置攔截的規(guī)則 Map<String, Filter> filters = new HashMap<>(); filters.put("jwt", new JwtAuthenticationFilter()); shiroFilterFactoryBean.setFilters(filters); LinkedHashMap<String, String> map = new LinkedHashMap<>(); //放行登錄請(qǐng)求 anon 可匿名訪問(wèn) map.put("/user/login", "anon"); map.put("/user/add", "anon"); map.put("/register.html", "anon"); // 放行靜態(tài)資源 map.put("/dist/**", "anon"); // 放行驗(yàn)證碼請(qǐng)求 map.put("/captcha/getCaptcha", "anon"); //已登錄或“記住我”的用戶才能訪問(wèn) map.put("/**", "user"); //放行所有攜帶token請(qǐng)求的訪問(wèn) map.put("/**", "jwt"); // 設(shè)置默認(rèn)的登錄頁(yè) shiroFilterFactoryBean.setLoginUrl("/login.html"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } // 將安全管理器交由spring管理 @Bean public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm) { DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); // 設(shè)置一周免登錄 CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); SimpleCookie rmbme = new SimpleCookie("rmbme"); rmbme.setMaxAge(60*60*24*7); cookieRememberMeManager.setCookie(rmbme); defaultWebSecurityManager.setRememberMeManager(cookieRememberMeManager); defaultWebSecurityManager.setRealm(realm); return defaultWebSecurityManager; } // 將自定義域?qū)ο?交給spring管理 @Bean public Realm realm() { CustomerRealm customerRealm = new CustomerRealm(); //設(shè)置憑證匹配器 MD5 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher("MD5"); hashedCredentialsMatcher.setHashIterations(20); customerRealm.setCredentialsMatcher(hashedCredentialsMatcher); // 開(kāi)啟shiro的緩存 開(kāi)啟全局緩存 customerRealm.setCachingEnabled(true); //將認(rèn)證和授權(quán)緩存寫入redis 分布式緩存 customerRealm.setCacheManager(new RedisCacheManager()); // 設(shè)置認(rèn)證緩存 customerRealm.setAuthenticationCachingEnabled(true); customerRealm.setAuthenticationCacheName("authentication"); // 設(shè)置授權(quán)緩存 customerRealm.setAuthorizationCachingEnabled(true); customerRealm.setAuthorizationCacheName("authorization"); return customerRealm; } }
1.3 添加 JwtUtil 工具類
JwtUtil 工具類是一個(gè)用于生成和解析JWT(JSON Web Token)的工具類。JWT是一種用于身份驗(yàn)證和授權(quán)的開(kāi)放標(biāo)準(zhǔn),它可以在客戶端和服務(wù)器之間傳遞安全可靠的信息。
該工具類中包含了生成JWT的方法createJWT(),可以設(shè)置token中要存放的數(shù)據(jù)、過(guò)期時(shí)間等信息,并使用HS256對(duì)稱加密算法簽名。同時(shí),該工具類還包含了解析JWT的方法parseJWT(),可以解析出token中存放的數(shù)據(jù)。此外,該工具類還包含了一些常量和輔助方法,如JWT_TTL、JWT_KEY、getUUID()等。
代碼如下:
package zk.gch.temperature.utils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date; import java.util.UUID; @Component public class JwtUtil { //有效期為 public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一個(gè)小時(shí) //設(shè)置秘鑰明文 public static final String JWT_KEY = "sangeng"; public static String getUUID(){ String token = UUID.randomUUID().toString().replaceAll("-", ""); return token; } /** * 生成jtw * @param subject token中要存放的數(shù)據(jù)(json格式) * @return */ public static String createJWT(String subject) { JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 設(shè)置過(guò)期時(shí)間 return builder.compact(); } /** * 生成jtw * @param subject token中要存放的數(shù)據(jù)(json格式) * @param ttlMillis token超時(shí)時(shí)間 * @return */ public static String createJWT(String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 設(shè)置過(guò)期時(shí)間 return builder.compact(); } private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); if(ttlMillis==null){ ttlMillis=JwtUtil.JWT_TTL; } long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); return Jwts.builder() .setId(uuid) //唯一的ID .setSubject(subject) // 主題 可以是JSON數(shù)據(jù) .setIssuer("sg") // 簽發(fā)者 .setIssuedAt(now) // 簽發(fā)時(shí)間 .signWith(signatureAlgorithm, secretKey) //使用HS256對(duì)稱加密算法簽名, 第二個(gè)參數(shù)為秘鑰 .setExpiration(expDate); } /** * 創(chuàng)建token * @param id * @param subject * @param ttlMillis * @return */ public static String createJWT(String id, String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 設(shè)置過(guò)期時(shí)間 return builder.compact(); } public static void main(String[] args) throws Exception { String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg"; Claims claims = parseJWT(token); System.out.println(claims); } /** * 生成加密后的秘鑰 secretKey * @return */ public static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * 解析 * * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); } }
1.4 添加 JwtAuthenticationFilter 過(guò)濾器類
JwtAuthenticationFilter 類是一個(gè)基于JWT(JSON Web Token)的身份驗(yàn)證過(guò)濾器,用于在每個(gè)請(qǐng)求中驗(yàn)證用戶的身份。它首先從請(qǐng)求頭中獲取Authorization字段,然后解析其中的token并驗(yàn)證其有效性。如果token有效,則將用戶信息存入SecurityContextHolder中,以便后續(xù)的身份驗(yàn)證。如果token無(wú)效,則返回401 Unauthorized。最后,它放行請(qǐng)求,使其繼續(xù)處理。
代碼如下:
package zk.gch.temperature.filter; import io.jsonwebtoken.Claims; import org.apache.shiro.web.servlet.OncePerRequestFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import zk.gch.temperature.utils.JwtUtil; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Override protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 獲取請(qǐng)求頭中的Authorization字段 String header = httpRequest.getHeader("Authorization"); if (header != null && header.startsWith("Bearer ")) { // 獲取token String token = header.substring(7); System.out.println("token = " + token); try { // 解析token并驗(yàn)證其有效性 Claims claims = jwtUtil.parseJWT(token); if (claims != null) { // 將用戶信息存入SecurityContextHolder中 // UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(claims.getSubject(), null, null); // SecurityContextHolder.getContext().setAuthentication(authentication); } } catch (Exception e) { // 驗(yàn)證失敗,返回401 Unauthorized httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return; } } // 放行請(qǐng)求 filterChain.doFilter(request, response); } }
1.5 添加 UserController 控制器類
主要代碼如下:
將JwtUtil添加到spring容器管理:
@Autowired private JwtUtil jwtUtil;
請(qǐng)求成功后,將token作為返回值,返回給前端:
String token = jwtUtil.createJWT(user.getId().toString(), JSON.toJSONString(user), JwtUtil.JWT_TTL);
全部代碼如下:
package zk.gch.temperature.controller; import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.web.bind.annotation.*; import zk.gch.temperature.commons.CodeMsg; import zk.gch.temperature.commons.ResponseResult; import zk.gch.temperature.dto.OhterPageDTO; import zk.gch.temperature.dto.UserLoginDTO; import zk.gch.temperature.dto.UserPageDTO; import zk.gch.temperature.dto.UserRegisterDTO; import zk.gch.temperature.entity.BasicInfo; import zk.gch.temperature.entity.Device; import zk.gch.temperature.entity.User; import zk.gch.temperature.service.UserService; import zk.gch.temperature.utils.JwtUtil; import javax.servlet.http.HttpSession; import java.util.Arrays; import java.util.List; @RestController @Api(tags="主用戶模塊") @RequestMapping("user") public class UserController { @Autowired private UserService userService; @Autowired private RedisTemplate redisTemplate; @Autowired private JwtUtil jwtUtil; @ApiOperation("用戶注冊(cè)") @PostMapping("add") public ResponseResult add(@RequestBody UserRegisterDTO user){ return userService.saveUser(user); } @ApiOperation("用戶登錄") @GetMapping("login") public ResponseResult login(UserLoginDTO userLoginDTO, HttpSession session){ System.out.println("userLoginDTO.getSessionId() = " + userLoginDTO.getSessionId()); String sessionId=""; // 1.判定用戶的驗(yàn)證碼是否正確 if(userLoginDTO.getSessionId()!=null){ sessionId=userLoginDTO.getSessionId(); }else{ sessionId=session.getId(); } String code = (String) redisTemplate.opsForValue().get(sessionId); System.out.println("code = " + code); String captcha = userLoginDTO.getCaptcha(); System.out.println("captcha = " + captcha); if(code==null){ // 驗(yàn)證碼失效 return new ResponseResult(CodeMsg.CAPTCHA_EXPIRE); }else{ if (code.equals(captcha)){ // 驗(yàn)證碼正確 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userLoginDTO.getName(), userLoginDTO.getPassword()); // 判定用戶是否開(kāi)啟 免登錄 String rememberMe = userLoginDTO.getRememberMe(); if("true".equals(rememberMe)){ usernamePasswordToken.setRememberMe(true); } subject.login(usernamePasswordToken); QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.eq("name",userLoginDTO.getName()); User user = userService.getOne(userQueryWrapper); if(!userLoginDTO.getRole().equals(user.getRole())){//角色錯(cuò)誤 return new ResponseResult(CodeMsg.ROLE_ERROR); } String token = jwtUtil.createJWT(user.getId().toString(), JSON.toJSONString(user), JwtUtil.JWT_TTL); return new ResponseResult(CodeMsg.SUCCESS,null,token); }else { // 驗(yàn)證碼錯(cuò)誤 return new ResponseResult(CodeMsg.CAPTCHA_ERROR); } } } @ApiOperation("用戶退出") @GetMapping("logout") public ResponseResult logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout(); return new ResponseResult(CodeMsg.SUCCESS); } @ApiOperation("查詢用戶信息列表") @GetMapping("all") public ResponseResult selectPage(UserPageDTO userPageDTO){ return userService.selectPage(userPageDTO); } @ApiOperation(value = "根據(jù)用戶名查詢單個(gè)用戶信息") @GetMapping("getByName") public ResponseResult getByName(String name){ LambdaQueryWrapper<User> lambda = new QueryWrapper<User>().lambda(); lambda.eq((name!=null&&!"".equals(name)),User::getName,name); User user = userService.getOne(lambda); return new ResponseResult(CodeMsg.SUCCESS,null,user); } @ApiOperation(value = "根據(jù)id查詢單個(gè)用戶信息") @GetMapping("getById") public ResponseResult getById(Integer id){ User user = userService.getById(id); return new ResponseResult(CodeMsg.SUCCESS,null,user); } @ApiOperation("更新用戶信息(綁定設(shè)備)") @PutMapping("update") public ResponseResult update(@RequestBody User user){ userService.updateById(user); return new ResponseResult(CodeMsg.SUCCESS); } @ApiOperation("刪除用戶") @DeleteMapping("delete") public ResponseResult delete(Integer[] ids){ List<Integer> integers = Arrays.asList(ids); userService.removeBatchByIds(integers); return new ResponseResult(CodeMsg.SUCCESS); } //密碼重置 @ApiOperation("密碼重置") @PostMapping("/updatePwd") public ResponseResult updatePwd(Integer id){ return userService.updatePwd(id); } }
2、前端代碼
登陸成功的代碼如下:
$.get("/user/login",data,function (res) { if(res.code==0){ layer.msg(res.msg,{icon:1},function () { localStorage.setItem("token",res.data); window.location = 'index.html'; }) }else{ layer.msg(res.msg) } },"JSON")
主要就是將token存儲(chǔ)在localStorage中;
其他頁(yè)面請(qǐng)求接口時(shí),在請(qǐng)求頭中添加Authorization字段;代碼如下:
var token = localStorage.getItem("token"); table.render({ elem: '#currentTableId', url: '/other/all?userName=' + localStorage.getItem("name"), toolbar: '#toolbarDemo', beforeSend: function(xhr) { // 在請(qǐng)求頭中添加Authorization字段 xhr.setRequestHeader("Authorization", "Bearer " + token); }, cols: [[ {type: "checkbox", width: 50}, {field: 'id', title: 'ID', width: 100, sort: true, hide: true}, {field: 'name', title: '用戶名', width: 100}, {field: 'age', title: '年齡(周歲)', width: 100}, {field: 'height', title: '身高(cm)', width: 100}, {field: 'weight', title: '體重(kg)', width: 100}, {field: 'maxTem', title: '體溫最大值(℃)', width: 130}, {field: 'minTem', title: '體溫最小值(℃)', width: 130}, {field: 'tel', title: '聯(lián)系方式', Width: 100}, {field: 'userName', title: '主用戶名', hide: true}, {field: 'deviceId', title: '設(shè)備編號(hào)', width: 100}, {field: 'deviceState', title: '設(shè)備狀態(tài)', templet: '#stateSwitch'}, {field: 'createTime', title: '創(chuàng)建時(shí)間'}, {field: 'updateTime', title: '修改時(shí)間'}, {fixed: 'right', title: '操作', width: 134, minWidth: 125, toolbar: '#barDemo'} ]], limits: [5, 10, 15, 20, 25, 50], limit: 5, page: true, skin: 'line' });
主要代碼:
獲取token:var token = localStorage.getItem("token");
在請(qǐng)求頭中添加Authorization字段:beforeSend: function(xhr) { // 在請(qǐng)求頭中添加Authorization字段 xhr.setRequestHeader("Authorization", "Bearer " + token); },
到此這篇關(guān)于SpringBoot 中使用 JWT案例的文章就介紹到這了,更多相關(guān)SpringBoot 使用 JWT內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
idea版本控制忽略.idea文件和.iml文件的問(wèn)題
這篇文章主要介紹了idea版本控制忽略.idea文件和.iml文件,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11JAVA中try-catch結(jié)構(gòu)之異常處理的使用方法
Java編程中一個(gè)非常重要且實(shí)用的概念,可以幫助我們處理代碼運(yùn)行時(shí)發(fā)生的異常情況,下面這篇文章主要給大家介紹了關(guān)于JAVA中try-catch結(jié)構(gòu)之異常處理的使用方法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-09-09SpringBoot3整合Mybatis完整版實(shí)例
本文詳細(xì)介紹了SpringBoot3整合MyBatis的完整步驟,包括添加數(shù)據(jù)庫(kù)驅(qū)動(dòng)和MyBatis依賴、配置數(shù)據(jù)源和MyBatis、創(chuàng)建表和Bean類、編寫Mapper接口和XML文件、創(chuàng)建Controller類以及配置掃描包,通過(guò)這些步驟,可以實(shí)現(xiàn)SpringBoot3與MyBatis的成功整合,并進(jìn)行功能測(cè)試2025-01-01