尚品汇-网关过滤用户请求、登录流程(三十五)

目录:

(1)用户认证与服务网关整合

(2)server-gateway网关配置  

(3)在服务网关中判断用户登录状态

(4)登录流程

(1)用户认证与服务网关整合

 实现思路

  1. 所有请求都会经过服务网关,服务网关对外暴露服务,不管是api异步请求还是web同步请求都走网关,在网关进行统一用户认证
  2. 既然要在网关进行用户认证,网关得知道对哪些url进行认证,所以我们得对url制定规则
  3. Web页面同请求(如:*.html),我采取配置白名单的形式,凡是配置在白名单里面的请求都是需要用户认证的(注:也可以采取域名的形式,方式多多)
  4. Api接口异步请求的,我们采取url规则匹配,如:/api/**/auth/**,如凡是满足该规则的都必须用户认证

redis:

  host: 192.168.200.129

  port: 6379

  database: 0

  timeout: 1800000

  password:

 
 

authUrls:

  url: trade.html,myOrder.html,list.html

(2)server-gateway网关配置  

pom.xml

<dependency>

   <groupId>org.springframework.boot</groupId>

   <artifactId>spring-boot-starter-data-redis-reactive</artifactId>

</dependency>

在网关中添加redis的配置类。

注意需要引入RedisConfig配置类:因为这个模块没有扫描service-util

server-gateway 项目中添加一个过滤器

ResultCodeEnum :

package com.atguigu.gmall.common.result;

import lombok.Getter;

/**
 * 统一返回结果状态信息类
 *
 */
@Getter
public enum ResultCodeEnum {

    SUCCESS(200,"成功"),
    FAIL(201, "失败"),
    SERVICE_ERROR(2012, "服务异常"),
    ILLEGAL_REQUEST( 204, "非法请求"),
    PAY_RUN(205, "支付中"),

    LOGIN_AUTH(208, "未登陆"),
    PERMISSION(209, "没有权限"),
    SECKILL_NO_START(210, "秒杀还没开始"),
    SECKILL_RUN(211, "正在排队中"),
    SECKILL_NO_PAY_ORDER(212, "您有未支付的订单"),
    SECKILL_FINISH(213, "已售罄"),
    SECKILL_END(214, "秒杀已结束"),
    SECKILL_SUCCESS(215, "抢单成功"),
    SECKILL_FAIL(216, "抢单失败"),
    SECKILL_ILLEGAL(217, "请求不合法"),
    SECKILL_ORDER_SUCCESS(218, "下单成功"),
    COUPON_GET(220, "优惠券已经领取"),
    COUPON_LIMIT_GET(221, "优惠券已发放完毕"),
    ;

    private Integer code;

    private String message;

    private ResultCodeEnum(Integer code, String message) {
        this.code = code;
        this.message = message;
    }
}
package com.atguigu.gmall.gateway.filter;

@Component
public class AuthGlobalFilter implements GlobalFilter{

    @Autowired
    private RedisTemplate redisTemplate;

    // 匹配路径的工具类
      private AntPathMatcher antPathMatcher = new AntPathMatcher();
 
    //获取nacos配置文件中的配置
    @Value("${authUrls.url}")
    private String authUrls;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 获取到请求对象
        ServerHttpRequest request = exchange.getRequest();
        // 获取Url
        String path = request.getURI().getPath();
        // 如果是内部接口,则网关拦截不允许外部访问!
        if (antPathMatcher.match("/**/inner/**",path)){
            ServerHttpResponse response = exchange.getResponse();
            return out(response,ResultCodeEnum.PERMISSION);
        }
        // 获取用户Id
        String userId = getUserId(request);

//token被盗用
       if("-1".equals(userId)) {
          ServerHttpResponse response = exchange.getResponse();
          return out(response,ResultCodeEnum.PERMISSION);
       }
        // 用户登录认证
        //api接口,异步请求,校验用户必须登录
        if(antPathMatcher.match("/api/**/auth/**", path)) {
            if(StringUtils.isEmpty(userId)) {
                ServerHttpResponse response = exchange.getResponse();
                return out(response,ResultCodeEnum.LOGIN_AUTH);
            }
        }
        // 验证url
        for (String authUrl : authUrls.split(",")) {
            // 当前的url包含登录的控制器域名,但是用户Id 为空!
            if (path.indexOf(authUrl)!=-1 && StringUtils.isEmpty(userId)){
                ServerHttpResponse response = exchange.getResponse();
                //303状态码表示由于请求对应的资源存在着另一个URI,应使用重定向获取请求的资源
                response.setStatusCode(HttpStatus.SEE_OTHER);
             
             //设置地址location   originUrl当前请求的地址   
          
         response.getHeaders().set(HttpHeaders.LOCATION,"http://www.gmall.com/login.html?originUrl="+request.getURI());
                // 重定向到登录
                return response.setComplete();
            }
        }

         
         //存储userId到请求中
        // 将userId 传递给后端
        if (!StringUtils.isEmpty(userId)){
            request.mutate().header("userId",userId).build();
            // 将现在的request 变成 exchange对象
            return chain.filter(exchange.mutate().request(request).build());
        }
        return chain.filter(exchange);
    }







