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