jwt用户登录,网关给微服务传递用户信息,以及微服务间feign调用传递用户信息

1、引入jwt依赖

java 复制代码
<dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

2、Jwt工具类,生成token以及解析token

java 复制代码
package com.niuniu.gateway.util;

import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JWTUtil {
    /**
     * 密钥
     */
    private static final String jwtToken = "niuniu";

    public static String createToken(Long userId) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("userId", userId);
        JwtBuilder jwtBuilder = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,密钥为jwtToken
                .setClaims(claims) // body数据,要唯一,自行设置
                .setIssuedAt(new Date()) // 设置签发时间
                .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 60 * 1000)); // 一天的有效时间
        String token = jwtBuilder.compact();
        return token;
    }

    public static Map<String, Object> checkToken(String token) {
        try {
            Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
            return (Map<String, Object>) parse.getBody();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static Long parseToken(String token) {
        Map<String, Object> map = JWTUtil.checkToken(token);
        if (map != null && map.containsKey("userId")) {
            return Long.parseLong(String.valueOf(map.get("userId")));
        }
        return null;
    }

    /**
     * main方法验证一下工具类
     * @param args
     */
    public static void main(String[] args) {
        String token = JWTUtil.createToken(1000L);
        System.out.println("生成的token" + token);

        System.out.println("解析token" + JWTUtil.checkToken(token));
    }

}

3、网关微服务的依赖

java 复制代码
<!-- 负载均衡 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>

        <!--用于被nacos发现该服务  被gateway发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!-- 网关 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

4、网关微服务的application.yaml

XML 复制代码
spring:
  application:
    name: gateway-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/order-service/**
        - id: product-service
          uri: lb://product-service
          predicates:
            - Path=/product-service/**
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/user-service/**
server:
  port: 8080
  servlet:
    context-path: /gateway-service
hm:
  auth:
    excludePaths: #不需要登录就能访问的路径
      - /user-service/user/login
      - /user-service/user/hello

所有的请求都请求到网关微服务,由网关微服务再映射到对应的微服务。

例如:http://localhost:8080/user-service/user/hello 不需要登录就能访问

登录代码,登录成功给前端返回token

java 复制代码
/**
     * 登录成功给前端返回token
     * @param name
     * @param password
     * @return
     */
    @GetMapping("/login")
    public Response login(@RequestParam(name = "name") String name, @RequestParam(name = "password") String password){
        // 1、参数是否合法
        if (StringUtil.isEmpty(name) || StringUtil.isEmpty(password)) {
            return Response.fail("用户名或密码不能为空");
        }
        // 2、用户是否存在
        User user = userMapper.login(name, password);
        // 3、用户不存在,登录失败
        if (user == null) {
            return Response.fail("用户不存在");
        }
        // 4、用户存在,使用jwt生成token返给前端
        String token = JWTUtil.createToken(user.getId());
        // 5、将token放入redis,设置有效期限为1分钟。
        String key = "token_" + token;
        redisTemplate.opsForValue().set(key, JSONObject.toJSONString(user),
                System.currentTimeMillis() + 5 * 60 * 1000L, TimeUnit.MILLISECONDS);
        return Response.ok(token);
    }

5、过滤器将token解析为userId,放到请求头里

java 复制代码
package com.niuniu.gateway.filter;

import com.niuniu.common.CommonConstant;
import com.niuniu.gateway.config.AuthProperties;
import com.niuniu.gateway.util.JWTUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.List;

@Component
@RequiredArgsConstructor
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final AuthProperties authProperties;

    private final AntPathMatcher antPathMatcher = new AntPathMatcher();

    private final RedisTemplate<String, String> redisTemplate;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1、获取request
        ServerHttpRequest request = exchange.getRequest();
        // 2、判断是否需要做登录拦截
        if (isExclude(request.getPath().toString())) {
            // 放行
            return chain.filter(exchange);
        }
        // 3、从请求中获取token
        String token = null;
        List<String> heads = request.getHeaders().get("token");
        if (heads != null && !heads.isEmpty()) {
            token = heads.get(0);
        }
        // 4、校验并解析token
        Long userId = JWTUtil.parseToken(token);
        if (userId == null) { // 解析失败
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }
        // 如果登录超时,需要重新登录
        String key = CommonConstant.TOKEN_ + token;
        String value = redisTemplate.opsForValue().get(key);
        if (value == null) { // 登录超时
            ServerHttpResponse response = exchange.getResponse();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        // 5、传递用户信息
        ServerWebExchange swe = exchange.mutate()
                .request(builder -> builder.header(CommonConstant.userInfo, userId.toString()))
                .build();
        System.out.println("userId = " + userId);

        // 6、放行
        return chain.filter(swe);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    private Boolean isExclude(String path) {
        for (String excluedePath : authProperties.getExcludePaths()) {
           if ( antPathMatcher.match(excluedePath, path)) {
               return true;
           }
        }
        return false;
    }
}

6、拦截器将用户信息从请求头中取出来并解析,存放到ThreadLocal里

java 复制代码
package com.niuniu.common.interceptors;

import com.niuniu.common.CommonConstant;
import com.niuniu.common.utils.UserContext;
import jodd.util.StringUtil;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * 前端请求到网关微服务,网关微服务再映射到具体的微服务
 * 如果是微服务之间的调用,此种方式则不能传递用户信息
 */
public class UserInfoInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String userInfo = request.getHeader(CommonConstant.userInfo);
        if (StringUtil.isNotEmpty(userInfo)) {
            UserContext.setUser(Long.parseLong(userInfo));
        }

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
        UserContext.removeUser();
    }
}
java 复制代码
package com.niuniu.common.utils;

