日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Shiro + JWT + Spring Boot Restful 简易教程

發(fā)布時(shí)間:2025/3/20 javascript 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Shiro + JWT + Spring Boot Restful 简易教程 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
點(diǎn)擊上方?好好學(xué)java?,選擇?星標(biāo)?公眾號重磅資訊,干貨,第一時(shí)間送達(dá)今日推薦:推薦19個(gè)github超牛逼項(xiàng)目!個(gè)人原創(chuàng)100W +訪問量博客:點(diǎn)擊前往,查看更多

?作者:Smith-Cruise

github.com/Smith-Cruise/Spring-Boot-Shiro

特性

  • 完全使用了 Shiro 的注解配置,保持高度的靈活性。

  • 放棄 Cookie ,Session ,使用JWT進(jìn)行鑒權(quán),完全實(shí)現(xiàn)無狀態(tài)鑒權(quán)。

  • JWT 密鑰支持過期時(shí)間。

  • 對跨域提供支持。

準(zhǔn)備工作

在開始本教程之前,請保證已經(jīng)熟悉以下幾點(diǎn)。

  • Spring Boot 基本語法,至少要懂得 Controller 、 RestController 、 Autowired 等這些基本注釋。其實(shí)看看官方的 Getting-Start 教程就差不多了。

  • JWT (Json Web Token)的基本概念,并且會(huì)簡單操作JWT的 JAVA SDK。

  • Shiro 的基本操作,看下官方的 10 Minute Tutorial 即可。

  • 模擬 HTTP 請求工具,我使用的是 PostMan。

簡要的說明下我們?yōu)槭裁匆?JWT ,因?yàn)槲覀円獙?shí)現(xiàn)完全的前后端分離,所以不可能使用 session, cookie 的方式進(jìn)行鑒權(quán),所以 JWT 就被派上了用場,你可以通過一個(gè)加密密鑰來進(jìn)行前后端的鑒權(quán)。

