1. 无状态登录原理
1.1 有状态登录
有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理。
例如:用户登陆后,将用户的信息保存一份在服务端的 Session 中,然后响应给客户端一个 Cookie,记录对应的 Session。用户再次发起请求时,我们根据其携带的 Cookie,就能识别到对应的 Session,从而识别用户身份。
有状态登陆的缺点:
- 服务端保存大量数据,增加服务端存储压力
- 如果有多台服务器的话,还需要设置共享 Session。如果不设置共享 Session,多次请求可能访问到不同的服务器,而其他服务器没有保存对应 Session,从而无法识别用户身份。
1.2 无状态登录
无状态登陆,即服务端不保存任何客户端信息,客户端的每次请求必须具备自描述信息,通过这些信息来识别客户端身份。
无状态登陆流程:
- 当客户端第一次请求服务时,服务端对用户进行信息认证
- 认证通过,将用户信息进行加密形成 token,返回给客户端,作为登录凭证
- 以后每次请求,客户端都携带认证的 token
- 服务端再对 token 进行解密,判断是否有效
token 的安全性:
token 是识别客户端身份的唯一标示,肯定要进行严格的加密,以防被人伪造,我们将采用 JWT + RSA 非对称加密。
无状态登陆的优点:
- 减小服务端存储压力
- 客户端请求不依赖服务端的信息,多次请求可以访问不同服务器
1.3 JWT
1.3.1 JWT 简介
JWT( JSON Web Token),是 JSON 风格轻量级的授权和身份认证规范,可实现无状态、分布式的 Web 应用授权。
1.3.2 JWT 的数据格式
JWT 的数据格式包含三部分:
-
Header,头部,由两部分组成:
- token 类型,比如 JWT
- 加密算法名称,比如 RSA
{ "alg":"RS256", "typ":"JWT" }
然后,这部分 JSON 被 Base64 编码,得到第一部分数据。
-
Payload,载荷,用来存放有效数据。一般包含以下信息:
- 用户身份信息(注意:这里因为采用 Base64 编码,可解码,因此不要存放敏感信息)
- 注册声明,如 token 的签发时间,过期时间,签发人等
{ "sub": '1234567890', "name": 'Rick', "admin":true }
然后,这部分 JSON 被 Base64 编码,得到第二部分数据。
-
Signature,签名,是整个数据的认证信息。由三部分组成:
- Header(Base64 编码后的)
- Payload(Base64 编码后的)
- 服务的密钥 secret
然后,这部分通过 Header 中声明的加密方式进行加盐 secret 组合加密,得到第三部分数据。
最终生成的 JWT:
1.3.3 JWT 交互流程
流程图:
- 用户登录
- 服务的认证,通过后根据 secret 生成 token
- 将生成的 token 返回给浏览器
- 用户每次请求携带 token
- 服务端利用公钥解读 JWT 签名,判断签名有效后,从 Payload 中获取用户信息
- 处理请求,返回响应结果
因为 JWT 签发的 token 中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,完全符合了 Rest 的无状态规范。
1.3.4 加密算法
加密技术是对信息进行编码和解码的技术,编码是把原来可读信息(又称明文)译成代码形式(又称密文),其逆过程就是解码(解密)。
加密技术的要点是加密算法,加密算法可以分为三类:
- 对称加密,如 AES
- 基本原理:将明文分成 N 个组,然后使用密钥对各个组进行加密,形成各自的密文,最后把所有的分组密文进行合并,形成最终的密文。
- 优点:算法公开、计算量小、加密速度快、加密效率高
- 缺点:双方都使用同样密钥,安全性得不到保证
- 非对称加密,如 RSA
- 基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端
- 私钥加密,持有私钥或公钥才可以解密
- 公钥加密,持有私钥才可解密
- 优点:安全,难以破解
- 缺点:算法比较耗时
- 基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端
- 不可逆加密,如 MD5,SHA
- 基本原理:加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,无法根据密文推算出明文。
1.4 JWT 结合 Zuul 鉴权
1.4.1 不使用 RSA 加密
- 用户请求登录
- Zuul 网关将请求转发到授权中心,请求授权
- 授权中心校验完成,颁发 JWT 凭证
- 客户端请求其它功能,携带 JWT
- Zuul 将 JWT 交给授权中心校验,通过后放行
- 用户请求到达微服务
- 微服务将 JWT 交给鉴权中心,鉴权同时解析用户信息
- 鉴权中心返回用户数据给微服务
- 微服务处理请求,返回响应
缺点:每次鉴权都需要访问鉴权中心,鉴权中心的访问压力非常大,这种方法显然是不推荐的。
1.4.2 使用 RSA 加密
- 首先利用 RSA 生成公钥和私钥。私钥保存在授权中心,公钥保存在 Zuul 和其他信任的微服务
- 用户请求登录
- 授权中心校验,通过后用私钥对 JWT 进行签名加密
- 返回 JWT 给用户
- 用户携带 JWT 访问
- Zuul 直接通过公钥解密 JWT,进行验证,验证通过则放行
- 请求到达微服务,微服务直接用公钥解析 JWT,获取用户信息
- 微服务处理请求,返回响应
2. 授权中心
2.1 搭建授权中心微服务
授权中心的功能:
- 用户鉴权:接收用户的登录请求,通过用户中心的接口进行校验,通过后使用私钥生成 JWT 并返回
- 服务鉴权:微服务间的调用不经过 Zuul 网关,会有风险,需要鉴权中心进行认证
由于生成 JWT,解析 JWT 这样的方法在其它微服务中也会用到,因此我们会将它们抽取成工具类。我们会把鉴权中心进行聚合,一个工具的 module,一个提供服务的 module。
2.1.1 创建 leyou-auth
-
右键 leyou 项目 --> New Module --> Maven --> Next
-
填写项目信息 --> Next
-
填写保存的位置 --> Finish
-
删除 src 目录
2.1.2 创建 leyou-auth-common
-
右键 leyou-auth 项目 --> New Module --> Maven --> Next
-
填写项目信息 --> Next
-
填写保存的位置 --> Finish
2.1.3 创建 leyou-auth-service
-
右键 leyou-auth 项目 --> New Module --> Maven --> Next
-
填写项目信息 --> Next
-
填写保存的位置 --> Finish
-
最终目录结构
-
添加依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>leyou-auth</artifactId> <groupId>com.leyou.auth</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.leyou.auth</groupId> <artifactId>leyou-auth-service</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.leyou.user</groupId> <artifactId>leyou-user-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <dependency> <groupId>com.leyou.auth</groupId> <artifactId>leyou-auth-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.leyou.common</groupId> <artifactId>leyou-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies> </project>
-
创建引导类
@EnableDiscoveryClient @EnableFeignClients @SpringBootApplication public class LeyouAuthApplication { public static void main(String[] args) { SpringApplication.run(LeyouAuthApplication.class, args); } }
-
编写配置文件
server: port: 8087 spring: application: name: auth-service eureka: client: service-url: defaultZone: http://localhost:10086/eureka registry-fetch-interval-seconds: 10 instance: lease-renewal-interval-in-seconds: 5 lease-expiration-duration-in-seconds: 10
-
目录结构
-
我们修改 leyou-gateway,添加路由规则,对 leyou-auth-service 进行路由。
zuul: prefix: /api routes: item-service: /item/** search-service: /search/** user-service: /user/** auth-service: /auth/**
2.2 JWT 工具类
2.2.1 创建工具类
-
在 leyou-auth-common 中添加依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>leyou-auth</artifactId> <groupId>com.leyou.auth</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <groupId>com.leyou.auth</groupId> <artifactId>leyou-auth-common</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies> </project>
-
在 leyou-auth-common 中导入 JWT 相关工具类,和载荷对象 UserInfo
2.2.2 测试工具类
-
我们在 leyou-auth-common 中编写测试类
public class JwtTest { private static final String pubKeyPath = "C:\\Users\\admin\\Desktop\\tmp\\rsa\\rsa.pub"; private static final String priKeyPath = "C:\\Users\\admin\\Desktop\\tmp\\rsa\\rsa.pri"; private PublicKey publicKey; private PrivateKey privateKey; /** * 生成公钥和私钥 * @throws Exception */ @Test public void testRsa() throws Exception { RsaUtils.generateKey(pubKeyPath, priKeyPath, "234"); } /** * 初始化公钥和私钥 * @throws Exception */ // @Before public void testGetRsa() throws Exception { this.publicKey = RsaUtils.getPublicKey(pubKeyPath); this.privateKey = RsaUtils.getPrivateKey(priKeyPath); } /** * 私钥加密生成 token * @throws Exception */ @Test public void testGenerateToken() throws Exception { // 生成 token String token = JwtUtils.generateToken(new UserInfo(20L, "jack"), privateKey, 5); System.out.println("token = " + token); } /** * 获取 token 中的用户信息 * @throws Exception */ @Test public void testParseToken() throws Exception { String token = "eyJhbGciOiJSUzI1NiJ9.eyJpZCI6MjAsInVzZXJuYW1lIjoiamFjayIsImV4cCI6MTUzMzI4MjQ3N30.EPo35Vyg1IwZAtXvAx2TCWuOPnRwPclRNAM4ody5CHk8RF55wdfKKJxjeGh4H3zgruRed9mEOQzWy79iF1nGAnvbkraGlD6iM-9zDW8M1G9if4MX579Mv1x57lFewzEo-zKnPdFJgGlAPtNWDPv4iKvbKOk1-U7NUtRmMsF1Wcg"; // 解析token UserInfo user = JwtUtils.getInfoFromToken(token, publicKey); System.out.println("id: " + user.getId()); System.out.println("userName: " + user.getUsername()); } }
-
运行 testRsa 方法,生成公钥和私钥
-
取消 @Before 的注释,运行 testGenerateToken 方法,私钥加密生成 token
-
使用上面生成的 token,运行 testParseToken 方法,获取 token 中的用户信息
2.3 编写登录授权接口
接下来,我们需要在 leyou-auth-servcice 编写一个接口,对外提供登录授权服务。基本流程如下:
-
客户端携带用户名和密码请求登录
-
授权中心调用用户中心接口,根据用户名和密码查询用户信息
-
如果用户名密码正确,则能获取用户,否则登录失败
-
如果校验成功,则生成 JWT 并返回
2.3.1 生成公钥和私钥
-
我们需要在授权中心生成真正的公钥和私钥,添加相关配置到 application.yaml
leyou: jwt: secret: vj6ad02cja3ud # 登录校验的密钥 pubKeyPath: D:\tmp\rsa\\rsa.pub # 公钥地址 priKeyPath: D:\tmp\rsa\\rsa.pri # 私钥地址 expire: 30 # 过期时间,单位分钟 cookieName: LY_TOKEN # cookie 名称
-
编写属性读取类
@ConfigurationProperties(prefix = "leyou.jwt") public class JwtProperties { private String secret; // 密钥 private String pubKeyPath;// 公钥 private String priKeyPath;// 私钥 private int expire;// token 过期时间 private PublicKey publicKey; // 公钥 private PrivateKey privateKey; // 私钥 private String cookieName; // cookie 名称 private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class); /** * @PostContruct:在构造方法执行之后执行该方法 */ @PostConstruct public void init(){ try { File pubKey = new File(pubKeyPath); File priKey = new File(priKeyPath); if (!pubKey.exists() || !priKey.exists()) { // 生成公钥和私钥 RsaUtils.generateKey(pubKeyPath, priKeyPath, secret); } // 获取公钥和私钥 this.publicKey = RsaUtils.getPublicKey(pubKeyPath); this.privateKey = RsaUtils.getPrivateKey(priKeyPath); } catch (Exception e) { logger.error("初始化公钥和私钥失败!", e); throw new RuntimeException(); } } // 省略 getter、setter 方法 }
2.3.2 Controller
编写授权接口,我们接收用户名和密码,校验成功后,写入 cookie 中。
- 请求方式:POST
- 请求路径:/accredit
- 请求参数:username 和 password
- 返回结果:无
@RestController
@EnableConfigurationProperties(JwtProperties.class)
public class AuthController {
@Autowired
private AuthService authService;
@Autowired
private JwtProperties jwtProperties;
/**
* 登录授权
*
* @param username
* @param password
* @return
*/
@PostMapping("/accredit")
public ResponseEntity<Void> accredit(
@RequestParam("username") String username,
@RequestParam("password") String password,
HttpServletRequest request,
HttpServletResponse response) {
// 登录校验
String token = this.authService.accredit(username, password);
// 用户校验失败
if (StringUtils.isBlank(token)) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// 将 token 写入 cookie,并指定 httpOnly 为 true,防止通过 JS 获取和修改
CookieUtils.setCookie(request, response, jwtProperties.getCookieName(), token, jwtProperties.getExpire() * 60, null, true);
// 用户校验成功
return ResponseEntity.ok().build();
}
}
2.3.3 FeignClient
接下来我们要对用户密码进行校验,所以我们需要通过 FeignClient 去访问 user-service 微服务。
-
在 leyou-user-interface 工程中添加 API 接口
public interface UserApi { /** * 根据用户名和密码查询用户 * * @param username * @param password * @return */ @GetMapping("query") public User queryUser(@RequestParam("username") String username, @RequestParam("password") String password); }
-
在 leyou-auth 中引入 user-service-interface 依赖
<dependency> <groupId>com.leyou.user</groupId> <artifactId>leyou-user-interface</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
在 leyou-auth-service 中编写 FeignClient
@FeignClient(value = "user-service") public interface UserClient extends UserApi { }
2.3.4 Service
在 leyou-auth-service 中编写 AuthService
@Service
@EnableConfigurationProperties(JwtProperties.class)
public class AuthService {
@Autowired
private UserClient userClient;
@Autowired
private JwtProperties jwtProperties;
/**
* 校验用户名和密码,校验成功则返回 token
* @param username
* @param password
* @return
*/
public String accredit(String username, String password) {
// 根据用户名和密码查询用户
User user = userClient.queryUser(username, password);
// 如果 user 为空,则返回 null
if(user == null) {
return null;
}
// 生成并返回 token
try {
UserInfo userInfo = new UserInfo();
userInfo.setId(user.getId());
userInfo.setUsername(user.getUsername());
String token = JwtUtils.generateToken(userInfo, jwtProperties.getPrivateKey(), jwtProperties.getExpire());
return token;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
2.3.5 项目完整目录
2.3.6 测试
-
启动 leyou-gateway、leyou-register、leyou-user、leyou-auth 四个微服务
-
打开 Postman 发送一条 POST 登录请求,响应 200,成功得到 Cookie
2.4 页面登录
-
进入登录页面,填写用户名和密码,点击登录
-
成功跳转到首页
-
但打开查看 Cookie,却什么都没有
2.5 解决 Cookie 写入问题
2.5.1 跟踪 Cookie
-
在 CookieUtils.setCookie 这一行打上断点
-
Debug 运行 leyou-auth,在页面登录
-
我们单步调试,找到 response 中 cookies 的 domain。
分析:这里我们发现 domain 与我们的域名不匹配。我们的域名为
www.leyou.com
,要想访问到 cookie,那么 domain 应该为.leyou.com
。既然发现是 domain 的值有问题,我们就再来看看 domain 的值是如何设置的。 -
再次 Debug 进入 CookieUtils.setCookie,我们找到 getDomainName 方法,发现 domainName 是根据 serverName 处理后得到的,而我们的 serverName 为 192.168.41.1:8087
-
所以最后 domainName 为 168.41.1
2.5.2 分析 serverName 问题
我们发起请求时,serverName 为 api.leyou.com
,怎么最后就变成了 192.168.41.1:8087
呢?
这里的 serverName 其实就是"请求的时的主机名:Host",之所以改变有两个原因:
-
我们使用了 nginx 反向代理,当监听到 api.leyou.com 的时候,会自动将请求转发至 127.0.0.1:10010
-
而后请求到达我们的网关 Zuul,Zuul 就会根据路径匹配,我们的请求是 /api/auth,根据规则被转发到了 localhost:8087,即我们的授权中心,所以最后 serverName 就变成了 192.168.41.1:8087。
2.5.3 解决问题
-
我们首先去更改 nginx 配置,让它不要修改我们的 host,并重启 nginx。
-
修改网关配置
2.5.4 再次测试
-
重启 leyou-gateway、leyou-register、leyou-user、leyou-auth 四个微服务
-
登录后,成功获得 Cookie
3. 首页显示登录用户
虽然 Cookie 已经成功写入,但是我们首页的顶部还是没有显示登录用户信息
这里需要向后台发起请求,根据 cookie 获取当前用户的信息
3.1 接口说明
我们在 leyou-auth-service 中定义校验用户登录状态接口,通过 cookie 获取 token,然后校验通过返回用户信息。
- 请求方式:GET
- 请求路径:/verify
- 请求参数:无,不过我们需要从 cookie 中获取 token 信息
- 返回结果:UserInfo,校验成功返回用户信息;校验失败,则返回 401
3.2 实现验证用户信息
在 AuthController 中添加 verifyUser 方法
/**
* 校验用户登录状态
*
* @param token
* @return
*/
@GetMapping("verify")
public ResponseEntity<UserInfo> verifyUser(@CookieValue("LY_TOKEN") String token) {
UserInfo userInfo = null;
// 从 token 中解析 token 信息
try {
userInfo = JwtUtils.getInfoFromToken(token, this.jwtProperties.getPublicKey());
// 解析不成功,返回未授权
if(userInfo == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// 解析成功返回用户信息
return ResponseEntity.ok(userInfo);
} catch (Exception e) {
e.printStackTrace();
}
// 出现异常,返回未授权
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
3.3 测试
-
重启 leyou-auth 微服务
-
再次登录,首页成功显示用户信息
3.4 刷新 token 过期时间
每当用户在页面进行新的操作,都应该刷新 token 的过期时间,否则 30 分钟后用户的登录信息就无效了。而刷新其实就是重新生成一份 token,然后写入 cookie 即可。
那么问题来了:我们怎么知道用户有操作呢?
事实上,每当用户来查询其个人信息,就证明他正在浏览网页,此时刷新 cookie 是比较合适的时机。因此我们可以对刚刚的校验用户登录状态的接口进行改进,加入刷新 token 的逻辑。
/**
* 校验用户登录状态
*
* @param token
* @return
*/
@GetMapping("verify")
public ResponseEntity<UserInfo> verifyUser(@CookieValue("LY_TOKEN") String token, HttpServletRequest request, HttpServletResponse response) {
UserInfo userInfo = null;
// 从 token 中解析 token 信息
try {
userInfo = JwtUtils.getInfoFromToken(token, this.jwtProperties.getPublicKey());
// 解析不成功,返回未授权
if(userInfo == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
// 解析成功要重新刷新 token
token = JwtUtils.generateToken(userInfo, this.jwtProperties.getPrivateKey(), this.jwtProperties.getExpire());
// 更新 cookie 中的 token
CookieUtils.setCookie(request, response, this.jwtProperties.getCookieName(), token, this.jwtProperties.getExpire()*60,null,true);
// 解析成功返回用户信息
return ResponseEntity.ok(userInfo);
} catch (Exception e) {
e.printStackTrace();
}
// 出现异常,返回未授权
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}
4. 网关登录拦截器
接下来,我们在 Zuul 编写拦截器,并设置访问路径的白名单。对访问路径不在白名单中的用户进行登录校验,如果发现未登录,则进行拦截。
4.1 JWT 相关配置
如果要进行登录校验,那肯定要先进行 JWT 相关配置。
-
在 leyou-gateway 中添加依赖
<dependency> <groupId>com.leyou.auth</groupId> <artifactId>leyou-auth-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>com.leyou.common</groupId> <artifactId>leyou-common</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
-
编写 application.yaml 配置文件
leyou: jwt: pubKeyPath: D:\tmp\rsa\\rsa.pub # 公钥地址 cookieName: LY_TOKEN # cookie的名称
-
编写属性读取类,读取 jwt 的配置
@ConfigurationProperties(prefix = "leyou.jwt") public class JwtProperties { private String pubKeyPath;// 公钥 private PublicKey publicKey; // 公钥 private String cookieName; private static final Logger logger = LoggerFactory.getLogger(JwtProperties.class); @PostConstruct public void init(){ try { // 获取公钥和私钥 this.publicKey = RsaUtils.getPublicKey(pubKeyPath); } catch (Exception e) { logger.error("初始化公钥失败!", e); throw new RuntimeException(); } } // 省略 getter、setter 方法 }
4.2 编写过滤器
过滤器基本流程:
-
获取 cookie 中的 token
-
通过 JWT 对 token 进行校验
- 通过:放行
- 不通过:返回 401
@Component
@EnableConfigurationProperties(JwtProperties.class)
public class LoginFilter extends ZuulFilter {
@Autowired
private JwtProperties jwtProperties;
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 10;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
// 获取上下文
RequestContext context = RequestContext.getCurrentContext();
// 获取 request
HttpServletRequest request = context.getRequest();
// 获取 token
String token = CookieUtils.getCookieValue(request, this.jwtProperties.getCookieName());
// 校验
try {
// 校验通过什么都不做,即放行
JwtUtils.getInfoFromToken(token, this.jwtProperties.getPublicKey());
} catch (Exception e) {
// 校验出现异常,返回 401
context.setSendZuulResponse(false);
context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
}
4.3 测试
-
重启 leyou-gateway 微服务
-
再次访问首页,发现校验用户登录状态的请求也被拦截了,这显然是不对的,我们对一些路径是不需要进行拦截的。
4.4 设置白名单
-
在 application.yaml 中添加白名单访问路径
leyou: filter: allowPaths: - /api/auth - /api/search - /api/user/register - /api/user/check - /api/user/code - /api/item
-
编写属性读取类,读取 filter 的配置
@ConfigurationProperties(prefix = "leyou.filter") public class FilterProperties { private List<String> allowPaths; public List<String> getAllowPaths() { return allowPaths; } public void setAllowPaths(List<String> allowPaths) { this.allowPaths = allowPaths; } }
-
在过滤器 LoginFilter 中的 shouldFilter 方法中添加判断逻辑,如果请求路径是白名单中的路径,过滤器就不生效。
@Component @EnableConfigurationProperties({JwtProperties.class, FilterProperties.class}) public class LoginFilter extends ZuulFilter { @Autowired private JwtProperties jwtProperties; @Autowired private FilterProperties filterProperties; @Override public String filterType() { return "pre"; } @Override public int filterOrder() { return 10; } @Override public boolean shouldFilter() { // 获取上下文 RequestContext requestContext = RequestContext.getCurrentContext(); // 获取 request HttpServletRequest request = requestContext.getRequest(); // 获取请求路径 String requestURI = request.getRequestURI(); // 判断是否在白名单中 for (String path : this.filterProperties.getAllowPaths()) { // 然后判断是否是符合 if (StringUtils.contains(requestURI, path)) { return false; } } return true; } @Override public Object run() throws ZuulException { // 获取上下文 RequestContext context = RequestContext.getCurrentContext(); // 获取 request HttpServletRequest request = context.getRequest(); // 获取 token String token = CookieUtils.getCookieValue(request, this.jwtProperties.getCookieName()); // 校验 try { // 校验通过什么都不做,即放行 JwtUtils.getInfoFromToken(token, this.jwtProperties.getPublicKey()); } catch (Exception e) { // 校验出现异常,返回 401 context.setSendZuulResponse(false); context.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value()); } return null; } }
4.5 再次测试
-
重启 leyou-gateway 微服务
-
在白名单中的请求,没有被过滤