springcloud+gateway 全局过滤器校验jwt token

1.编写token jwt工具类

package com.baidu.admin.utils;

import io.jsonwebtoken.*;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.*;

public class AppJwtUtil {

    // TOKEN的有效期一天(S)
    private static final int TOKEN_TIME_OUT = 3_600;
    // 加密KEY
    private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
    // 最小刷新间隔(S)
    private static final int REFRESH_TIME = 300;

    // 生产ID
    public static String getToken(Long id){
        Map<String, Object> claimMaps = new HashMap<>();
        claimMaps.put("id",id);
        long currentTime = System.currentTimeMillis();
        return Jwts.builder()
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(currentTime))  //签发时间
                .setSubject("system")  //说明
                .setIssuer("baidu") //签发者信息
                .setAudience("appUser")  //接收用户
                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式
                .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳
                .addClaims(claimMaps) //cla信息
                .compact();
    }

    /**
     * 获取token中的claims信息
     *
     * @param token
     * @return
     */
    private static Jws<Claims> getJws(String token) {
            return Jwts.parser()
                    .setSigningKey(generalKey())
                    .parseClaimsJws(token);
    }

    /**
     * 获取payload body信息
     *
     * @param token
     * @return
     */
    public static Claims getClaimsBody(String token) {
        try {
            return getJws(token).getBody();
        }catch (ExpiredJwtException e){
            return null;
        }
    }

    /**
     * 获取hearder body信息
     *
     * @param token
     * @return
     */
    public static JwsHeader getHeaderBody(String token) {
        return getJws(token).getHeader();
    }

    /**
     * 是否过期
     *
     * @param claims
     * @return -1:有效,0:有效,1:过期,2:过期
     */
    public static int verifyToken(Claims claims) {
        if(claims==null){
            return 1;
        }
        try {
            claims.getExpiration()
                    .before(new Date());
            // 需要自动刷新TOKEN
            if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
                return -1;
            }else {
                return 0;
            }
        } catch (ExpiredJwtException ex) {
            return 1;
        }catch (Exception e){
            return 2;
        }
    }

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }
	//编写主方法进行测试
    public static void main(String[] args) {
       /* Map map = new HashMap();
        map.put("id","11");*/
        System.out.println(AppJwtUtil.getToken(1102L));
        Jws<Claims> jws = AppJwtUtil.getJws("eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAADWLQQqEMAwA_5KzhURNt_qb1KZYQSi0wi6Lf9942NsMw3zh6AVW2DYmDGl2WabkZgreCaM6VXzhFBfJMcMARTqsxIG9Z888QLui3e3Tup5Pb81013KKmVzJTGo11nf9n8v4nMUaEY73DzTabjmDAAAA.4SuqQ42IGqCgBai6qd4RaVpVxTlZIWC826QA9kLvt9d-yVUw82gU47HDaSfOzgAcloZedYNNpUcd18Ne8vvjQA");
        Claims claims = jws.getBody();
        System.out.println(claims.get("id"));

    }

}

2.在service实现类中编写登录模块的登录校验,校验密码是否与数据库相同;

@Service
@Transactional
public class UserLoginServiceImpl extends ServiceImpl<AdUserMapper, AdUser> implements UserLoginService {
    @Override
    public ResponseResult login(AdUserDto dto) {
        //1.参数校验
        if (StringUtils.isEmpty(dto.getName()) || StringUtils.isEmpty(dto.getPassword())) {
            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_REQUIRE, "用户名或密码不能为空");
        }

        Wrapper wrapper = new QueryWrapper<AdUser>();
        ((QueryWrapper) wrapper).eq("name", dto.getName());