程序邏輯

  • 我們 POST 用戶名與密碼到 /login 進(jìn)行登入,如果成功返回一個(gè)加密 token,失敗的話直接返回 401 錯(cuò)誤。

  • 之后用戶訪問每一個(gè)需要權(quán)限的網(wǎng)址請求必須在 header 中添加 Authorization 字段,例如 Authorization: token ,token 為密鑰。

  • 后臺(tái)會(huì)進(jìn)行 token 的校驗(yàn),如果有誤會(huì)直接返回 401。

  • Token加密說明

    • 攜帶了 username 信息在 token 中。

    • 設(shè)定了過期時(shí)間。

    • 使用用戶登入密碼對 token 進(jìn)行加密。

    Token校驗(yàn)流程

  • 獲得 token 中攜帶的 username 信息。

  • 進(jìn)入數(shù)據(jù)庫搜索這個(gè)用戶,得到他的密碼。

  • 使用用戶的密碼來檢驗(yàn) token 是否正確。

  • 準(zhǔn)備Maven文件

    新建一個(gè) Maven 工程,添加相關(guān)的 dependencies。

    <?xml?version="1.0"?encoding="UTF-8"?> <project?xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0?http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.inlighting</groupId><artifactId>shiro-study</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version></dependency><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.2.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>1.5.8.RELEASE</version></dependency></dependencies><build><plugins><!--?Srping?Boot?打包工具?--><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>1.5.7.RELEASE</version><executions><execution><goals><goal>repackage</goal></goals></execution></executions></plugin><!--?指定JDK編譯版本?--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>1.8</source><target>1.8</target><encoding>UTF-8</encoding></configuration></plugin></plugins></build> </project>

    注意指定JDK版本和編碼。

    構(gòu)建簡易的數(shù)據(jù)源

    為了縮減教程的代碼,我使用 HashMap 本地模擬了一個(gè)數(shù)據(jù)庫,結(jié)構(gòu)如下:

    usernamepasswordrolepermission
    smithsmith123userview
    dannydanny123adminview,edit

    這是一個(gè)最簡單的用戶權(quán)限表,如果想更加進(jìn)一步了解,自行百度 RBAC。

    之后再構(gòu)建一個(gè) UserService 來模擬數(shù)據(jù)庫查詢,并且把結(jié)果放到 UserBean 之中。

    UserService.java

    @Component public?class?UserService?{public?UserBean?getUser(String?username)?{//?沒有此用戶直接返回nullif?(!?DataSource.getData().containsKey(username))return?null;UserBean?user?=?new?UserBean();Map<String,?String>?detail?=?DataSource.getData().get(username);user.setUsername(username);user.setPassword(detail.get("password"));user.setRole(detail.get("role"));user.setPermission(detail.get("permission"));return?user;} }

    UserBean.java

    public?class?UserBean?{private?String?username;private?String?password;private?String?role;private?String?permission;public?String?getUsername()?{return?username;}public?void?setUsername(String?username)?{this.username?=?username;}public?String?getPassword()?{return?password;}public?void?setPassword(String?password)?{this.password?=?password;}public?String?getRole()?{return?role;}public?void?setRole(String?role)?{this.role?=?role;}public?String?getPermission()?{return?permission;}public?void?setPermission(String?permission)?{this.permission?=?permission;} }

    配置 JWT

    我們寫一個(gè)簡單的 JWT 加密,校驗(yàn)工具,并且使用用戶自己的密碼充當(dāng)加密密鑰,這樣保證了 token 即使被他人截獲也無法破解。并且我們在 token 中附帶了 username 信息,并且設(shè)置密鑰5分鐘就會(huì)過期。

    public?class?JWTUtil?{//?過期時(shí)間5分鐘private?static?final?long?EXPIRE_TIME?=?5*60*1000;/***?校驗(yàn)token是否正確*?@param?token?密鑰*?@param?secret?用戶的密碼*?@return?是否正確*/public?static?boolean?verify(String?token,?String?username,?String?secret)?{try?{Algorithm?algorithm?=?Algorithm.HMAC256(secret);JWTVerifier?verifier?=?JWT.require(algorithm).withClaim("username",?username).build();DecodedJWT?jwt?=?verifier.verify(token);return?true;}?catch?(Exception?exception)?{return?false;}}/***?獲得token中的信息無需secret解密也能獲得*?@return?token中包含的用戶名*/public?static?String?getUsername(String?token)?{try?{DecodedJWT?jwt?=?JWT.decode(token);return?jwt.getClaim("username").asString();}?catch?(JWTDecodeException?e)?{return?null;}}/***?生成簽名,5min后過期*?@param?username?用戶名*?@param?secret?用戶的密碼*?@return?加密的token*/public?static?String?sign(String?username,?String?secret)?{try?{Date?date?=?new?Date(System.currentTimeMillis()+EXPIRE_TIME);Algorithm?algorithm?=?Algorithm.HMAC256(secret);//?附帶username信息return?JWT.create().withClaim("username",?username).withExpiresAt(date).sign(algorithm);}?catch?(UnsupportedEncodingException?e)?{return?null;}} }

    構(gòu)建URL

    ResponseBean.java

    既然想要實(shí)現(xiàn) restful,那我們要保證每次返回的格式都是相同的,因此我建立了一個(gè) ResponseBean 來統(tǒng)一返回的格式。(搜索公眾號Java知音,回復(fù)“2021”,送你一份Java面試題寶典)

    public?class?ResponseBean?{//?http?狀態(tài)碼private?int?code;//?返回信息private?String?msg;//?返回的數(shù)據(jù)private?Object?data;public?ResponseBean(int?code,?String?msg,?Object?data)?{this.code?=?code;this.msg?=?msg;this.data?=?data;}public?int?getCode()?{return?code;}public?void?setCode(int?code)?{this.code?=?code;}public?String?getMsg()?{return?msg;}public?void?setMsg(String?msg)?{this.msg?=?msg;}public?Object?getData()?{return?data;}public?void?setData(Object?data)?{this.data?=?data;} }

    自定義異常

    為了實(shí)現(xiàn)我自己能夠手動(dòng)拋出異常,我自己寫了一個(gè) UnauthorizedException.java

    public?class?UnauthorizedException?extends?RuntimeException?{public?UnauthorizedException(String?msg)?{super(msg);}public?UnauthorizedException()?{super();} }

    URL結(jié)構(gòu)

    URL作用
    /login登入
    /article所有人都可以訪問,但是用戶與游客看到的內(nèi)容不同
    /require_auth登入的用戶才可以進(jìn)行訪問
    /require_roleadmin的角色用戶才可以登入
    /require_permission擁有view和edit權(quán)限的用戶才可以訪問

    Controller

    @RestController public?class?WebController?{private?static?final?Logger?LOGGER?=?LogManager.getLogger(WebController.class);private?UserService?userService;@Autowiredpublic?void?setService(UserService?userService)?{this.userService?=?userService;}@PostMapping("/login")public?ResponseBean?login(@RequestParam("username")?String?username,@RequestParam("password")?String?password)?{UserBean?userBean?=?userService.getUser(username);if?(userBean.getPassword().equals(password))?{return?new?ResponseBean(200,?"Login?success",?JWTUtil.sign(username,?password));}?else?{throw?new?UnauthorizedException();}}@GetMapping("/article")public?ResponseBean?article()?{Subject?subject?=?SecurityUtils.getSubject();if?(subject.isAuthenticated())?{return?new?ResponseBean(200,?"You?are?already?logged?in",?null);}?else?{return?new?ResponseBean(200,?"You?are?guest",?null);}}@GetMapping("/require_auth")@RequiresAuthenticationpublic?ResponseBean?requireAuth()?{return?new?ResponseBean(200,?"You?are?authenticated",?null);}@GetMapping("/require_role")@RequiresRoles("admin")public?ResponseBean?requireRole()?{return?new?ResponseBean(200,?"You?are?visiting?require_role",?null);}@GetMapping("/require_permission")@RequiresPermissions(logical?=?Logical.AND,?value?=?{"view",?"edit"})public?ResponseBean?requirePermission()?{return?new?ResponseBean(200,?"You?are?visiting?permission?require?edit,view",?null);}@RequestMapping(path?=?"/401")@ResponseStatus(HttpStatus.UNAUTHORIZED)public?ResponseBean?unauthorized()?{return?new?ResponseBean(401,?"Unauthorized",?null);} }

    處理框架異常

    之前說過 restful 要統(tǒng)一返回的格式,所以我們也要全局處理 Spring Boot 的拋出異常。利用 @RestControllerAdvice 能很好的實(shí)現(xiàn)。

    @RestControllerAdvice public?class?ExceptionController?{//?捕捉shiro的異常@ResponseStatus(HttpStatus.UNAUTHORIZED)@ExceptionHandler(ShiroException.class)public?ResponseBean?handle401(ShiroException?e)?{return?new?ResponseBean(401,?e.getMessage(),?null);}//?捕捉UnauthorizedException@ResponseStatus(HttpStatus.UNAUTHORIZED)@ExceptionHandler(UnauthorizedException.class)public?ResponseBean?handle401()?{return?new?ResponseBean(401,?"Unauthorized",?null);}//?捕捉其他所有異常@ExceptionHandler(Exception.class)@ResponseStatus(HttpStatus.BAD_REQUEST)public?ResponseBean?globalException(HttpServletRequest?request,?Throwable?ex)?{return?new?ResponseBean(getStatus(request).value(),?ex.getMessage(),?null);}private?HttpStatus?getStatus(HttpServletRequest?request)?{Integer?statusCode?=?(Integer)?request.getAttribute("javax.servlet.error.status_code");if?(statusCode?==?null)?{return?HttpStatus.INTERNAL_SERVER_ERROR;}return?HttpStatus.valueOf(statusCode);} }

    配置 Shiro

    大家可以先看下官方的 Spring-Shiro 整合教程,有個(gè)初步的了解。不過既然我們用了 Spring-Boot,那我們肯定要爭取零配置文件。(搜索公眾號Java知音,回復(fù)“2021”,送你一份Java面試題寶典)

    實(shí)現(xiàn)JWTToken

    JWTToken 差不多就是 Shiro 用戶名密碼的載體。因?yàn)槲覀兪乔昂蠖朔蛛x,服務(wù)器無需保存用戶狀態(tài),所以不需要 RememberMe 這類功能,我們簡單的實(shí)現(xiàn)下 AuthenticationToken 接口即可。因?yàn)?token 自己已經(jīng)包含了用戶名等信息,所以這里我就弄了一個(gè)字段。如果你喜歡鉆研,可以看看官方的 UsernamePasswordToken 是如何實(shí)現(xiàn)的。

    public?class?JWTToken?implements?AuthenticationToken?{//?密鑰private?String?token;public?JWTToken(String?token)?{this.token?=?token;}@Overridepublic?Object?getPrincipal()?{return?token;}@Overridepublic?Object?getCredentials()?{return?token;} }

    實(shí)現(xiàn)Realm

    realm 的用于處理用戶是否合法的這一塊,需要我們自己實(shí)現(xiàn)。

    @Service public?class?MyRealm?extends?AuthorizingRealm?{private?static?final?Logger?LOGGER?=?LogManager.getLogger(MyRealm.class);private?UserService?userService;@Autowiredpublic?void?setUserService(UserService?userService)?{this.userService?=?userService;}/***?大坑!,必須重寫此方法,不然Shiro會(huì)報(bào)錯(cuò)*/@Overridepublic?boolean?supports(AuthenticationToken?token)?{return?token?instanceof?JWTToken;}/***?只有當(dāng)需要檢測用戶權(quán)限的時(shí)候才會(huì)調(diào)用此方法,例如checkRole,checkPermission之類的*/@Overrideprotected?AuthorizationInfo?doGetAuthorizationInfo(PrincipalCollection?principals)?{String?username?=?JWTUtil.getUsername(principals.toString());UserBean?user?=?userService.getUser(username);SimpleAuthorizationInfo?simpleAuthorizationInfo?=?new?SimpleAuthorizationInfo();simpleAuthorizationInfo.addRole(user.getRole());Set<String>?permission?=?new?HashSet<>(Arrays.asList(user.getPermission().split(",")));simpleAuthorizationInfo.addStringPermissions(permission);return?simpleAuthorizationInfo;}/***?默認(rèn)使用此方法進(jìn)行用戶名正確與否驗(yàn)證,錯(cuò)誤拋出異常即可。*/@Overrideprotected?AuthenticationInfo?doGetAuthenticationInfo(AuthenticationToken?auth)?throws?AuthenticationException?{String?token?=?(String)?auth.getCredentials();//?解密獲得username,用于和數(shù)據(jù)庫進(jìn)行對比String?username?=?JWTUtil.getUsername(token);if?(username?==?null)?{throw?new?AuthenticationException("token?invalid");}UserBean?userBean?=?userService.getUser(username);if?(userBean?==?null)?{throw?new?AuthenticationException("User?didn't?existed!");}if?(!?JWTUtil.verify(token,?username,?userBean.getPassword()))?{throw?new?AuthenticationException("Username?or?password?error");}return?new?SimpleAuthenticationInfo(token,?token,?"my_realm");} }

    在 doGetAuthenticationInfo() 中用戶可以自定義拋出很多異常,詳情見文檔。

    重寫 Filter

    所有的請求都會(huì)先經(jīng)過 Filter,所以我們繼承官方的 BasicHttpAuthenticationFilter ,并且重寫鑒權(quán)的方法。

    代碼的執(zhí)行流程 preHandle -> isAccessAllowed -> isLoginAttempt -> executeLogin 。

    public?class?JWTFilter?extends?BasicHttpAuthenticationFilter?{private?Logger?LOGGER?=?LoggerFactory.getLogger(this.getClass());/***?判斷用戶是否想要登入。*?檢測header里面是否包含Authorization字段即可*/@Overrideprotected?boolean?isLoginAttempt(ServletRequest?request,?ServletResponse?response)?{HttpServletRequest?req?=?(HttpServletRequest)?request;String?authorization?=?req.getHeader("Authorization");return?authorization?!=?null;}/****/@Overrideprotected?boolean?executeLogin(ServletRequest?request,?ServletResponse?response)?throws?Exception?{HttpServletRequest?httpServletRequest?=?(HttpServletRequest)?request;String?authorization?=?httpServletRequest.getHeader("Authorization");JWTToken?token?=?new?JWTToken(authorization);//?提交給realm進(jìn)行登入,如果錯(cuò)誤他會(huì)拋出異常并被捕獲getSubject(request,?response).login(token);//?如果沒有拋出異常則代表登入成功,返回truereturn?true;}/***?這里我們詳細(xì)說明下為什么最終返回的都是true,即允許訪問*?例如我們提供一個(gè)地址?GET?/article*?登入用戶和游客看到的內(nèi)容是不同的*?如果在這里返回了false,請求會(huì)被直接攔截,用戶看不到任何東西*?所以我們在這里返回true,Controller中可以通過?subject.isAuthenticated()?來判斷用戶是否登入*?如果有些資源只有登入用戶才能訪問,我們只需要在方法上面加上?@RequiresAuthentication?注解即可*?但是這樣做有一個(gè)缺點(diǎn),就是不能夠?qū)ET,POST等請求進(jìn)行分別過濾鑒權(quán)(因?yàn)槲覀冎貙懥斯俜降姆椒?,但實(shí)際上對應(yīng)用影響不大*/@Overrideprotected?boolean?isAccessAllowed(ServletRequest?request,?ServletResponse?response,?Object?mappedValue)?{if?(isLoginAttempt(request,?response))?{try?{executeLogin(request,?response);}?catch?(Exception?e)?{response401(request,?response);}}return?true;}/***?對跨域提供支持*/@Overrideprotected?boolean?preHandle(ServletRequest?request,?ServletResponse?response)?throws?Exception?{HttpServletRequest?httpServletRequest?=?(HttpServletRequest)?request;HttpServletResponse?httpServletResponse?=?(HttpServletResponse)?response;httpServletResponse.setHeader("Access-control-Allow-Origin",?httpServletRequest.getHeader("Origin"));httpServletResponse.setHeader("Access-Control-Allow-Methods",?"GET,POST,OPTIONS,PUT,DELETE");httpServletResponse.setHeader("Access-Control-Allow-Headers",?httpServletRequest.getHeader("Access-Control-Request-Headers"));//?跨域時(shí)會(huì)首先發(fā)送一個(gè)option請求,這里我們給option請求直接返回正常狀態(tài)if?(httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name()))?{httpServletResponse.setStatus(HttpStatus.OK.value());return?false;}return?super.preHandle(request,?response);}/***?將非法請求跳轉(zhuǎn)到?/401*/private?void?response401(ServletRequest?req,?ServletResponse?resp)?{try?{HttpServletResponse?httpServletResponse?=?(HttpServletResponse)?resp;httpServletResponse.sendRedirect("/401");}?catch?(IOException?e)?{LOGGER.error(e.getMessage());}} }

    getSubject(request, response).login(token); 這一步就是提交給了 realm 進(jìn)行處理。

    配置Shiro

    @Configuration public?class?ShiroConfig?{@Bean("securityManager")public?DefaultWebSecurityManager?getManager(MyRealm?realm)?{DefaultWebSecurityManager?manager?=?new?DefaultWebSecurityManager();//?使用自己的realmmanager.setRealm(realm);/**?關(guān)閉shiro自帶的session,詳情見文檔*?http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29*/DefaultSubjectDAO?subjectDAO?=?new?DefaultSubjectDAO();DefaultSessionStorageEvaluator?defaultSessionStorageEvaluator?=?new?DefaultSessionStorageEvaluator();defaultSessionStorageEvaluator.setSessionStorageEnabled(false);subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);manager.setSubjectDAO(subjectDAO);return?manager;}@Bean("shiroFilter")public?ShiroFilterFactoryBean?factory(DefaultWebSecurityManager?securityManager)?{ShiroFilterFactoryBean?factoryBean?=?new?ShiroFilterFactoryBean();//?添加自己的過濾器并且取名為jwtMap<String,?Filter>?filterMap?=?new?HashMap<>();filterMap.put("jwt",?new?JWTFilter());factoryBean.setFilters(filterMap);factoryBean.setSecurityManager(securityManager);factoryBean.setUnauthorizedUrl("/401");/**?自定義url規(guī)則*?http://shiro.apache.org/web.html#urls-*/Map<String,?String>?filterRuleMap?=?new?HashMap<>();//?所有請求通過我們自己的JWT?FilterfilterRuleMap.put("/**",?"jwt");//?訪問401和404頁面不通過我們的FilterfilterRuleMap.put("/401",?"anon");factoryBean.setFilterChainDefinitionMap(filterRuleMap);return?factoryBean;}/***?下面的代碼是添加注解支持*/@Bean@DependsOn("lifecycleBeanPostProcessor")public?DefaultAdvisorAutoProxyCreator?defaultAdvisorAutoProxyCreator()?{DefaultAdvisorAutoProxyCreator?defaultAdvisorAutoProxyCreator?=?new?DefaultAdvisorAutoProxyCreator();//?強(qiáng)制使用cglib,防止重復(fù)代理和可能引起代理出錯(cuò)的問題//?https://zhuanlan.zhihu.com/p/29161098defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);return?defaultAdvisorAutoProxyCreator;}@Beanpublic?LifecycleBeanPostProcessor?lifecycleBeanPostProcessor()?{return?new?LifecycleBeanPostProcessor();}@Beanpublic?AuthorizationAttributeSourceAdvisor?authorizationAttributeSourceAdvisor(DefaultWebSecurityManager?securityManager)?{AuthorizationAttributeSourceAdvisor?advisor?=?new?AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return?advisor;} }

    里面 URL 規(guī)則自己參考文檔即可 http://shiro.apache.org/web.html 。

    總結(jié)

    我就說下代碼還有哪些可以進(jìn)步的地方吧

    • 沒有實(shí)現(xiàn) Shiro 的 Cache 功能。

    • Shiro 中鑒權(quán)失敗時(shí)不能夠直接返回 401 信息,而是通過跳轉(zhuǎn)到 /401 地址實(shí)現(xiàn)。

    GitHub 項(xiàng)目地址:

    https://github.com/Smith-Cruise/Spring-Boot-Shiro

    推薦文章
    • 2021 最新版 Spring Boot 速記教程

    • 2W 字你全面認(rèn)識(shí) Nginx

    • 47K Star 的SpringBoot+MyBatis+docker電商項(xiàng)目,附帶超詳細(xì)的文檔!

    • 寫博客能月入10K?

    • 一款基于 Spring Boot 的現(xiàn)代化社區(qū)(論壇/問答/社交網(wǎng)絡(luò)/博客)

    更多項(xiàng)目源碼
    • 這或許是最美的Vue+Element開源后臺(tái)管理UI

    • 推薦一款高顏值的 Spring Boot 快速開發(fā)框架

    • 一款基于 Spring Boot 的現(xiàn)代化社區(qū)(論壇/問答/社交網(wǎng)絡(luò)/博客)

    • 13K點(diǎn)贊都基于 Vue+Spring 前后端分離管理系統(tǒng)ELAdmin,大愛

    • 想接私活時(shí)薪再翻一倍,建議根據(jù)這幾個(gè)開源的SpringBoot項(xiàng)目

    總結(jié)

    以上是生活随笔為你收集整理的Shiro + JWT + Spring Boot Restful 简易教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。