目录:
(1)用户认证与服务网关整合
(2)server-gateway网关配置
(3)在服务网关中判断用户登录状态
(4)登录流程
(1)用户认证与服务网关整合
实现思路
- 所有请求都会经过服务网关,服务网关对外暴露服务,不管是api异步请求还是web同步请求都走网关,在网关进行统一用户认证
- 既然要在网关进行用户认证,网关得知道对哪些url进行认证,所以我们得对url制定规则
- Web页面同请求(如:*.html),我采取配置白名单的形式,凡是配置在白名单里面的请求都是需要用户认证的(注:也可以采取域名的形式,方式多多)
- 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{@Autowiredprivate RedisTemplate redisTemplate;// 匹配路径的工具类private AntPathMatcher antPathMatcher = new AntPathMatcher();//获取nacos配置文件中的配置@Value("${authUrls.url}")private String authUrls;@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 获取到请求对象ServerHttpRequest request = exchange.getRequest();// 获取UrlString path = request.getURI().getPath();// 如果是内部接口,则网关拦截不允许外部访问!if (antPathMatcher.match("/**/inner/**",path)){ServerHttpResponse response = exchange.getResponse();return out(response,ResultCodeEnum.PERMISSION);}// 获取用户IdString 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);}}// 验证urlfor (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)在服务网关中判断用户登录状态
在网关中如何获取用户信息:
- 从cookie中获取(如:web同步请求)
- 从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);//获取ipString ip = userJson.getString("ip");//获取请求的ipString 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));
}
当退出登录后,点击
跳转到登录页面
登录后:
会跳转到点击的页面
后面就可以用了
(4)登录流程
退出:
网关中的过滤器:如果有多个过滤器可以实现Order,执行过滤器的执行顺序,谁的越小谁就先执行