        List<AdUser> list = list(wrapper);
        if (list != null && list.size() == 1) {
            AdUser adUser = list.get(0);
			//将用户登录输入的密码加上数据库中的盐,使用MD5加密,判断是否相同
            String pswd = DigestUtils.md5DigestAsHex((dto.getPassword() + adUser.getSalt()).getBytes());
            if (adUser.getPassword().equals(pswd)) {
                Map<String, Object> map = Maps.newHashMap();
                adUser.setPassword("");
                adUser.setSalt("");
                map.put("token", AppJwtUtil.getToken(adUser.getId().longValue()));
                map.put("user", adUser);

                return ResponseResult.okResult(map);
            } else {
                return ResponseResult.errorResult(AppHttpCodeEnum.LOGIN_PASSWORD_ERROR);
            }
        } else {
            return ResponseResult.errorResult(AppHttpCodeEnum.DATA_NOT_EXIST, "用户不存在");
        }
    }
}

3.微服务网关的配置,在spring cloud gateway网关中添加全局过滤器,进行路由和统一的token校验

3.1 如果让客户端直接与各个微服务通信,会有以下的问题:

  • 客户端会多次请求不同的微服务,增加了客户端的复杂性
  • 存在跨域请求,在一定场景下处理相对复杂
  • 认证复杂,每个服务都需要独立认证
  • 难以重构,随着项目的迭代,可能需要重新划分微服务。例如,可能将多个服务合并成一个或者将一个服务拆分成多个。如果客户端直接与微服务通信,那么重构将会很难实施
  • 某些微服务可能使用了防火墙 / 浏览器不友好的协议,直接访问会有一定的困难

3.2跨域请求

跨域只发生在异步请求中,当前域发送异步请求访问其他域

跨域时,浏览器会进行拦截 发送预请求,若预响应的允许地址,请求方式 与 当前域匹配 即可发送

3.3在网关微服务中添加依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-gateway</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
    </dependency>
</dependencies>

3.4application.yml配置

server:
  port: 6001
