## 前言
面試過(guò)很多Java開(kāi)發(fā),能把權(quán)限這塊說(shuō)的清楚的實(shí)在是不多,很多人因?yàn)楣卷?xiàng)目職責(zé)問(wèn)題,很難學(xué)到這類相關(guān)的流程和技術(shù),本文梳理一個(gè)簡(jiǎn)單的場(chǎng)景,實(shí)現(xiàn)一個(gè)基于jwt前后端分離的權(quán)限框架。
## 簡(jiǎn)易流程
### 登錄獲取票據(jù)和緩存信息

### 鑒權(quán)流程

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

客戶端連接測(cè)試,問(wèn)題不大~

### 創(chuàng)建SpringBoot項(xiàng)目并引入JWT依賴
```
<dependency>
? <groupId>io.jsonwebtoken</groupId>
? <artifactId>jjwt</artifactId>
? <version>0.9.1</version>
</dependency>
```
其實(shí)主要是引了這個(gè)包,其他的就不貼出來(lái)了。主要有redis以及json相關(guān)的,完整代碼都在項(xiàng)目可以自己看
## 寫(xiě)一個(gè)登陸方法
`UserController`
```java
? @PostMapping("/login")
? ? public UserVO login(@RequestBody LoginDTO loginDTO)? {
? ? ? ? return userService.login(loginDTO);
? ? }
```
`UserService`
用戶信息就模擬的了,都看的懂。登錄成功后根據(jù)uid生產(chǎn)jwt,然后緩存到redis,封裝結(jié)果給到前端
```java
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());
? ? ? ? //用戶信息校驗(yàn)和查詢
? ? ? ? if (user == null){
? ? ? ? ? ? throw new ServiceException(ResultCodeEnum.LOGIN_FAIL);
? ? ? ? }
? ? ? ? //密碼校驗(yàn)
? ? ? ? if(!PWD.equals(loginDTO.getPwd())){
? ? ? ? ? ? throw new ServiceException(ResultCodeEnum.LOGIN_FAIL);
? ? ? ? }
? ? ? ? //緩存用戶信息并設(shè)置過(guò)期時(shí)間
? ? ? ? UserVO userVO = new UserVO();
? ? ? ? userVO.setName(user.getName());
? ? ? ? userVO.setUid(user.getUid());
? ? ? ? userVO.setToken(JWTUtils.generate(user.getUid()));
? ? ? ? //信息入庫(kù)redis
? ? ? ? redisUtils.set(RedisKeyEnum.OAUTH_APP_TOKEN.keyBuilder(userVO.getUid()), JSONObject.toJSONString(userVO), timeout);
? ? ? ? return userVO;
? ? }
? ? /**
? ? * 通過(guò)用戶名獲取用戶
? ? * @param name
? ? * @return
? ? */
? ? public User getByName(String name){
? ? ? ? User user = null;
? ? ? ? if(USER_NAME.equals(name)){
? ? ? ? ? ? user =? new User("1","張三","Aa123456");
? ? ? ? }
? ? ? ? return user;
? ? }
}
```
### 定義登錄攔截器
`LoginInterceptor`
```java
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)前請(qǐ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 {
? ? }
}
```
### 配置跨域、資源、定義攔截器、加白名單
```java
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)試**
模擬成功和失敗場(chǎng)景

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

失敗如上圖
### 測(cè)試非白名單接口


**使用token再來(lái)訪問(wèn)當(dāng)前接口**

完美~
至此本項(xiàng)目就完美結(jié)束了哦
### 項(xiàng)目代碼
https://github.com/pengziliu/GitHub-code-practice