/**
 * 获取当前登录用户id
 * @param request
 * @return
 */
//public static String getUserId(HttpServletRequest request) {
//    String userId = request.getHeader("userId");
//    return StringUtils.isEmpty(userId) ? "" : userId;
// }

(3)在服务网关中判断用户登录状态

在网关中如何获取用户信息:

  1. 从cookie中获取(如:web同步请求)
  2. 从header头信息中获取(如:异步请求)

如何判断用户信息合法:

登录时我们返回用户token,在服务网关中获取到token后,我在到redis中去查看用户id,如果用户id存在,则token合法,否则不合法,同时校验ip,防止token被盗用。

/**
 * 获取当前登录用户id
 * @param request
 * @return
 */
private String getUserId(ServerHttpRequest request) {
    String token = "";
    //头信息
    List<String> tokenList = request.getHeaders().get("token");
    if(null  != tokenList) {
        token = tokenList.get(0);
    } else {

        //从cookie中获取
        MultiValueMap<String, HttpCookie> cookieMultiValueMap =  request.getCookies();
        HttpCookie cookie = cookieMultiValueMap.getFirst("token");
        if(cookie != null){
            token = URLDecoder.decode(cookie.getValue());
        }
    }


//获取数据
  if(!StringUtils.isEmpty(token)) {
        //从Redis中获取
        String userStr = (String)redisTemplate.opsForValue().get("user:login:" + token);
        //转换 
        JSONObject userJson = JSONObject.parseObject(userStr);
        //获取ip
        String ip = userJson.getString("ip");
        //获取请求的ip
        String curIp = IpUtil.getGatwayIpAddress(request);
        //校验token是否被盗用
        if(ip.equals(curIp)) {
            return userJson.getString("userId");
        } else {
            //ip不一致
            return "-1";
        }
    }
    return "";
}

 

输入信息out 方法

// 接口鉴权失败返回数据
private Mono<Void> out(ServerHttpResponse response,ResultCodeEnum resultCodeEnum) {
    // 返回用户没有权限登录
    Result<Object> result = Result.build(null, resultCodeEnum);
    byte[] bits = JSONObject.toJSONString(result).getBytes(StandardCharsets.UTF_8);

    //获取DataBuffer 
    DataBuffer wrap = response.bufferFactory().wrap(bits);

    //乱码处理
    response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
    // 输入到页面
    return response.writeWith(Mono.just(wrap));
}

AuthGlobalFilter :总代码

package com.atguigu.gmall.gateway.filter;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.IpUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
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.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;

@Component
public class AuthGlobalFilter implements GlobalFilter{

    //路劲匹配工具
    private AntPathMatcher matcher=new AntPathMatcher();

    @Autowired
    private RedisTemplate redisTemplate;

    //读取白名单
    @Value("${authUrls.url}")
    private String authUrls;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取请求对象
        ServerHttpRequest request = exchange.getRequest();
        //获取响应对象
        ServerHttpResponse response = exchange.getResponse();
        //获取资源路径
        String path = request.getURI().getPath();

        //判断内部接口--拒绝
        if(matcher.match("/api/**/inner/**",path)){
            return out(response, ResultCodeEnum.PERMISSION);
        }

        //获取用户id
        String userId=this.getUserId(request);

