實戰(zhàn):十分鐘實現(xiàn)基于JWT前后端分離的權(quán)限框架
前言
面試過很多Java開發(fā),能把權(quán)限這塊說的清楚的實在是不多,很多人因為公司項目職責(zé)問題,很難學(xué)到這類相關(guān)的流程和技術(shù),本文梳理一個簡單的場景,實現(xiàn)一個基于jwt前后端分離的權(quán)限框架。
簡易流程
登錄獲取票據(jù)和緩存信息

鑒權(quán)流程

技術(shù)棧和功能規(guī)劃
本文技術(shù)選型為SpringBoot+JWT+Redis, 實現(xiàn)上圖的登錄流程和鑒權(quán)流程,并提供完整項目代碼。
「本項目已實現(xiàn)如下功能」:
跨域配置 jwt集成 redis集成 BaseController封裝,方便取出用戶信息 攔截器和白名單 全局異常 jwt工具類封裝 redis工具類封裝 redis枚舉Key封裝
Redis安裝和啟動
使用Docker一行命令構(gòu)建啟動Redis,命令如下
?docker?run?-itd?--name?redis-test?-p?6379:6379?redis?--requirepass?"123456redis"
指定端口:6379
指定密碼:123456redis

客戶端連接測試,問題不大~

創(chuàng)建SpringBoot項目并引入JWT依賴
??io.jsonwebtoken
??jjwt
??0.9.1
其實主要是引了這個包,其他的就不貼出來了。主要有redis以及json相關(guān)的,完整代碼都在項目可以自己看
寫一個登陸方法
UserController
??@PostMapping("/login")
????public?UserVO?login(@RequestBody?LoginDTO?loginDTO)??{
????????return?userService.login(loginDTO);
????}
UserService
用戶信息就模擬的了,都看的懂。登錄成功后根據(jù)uid生產(chǎn)jwt,然后緩存到redis,封裝結(jié)果給到前端
package?com.lzp.auth.service;
@Service
public?class?UserService?{
????@Value("${server.session.timeout:3000}")
????private?Long?timeout;
????@Autowired
????private?RedisUtils?redisUtils;
????final?static?String?USER_NAME?=?"admin";
????//密碼?演示用就不做加密處理了
????final?static?String?PWD?=?"admin";
????public?UserVO?login(LoginDTO?loginDTO){
????????User?user?=?getByName(loginDTO.getUserName());
????????//用戶信息校驗和查詢
????????if?(user?==?null){
????????????throw?new?ServiceException(ResultCodeEnum.LOGIN_FAIL);
????????}
????????//密碼校驗
????????if(!PWD.equals(loginDTO.getPwd())){
????????????throw?new?ServiceException(ResultCodeEnum.LOGIN_FAIL);
????????}
????????//緩存用戶信息并設(shè)置過期時間
????????UserVO?userVO?=?new?UserVO();
????????userVO.setName(user.getName());
????????userVO.setUid(user.getUid());
????????userVO.setToken(JWTUtils.generate(user.getUid()));
????????//信息入庫redis
????????redisUtils.set(RedisKeyEnum.OAUTH_APP_TOKEN.keyBuilder(userVO.getUid()),?JSONObject.toJSONString(userVO),?timeout);
????????return?userVO;
????}
????/**
?????*?通過用戶名獲取用戶
?????*?@param?name
?????*?@return
?????*/
????public?User?getByName(String?name){
????????User?user?=?null;
????????if(USER_NAME.equals(name)){
????????????user?=??new?User("1","張三","Aa123456");
????????}
????????return?user;
????}
}
定義登錄攔截器
LoginInterceptor
package?com.lzp.auth.config;
import?com.lzp.auth.exception.ServiceException;
import?com.lzp.auth.utils.JWTUtils;
import?com.lzp.auth.utils.ResultCodeEnum;
import?com.lzp.auth.utils.SessionContext;
import?io.jsonwebtoken.Claims;
import?lombok.extern.slf4j.Slf4j;
import?org.springframework.beans.factory.annotation.Autowired;
import?org.springframework.data.redis.core.StringRedisTemplate;
import?org.springframework.util.StringUtils;
import?org.springframework.web.servlet.ModelAndView;
import?org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import?javax.servlet.http.HttpServletRequest;
import?javax.servlet.http.HttpServletResponse;
@Slf4j
public?class?LoginInterceptor?extends?HandlerInterceptorAdapter?{
????@Autowired
????StringRedisTemplate?redisTemplate;
????@Override
????public?boolean?preHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler)?throws?Exception?{
????????String?token?=?request.getHeader("token");
????????String?requestURI?=?request.getRequestURI().replaceAll("/+",?"/");
????????log.info("requestURI:{}",requestURI);
????????if?(StringUtils.isEmpty(token))?{
????????????throw?new?ServiceException(ResultCodeEnum.AUTH_FAIL);
????????}
????????Claims?claim?=?JWTUtils.getClaim(token);
????????if(claim?==?null){
????????????throw?new?ServiceException(ResultCodeEnum.AUTH_FAIL);
????????}
????????String?uid?=?null;
????????try?{
????????????uid?=?JWTUtils.getOpenId(token);
????????}?catch?(Exception?e)?{
????????????throw?new?ServiceException(ResultCodeEnum.AUTH_FAIL);
????????}
????????//用戶id放到上下文?可以當(dāng)前請求進(jìn)行傳遞
????????request.setAttribute(SessionContext.USER_ID_KEY,?uid);
????????return?true;
????}
????@Override
????public?void?postHandle(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,?ModelAndView?modelAndView)?throws?Exception?{
????}
????@Override
????public?void?afterCompletion(HttpServletRequest?request,?HttpServletResponse?response,?Object?handler,?Exception?ex)?throws?Exception?{
????}
}
配置跨域、資源、定義攔截器、加白名單
package?com.lzp.auth.config;
import?org.springframework.beans.BeansException;
import?org.springframework.context.ApplicationContext;
import?org.springframework.context.ApplicationContextAware;
import?org.springframework.context.annotation.Bean;
import?org.springframework.context.annotation.Configuration;
import?org.springframework.web.cors.CorsConfiguration;
import?org.springframework.web.cors.reactive.CorsWebFilter;
import?org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import?org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import?org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import?org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import?org.springframework.web.util.pattern.PathPatternParser;
@Configuration
public?class?WebMvcConfig?extends?WebMvcConfigurerAdapter?implements?ApplicationContextAware?{
????private?ApplicationContext?applicationContext;
????public?WebMvcConfig()?{
????????super();
????}
????@Override
????public?void?addResourceHandlers(ResourceHandlerRegistry?registry)?{
????????super.addResourceHandlers(registry);
????}
????@Override
????public?void?setApplicationContext(ApplicationContext?applicationContext)?throws?BeansException?{
????????this.applicationContext?=?applicationContext;
????}
????@Bean
????public?CorsWebFilter?corsFilter()?{
????????CorsConfiguration?config?=?new?CorsConfiguration();
????????config.addAllowedMethod("*");
????????config.addAllowedOrigin("*");
????????config.addAllowedHeader("*");
????????UrlBasedCorsConfigurationSource?source?=?new?UrlBasedCorsConfigurationSource(new?PathPatternParser());
????????source.registerCorsConfiguration("/**",?config);
????????return?new?CorsWebFilter(source);
????}
????@Bean
????LoginInterceptor?loginInterceptor()?{
????????return?new?LoginInterceptor();
????}
????@Override
????public?void?addInterceptors(InterceptorRegistry?registry)?{
????????//攔截規(guī)則:除了login,其他都攔截判斷
????????registry.addInterceptor(loginInterceptor())
????????????????.addPathPatterns("/**")
????????????????.excludePathPatterns("/user/login");
????????super.addInterceptors(registry);
????}
}
「postMan調(diào)試」
模擬成功和失敗場景

