一.实现登录校验

首先我们看一看项目中有关登录的文件。登录校验需要用到JWT,而且JWT的加密需要秘钥和加密工具。这些在hm-service
中已经有了,我们直接拷贝过来:

具体作用如下:
AuthProperties
:配置登录校验需要拦截的路径,因为不是所有的路径都需要登录才能访问
JwtProperties
:定义与JWT工具有关的属性,比如秘钥文件位置
SecurityConfig
:工具的自动装配
JwtTool
:JWT工具,其中包含了校验和解析token
的功能
hmall.jks
:秘钥文件,其加密和解密要使用在yaml配置文件中的password
hm:jwt:location: classpath:hmall.jksalias: hmallpassword: hmall123tokenTTL: 30mauth:excludePaths:- /search/**- /users/login- /hi
package com.hmall.gateway.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.core.io.Resource;import java.time.Duration;@Data
@ConfigurationProperties(prefix = "hm.jwt")
public class JwtProperties {private Resource location;private String password;private String alias;private Duration tokenTTL = Duration.ofMinutes(10);
}
其中auth:excludePaths中配置的是不需要token验证的请求路径。
package com.hmall.gateway.config;import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;import java.util.List;@Data
@ConfigurationProperties(prefix = "hm.auth")
@Component
public class AuthProperties {private List<String> includePaths;private List<String> excludePaths;
}
我们在一会儿进行登录时要校验是否需要登录校验,如果不需要就直接放行。
package com.hmall.gateway.filter;import com.hmall.common.exception.UnauthorizedException;
import com.hmall.common.utils.CollUtils;
import com.hmall.gateway.config.AuthProperties;
import com.hmall.gateway.util.JwtTool;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
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.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;import java.util.List;@Component
@RequiredArgsConstructor
@EnableConfigurationProperties(AuthProperties.class)
public class AuthGlobalFilter implements GlobalFilter, Ordered {private final JwtTool jwtTool;private final AuthProperties authProperties;private final AntPathMatcher antPathMatcher = new AntPathMatcher();@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取RequestServerHttpRequest request = exchange.getRequest();// 2.判断是否不需要拦截if(isExclude(request.getPath().toString())){// 无需拦截,直接放行return chain.filter(exchange);}// 3.获取请求头中的tokenString token = null;List<String> headers = request.getHeaders().get("authorization");if (!CollUtils.isEmpty(headers)) {token = headers.get(0);}// 4.校验并解析tokenLong userId = null;try {userId = jwtTool.parseToken(token);} catch (UnauthorizedException e) {// 如果无效,拦截ServerHttpResponse response = exchange.getResponse();response.setRawStatusCode(401);return response.setComplete();}// TODO 5.如果有效,传递用户信息System.out.println("userId = " + userId);// 6.放行return chain.filter(exchange);}private boolean isExclude(String antPath) {for (String pathPattern : authProperties.getExcludePaths()) {if(antPathMatcher.match(pathPattern, antPath)){return true;}}return false;}@Overridepublic int getOrder() {return 0;}
}
1.首先获取到请求路径。
2.然后判断是否需要登录校验,如果不需要就直接放行,这里判断使用AntPathMatcher对象的match方法进行路径匹配。
3.不需要直接放行,就要进行登录校验。获取请求头为authorization的值。即为token。
4.利用工具类校验并解析token。
package com.hmall.gateway.utils;import cn.hutool.core.exceptions.ValidateException;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTValidator;
import cn.hutool.jwt.signers.JWTSigner;
import cn.hutool.jwt.signers.JWTSignerUtil;
import com.hmall.common.exception.UnauthorizedException;
import org.springframework.stereotype.Component;import java.security.KeyPair;
import java.time.Duration;
import java.util.Date;@Component
public class JwtTool {private final JWTSigner jwtSigner;public JwtTool(KeyPair keyPair) {this.jwtSigner = JWTSignerUtil.createSigner("rs256", keyPair);}/*** 创建 access-token** @param userId 用户id* @return access-token*/public String createToken(Long userId, Duration ttl) {// 1.生成jwsreturn JWT.create().setPayload("user", userId).setExpiresAt(new Date(System.currentTimeMillis() + ttl.toMillis())).setSigner(jwtSigner).sign();}/*** 解析token** @param token token* @return 解析刷新token得到的用户信息*/public Long parseToken(String token) {// 1.校验token是否为空if (token == null) {throw new UnauthorizedException("未登录");}// 2.校验并解析jwtJWT jwt;try {jwt = JWT.of(token).setSigner(jwtSigner);} catch (Exception e) {throw new UnauthorizedException("无效的token", e);}// 2.校验jwt是否有效if (!jwt.verify()) {// 验证失败throw new UnauthorizedException("无效的token");}// 3.校验是否过期try {JWTValidator.of(jwt).validateDate();} catch (ValidateException e) {throw new UnauthorizedException("token已经过期");}// 4.数据格式校验Object userPayload = jwt.getPayload("user");if (userPayload == null) {// 数据为空throw new UnauthorizedException("无效的token");}// 5.数据解析try {return Long.valueOf(userPayload.toString());} catch (RuntimeException e) {// 数据格式有误throw new UnauthorizedException("无效的token");}}
}
一旦解析失败,抛出异常,终止运行。但是在网关里终止运行如果抛异常来终止,页面状态码为500,彰显不出是因为登录失败而被拦截。因此我们会用http状态码来表示,如401,代表未登录/未授权。
// 给前端抛出一个响应状态码:401 代表未授权,拦截前端发起的请求
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete(); // response.setComplete()返回值是Mono<Void>
定义响应,并设置响应状态码为401。然后返回,这里直接终止,响应完成。response.setComplete()返回值是Mono<Void>。
5.传递用户信息(下一节)
6.放行