public class UserContext {
    public static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public static void setUser(Long userId){
        threadLocal.set(userId);
    }

    public static Long getUser(){
        return threadLocal.get();
    }

    public static void removeUser(){
        threadLocal.remove();
    }
}

7、微服务间通过feign调用,传递用户信息。

java 复制代码
package com.niuniu.common.config;

import com.niuniu.common.CommonConstant;
import com.niuniu.common.utils.UserContext;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfig {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.FULL;
    }

    @Bean
    public RequestInterceptor userInfoRequestInterceptor(){
        return new RequestInterceptor() {
            @Override
            public void apply(RequestTemplate requestTemplate) {
                Long userId = UserContext.getUser();
                if (userId != null) {
                    requestTemplate.header(CommonConstant.userInfo, String.valueOf(userId));
                }
            }
        };
    }
}
java 复制代码
package com.niuniu.user.feignclient;

import com.niuniu.common.config.DefaultFeignConfig;
import com.niuniu.user.model.Order;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@Component
@FeignClient(value = "order-service", configuration = DefaultFeignConfig.class)
public interface OrderClient {
    @GetMapping(value = "/order-service/order/getOrdersByUserId")
    List<Order> getOrdersByUserId(@RequestParam("userId") Long userId);
}
相关推荐
小蒜学长7 分钟前
springboot基于SpringBoot的企业客户管理系统的设计与实现
java·spring boot·后端·spring·小程序·旅游
训山29 分钟前
Java线程池浅谈(创建线程池及线程池任务处理)
java·服务器·数据库
Ujimatsu34 分钟前
虚拟机安装Ubuntu 24.04服务器版(命令行版)
linux·运维·服务器·ubuntu·运维开发
2402_8575893636 分钟前
Spring Boot框架:电商系统的技术展望
java·spring boot·后端
綦枫Maple1 小时前
Jmeter基础篇(22)服务器性能监测工具Nmon的使用
运维·服务器·jmeter·性能监控·nmon
眷怀1 小时前
网卡绑定bonding
linux·运维·服务器·网络·云计算
ぁ'cultrue1 小时前
Linux服务器定时执行jar重启命令
linux·服务器·jar
Neteen1 小时前
七大经典基于比较排序算法【Java实现】
java·算法·排序算法
海无极2 小时前
EDUCODER头哥 SpringBoot 异常处理
java·spring boot·spring
binqian2 小时前
【Linux】内核模版加载modprobe | lsmod
linux·服务器·前端