当前位置: 首页 > article >正文

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

1、引入jwt依赖

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

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

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、网关微服务的依赖

<!-- 负载均衡 -->
        <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

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

/**
     * 登录成功给前端返回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,放到请求头里

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里

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();
    }
}
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调用,传递用户信息。

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));
                }
            }
        };
    }
}
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);
}


http://www.kler.cn/a/391684.html

相关文章:

  • JVM详解:JVM的系统架构
  • 21. Drag-Drop拖放操作(二) - 文件、表格和树的拖放实现
  • macOS解决U盘装完系统容量变小的问题
  • DOM 规范 — MutationObserver 接口
  • C++20 中最优雅的那个小特性 - Ranges
  • 修改yolo格式的labels类别、删除yolo格式的labels类别
  • 2024.11.7- Redis的主从复制集群
  • LayUI组件国际化多国语言版本脚本-下篇根据语种替换
  • idea的mapper.xml文件里写sql语句出现Tag name expected错误提示
  • hadoop健康舆情研究-计算机毕业设计源码05954
  • 【计网】实现reactor反应堆模型 --- 处理数据发回问题 ,异常处理问题
  • TTL电平是什么?
  • PCL 点云分割 分割多个平面
  • 【软考】系统分析师第二版 新增章节 第20章微服务系统分析与设计
  • HTTP的诞生:它解决了哪些网络通信难题?
  • 如何下载西瓜视频没有水印
  • 2024年Postman 下载安装的详细图文教程
  • 【SpringMVC】——Cookie和Session机制
  • 【面试分享】xshell连接Linux服务器22端口执行命令top期间的技术细节和底层逻辑
  • CV图像处理小工具——语义分割json生成检测框json
  • Windows 安装和配置虚拟机
  • python爬虫(二)爬取国家博物馆的信息
  • 使用nossl模式连接MySQL数据库详解
  • 2024 年 10 款替代 Postman 的工具,有免费有开源
  • 我来讲一下-Service Mesh.
  • 【Linux】网络编程3