成功如上圖,返回用戶信息和Token

失敗如上圖
測試非白名單接口


「使用token再來訪問當(dāng)前接口」

完美~
至此本項目就完美結(jié)束了哦
項目代碼
https://github.com/pengziliu/GitHub-code-practice
這個倉庫除了這個還有更多精彩代碼喲
已擼完部分開源輪子,更多精彩正在路上
| 模塊 | 所屬開源項目 | 項目介紹 |
|---|---|---|
springboot_api_encryption | rsa-encrypt-body-spring-boot | Spring Boot接口加密,可以對返回值、參數(shù)值通過注解的方式自動加解密 。 |
simpleimage-demo | simpleimage | 圖片處理工具類,具有加水印、壓縮、裁切等功能 |
xxl-job-demo | xxl-job | 分布式定時任務(wù)使用場景 |
xxl-sso-demo | xxl-sso | 單點登錄功能 |
vuepress-demo | vuepress | 建立自己的知識檔案庫 |
xxl-conf-demo | xxl-conf | 分布式配置中心 |
Shardingsphere-demo | Shardingsphere | 分庫分表 |
easyexcel-demo | easyexcel | excel操作工具類 |
| kaptcha-demo | kaptcha | 前后端分離驗證碼方案 |
—?【 THE END 】— 本公眾號全部博文已整理成一個目錄,請在公眾號里回復(fù)「m」獲取! 3T技術(shù)資源大放送!包括但不限于:Java、C/C++,Linux,Python,大數(shù)據(jù),人工智能等等。在公眾號內(nèi)回復(fù)「1024」,即可免費獲取!!
評論
圖片
表情