        //判断ip是否被盗
        if("-1".equals(userId)){

            return out(response,ResultCodeEnum.ILLEGAL_REQUEST);
        }
        //判断
        if(matcher.match("/api/**/auth/**",path)){

            //判断未登录
            if(StringUtils.isEmpty(userId)){

                return out(response,ResultCodeEnum.LOGIN_AUTH);
            }
        }

        //认证白名单
        if(!StringUtils.isEmpty(authUrls)){
            String[] split = authUrls.split(",");
            for (String url : split) {

                if(path.indexOf(url)!=-1&&StringUtils.isEmpty(userId)){
                    //表示包含白名单的路径,并且没有登录

                    //设置状态码
                    response.setStatusCode(HttpStatus.SEE_OTHER);
                    //设置地址 location
                    //originUrl 当前请求的访问地址
                    response.getHeaders().set(HttpHeaders.LOCATION,"http://www.gmall.com/login.html?originUrl="+request.getURI());

                    //重定向
                    return response.setComplete();

                }

            }

        }


        //获取临时id
        String userTempId=this.getUserTempId(request);

        //存储userId到请求
        if(!StringUtils.isEmpty(userId)||!StringUtils.isEmpty(userTempId)){

            if(!StringUtils.isEmpty(userId)){

                //存储到request
                request.mutate().header("userId",userId).build();

            }
            if(!StringUtils.isEmpty(userTempId)){

                //存储到request
                request.mutate().header("userTempId",userTempId).build();

            }

            return chain.filter(exchange.mutate().request(request).build());

        }


        //放行
        return chain.filter(exchange);
    }

    /**
     * 获取用户临时id
     * @param request
     * @return
     */
    private String getUserTempId(ServerHttpRequest request) {
        String userTempId="";
        //从头信息
        List<String> list = request.getHeaders().get("userTempId");
        //判断
        if(!CollectionUtils.isEmpty(list)){
            userTempId=list.get(0);
        }
        //判断,从cookie取
        if(StringUtils.isEmpty(userTempId)){
            HttpCookie cookie = request.getCookies().getFirst("userTempId");
            if(cookie!=null){

                userTempId=cookie.getValue();
            }

        }


        return userTempId;
    }


    /**
     * 获取用户id
     * 分析:有几种请求
     * 1.获取不到 ""
     * 2.获取到  userId
     * 3.ip不对应 盗取ip  -1
     *
     *
     * @param request
     * @return
     */
    private String getUserId(ServerHttpRequest request) {

        //定义变量记录token
        String token=null;
        //获取token --头信息
        List<String> list = request.getHeaders().get("token");
        //判断
        if(!CollectionUtils.isEmpty(list)){

           token=list.get(0);
        }

        //获取token --cookie
        if(StringUtils.isEmpty(token)){

            //获取所有的cookie
            MultiValueMap<String, HttpCookie> cookies = request.getCookies();
            HttpCookie cookie = cookies.getFirst("token");
            //判断
            if(cookie!=null){

               token=cookie.getValue();
            }


        }

        //获取数据
        //判断
        if(!StringUtils.isEmpty(token)){

            //从redis中获取
            String strJson = (String) redisTemplate.opsForValue().get("user:login:" + token);
            //转换
            JSONObject jsonObject = JSONObject.parseObject(strJson);
            //判断ip
            String ip = jsonObject.getString("ip");
            //获取当前请求的ip
            String curIp = IpUtil.getGatwayIpAddress(request);
            //判断ip
            if(curIp.equals(ip)){

                return jsonObject.getString("userId");
            }else{

                return "-1";
            }


        }





        //没有用户id
        return "";
    }

    /**
     * 设置响应结果
     * @param response
     * @param permission
     * @return
     */
    private Mono<Void> out(ServerHttpResponse response, ResultCodeEnum permission) {


        Result<Object> build = Result.build(null, permission);

        byte[] bytes = JSONObject.toJSONString(build).getBytes(StandardCharsets.UTF_8);

        //获取DataBuffer
        DataBuffer wrap = response.bufferFactory().wrap(bytes);
        //中文乱码处理
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");

        return response.writeWith(Mono.just(wrap));
    }

}

 

当退出登录后,点击 

跳转到登录页面

登录后:

会跳转到点击的页面

后面就可以用了

(4)登录流程

退出:

 

网关中的过滤器:如果有多个过滤器可以实现Order,执行过滤器的执行顺序,谁的越小谁就先执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

喵俺第一专栏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值