spring:
  application:
    name: leadnews-admin-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.200.129:8848
    gateway:
      globalcors:
        cors-configurations:
          '[/**]': # 匹配所有请求
            allowedOrigins: "*" #跨域处理 允许所有的域
            allowedMethods: # 支持的方法
            - GET
            - POST
            - PUT
            - DELETE
      routes:
      # 平台管理
      - id: admin
        uri: lb://leadnews-admin
        predicates:  #断言
        - Path=/admin/**          #若路劲匹配,则路由到对应的微服务端口
        filters:  
        - StripPrefix= 1          #过滤掉第一个前缀 admin

3.5编写代码

思路:

  1. 用户进入网关开始登陆,网关过滤器进行判断,如果是登录,则路由到后台管理微服务进行登录

  2. 用户登录成功,后台管理微服务签发JWT TOKEN信息返回给用户

  3. 用户再次进入网关开始访问,网关过滤器接收用户携带的TOKEN

  4. 网关过滤器解析TOKEN ,判断是否有权限,如果有,则放行,如果没有则返回未认证错误

    @Component
    @Log4j2
    public class AuthorizeFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            //1.获取请求对象和响应对象
            ServerHttpRequest request = exchange.getRequest();
            ServerHttpResponse response = exchange.getResponse();
            //2.判断当前的请求是否为登录,如果是,直接放行
            if(request.getURI().getPath().contains("/login/in")){
                //放行
                return chain.filter(exchange);
            }
    
            //3.获取当前用户的请求头jwt信息
            HttpHeaders headers = request.getHeaders();
            String jwtToken = headers.getFirst("token");
    
            //4.判断当前令牌是否存在
            if(StringUtils.isEmpty(jwtToken)){
                //如果不存在,向客户端返回错误提示信息
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
    
            try {
                //5.如果令牌存在,解析jwt令牌,判断该令牌是否合法,如果不合法,则向客户端返回错误信息
                Claims claims = AppJwtUtil.getClaimsBody(jwtToken);
                int result = AppJwtUtil.verifyToken(claims);
                if(result == 0 || result == -1){
                    //5.1 合法,则向header中重新设置userId
                    Integer id = (Integer) claims.get("id");
                    log.info("find userid:{} from uri:{}",id,request.getURI());
                    //重新设置token到header中
                    ServerHttpRequest serverHttpRequest = request.mutate().headers(httpHeaders -> {
                        httpHeaders.add("userId", id + "");
                    }).build();
                    exchange.mutate().request(serverHttpRequest).build();
                }
            }catch (Exception e){
                e.printStackTrace();
                //想客户端返回错误提示信息
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
    
    
            //6.放行
            return chain.filter(exchange);
        }
    
        /**
         * 优先级设置
         * 值越小,优先级越高
         * @return
         */
        @Override
        public int getOrder() {
            return 0;
        }
    }
    

    3.6 可优化携带短标识给前端,jwt数据存入redis,避免因token太长而占用太多的带宽

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: 可以通过实现 Filter 接口来创建一个过滤器,然后在 Spring Boot 使用 @Bean 注解将其注册到应用程序。在过滤器,可以通过 HttpServletRequest 对象获取请求头token,然后进行验证或者其他操作。具体实现可以参考 Spring Boot 官方文档或者相关教程。 ### 回答2: 在使用Spring Boot,可以通过创建一个过滤器来拦截token。下面是一个简单的示例,说明如何实现。 首先,需要创建一个自定义的过滤器类,实现javax.servlet.Filter接口。在实现过滤器时,我们可以在doFilter方法进行token验证的逻辑处理。以下是示例代码: ```java import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class TokenFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // 将请求和响应对象转换成HttpServletRequest和HttpServletResponse对象 HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; // 从请求头获取token String token = httpRequest.getHeader("Authorization"); // 进行token验证的逻辑处理,例如验证token是否过期、是否有效等 // ... // 如果验证通过,将请求继续传递给下一个过滤器或目标资源处理 chain.doFilter(request, response); } // 其他方法,例如init和destroy方法,可以留空不做处理 } ``` 接下来,需要将自定义的过滤器添加到Spring Boot应用程序。可以使用@Configuration注解将过滤器添加为一个Bean,并使用@Order注解指定过滤器的执行顺序。例如: ```java import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; @Configuration public class FilterConfig { @Bean public FilterRegistrationBean<TokenFilter> tokenFilter() { FilterRegistrationBean<TokenFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new TokenFilter()); // 设置过滤器的URL映射规则,例如/*表示拦截所有请求 registrationBean.addUrlPatterns("/*"); // 设置过滤器的执行顺序 registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); return registrationBean; } } ``` 最后,重新启动Spring Boot应用程序,自定义的过滤器就会拦截所有请求,并对token进行验证处理。 请注意,上述示例只是一个简单的演示,实际应用可能需要根据具体的需求进行修改和扩展。例如,可以从数据库或缓存获取token,进行更加复杂的验证逻辑,并在验证失败时返回相应的错误信息。 ### 回答3: 使用Spring Boot实现过滤器拦截Token的步骤如下: 1. 创建一个自定义的过滤器类,实现javax.servlet.Filter接口,并重写doFilter方法。在doFilter方法,可以通过HttpServletRequest对象获取请求头Token信息,并进行相应的验证或处理。 2. 在Spring Boot应用的启动类,通过添加注解@EnableWebSecurity开启Web安全配置,并通过继承WebSecurityConfigurerAdapter类重写configure方法。 3. 在configure方法,使用http对象的addFilterBefore方法将自定义的过滤器添加到过滤器,指定过滤器在哪个过滤器之前进行拦截。 4. 在过滤器,可以进行Token的验证和处理逻辑。例如,可以使用JWT来生成和验证Token,或将Token存储在Redis,根据请求的Token进行校验等。 5. 如果Token验证不通过,可以返回相应的错误信息或重定向到登录页面。如果验证通过,可以进行其他的业务逻辑处理。 总之,通过自定义过滤器并将其添加到Spring Boot的过滤器,可以在请求到达Controller之前进行Token的验证和拦截操作,以实现对请求的安全控制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值