目录:
(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{
@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)在服务网关中判断用户登录状态
在网关中如何获取用户信息:
- 从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);
//获取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,执行过滤器的执行顺序,谁的越小谁就先执行