GateWay网关整合JWT进行认证
- cloud-auth服务:模拟登陆,获取token
- cloud-gateway服务:所有请求通过网关
cloud-auth服务里面的模拟登陆的接口
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yixun.cloudauth.domain.Student;
import com.yixun.cloudauth.rest.entity.RestResult;
import com.yixun.cloudauth.rest.entity.RestResultCode;
import com.yixun.cloudauth.rest.generator.RestResultGenerator;
import com.yixun.cloudauth.service.StudentService;
import com.yixun.cloudauth.utils.JwtModel;
import com.yixun.cloudauth.utils.JwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
/**
* @author ma
* @date 2022/5/12
*/
@RestController
@Slf4j
@RequestMapping("/auth")
public class AuthController {
@Autowired
private StudentService studentService;
private ObjectMapper objectMapper;
@Value("1h")
private String effectiveTime;
public AuthController(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@PostMapping("/login")
public RestResult<String> login(@RequestBody Student student) throws Exception {
//判断登陆的用户信息是否正确
RestResult<Student> studentByName = studentService.findStudentByName(student.getSname());
if(studentByName==null || !studentByName.getBody().getData().getPassword().equals(student.getPassword())){
return RestResultGenerator.failure("用户名与密码不正确");
}
ArrayList<String> roleIdList = new ArrayList<>(1);
roleIdList.add("role_test_1");
JwtModel jwtModel = new JwtModel(student.getSname(),"000000",roleIdList);
int effectivTimeInt = Integer.valueOf(effectiveTime.substring(0,effectiveTime.length()-1));
String effectivTimeUnit = effectiveTime.substring(effectiveTime.length()-1,effectiveTime.length());
String jwt = null;
switch (effectivTimeUnit){
case "s" :{
//秒
jwt = JwtUtil.createJWT("test", "test", objectMapper.writeValueAsString(jwtModel), effectivTimeInt * 1000L);
break;
}
case "m" :{
//分钟
jwt = JwtUtil.createJWT("test", "test", objectMapper.writeValueAsString(jwtModel), effectivTimeInt * 60L * 1000L);
break;
}
case "h" :{
//小时
jwt = JwtUtil.createJWT("test", "test", objectMapper.writeValueAsString(jwtModel), effectivTimeInt * 60L * 60L * 1000L);
break;
}
case "d" :{
//小时
jwt = JwtUtil.createJWT("test", "test", objectMapper.writeValueAsString(jwtModel), effectivTimeInt * 24L * 60L * 60L * 1000L);
break;
}
}
return RestResultGenerator.success("认证成功",jwt);
}
}
cloud-gateway服务里面的认证过滤器
/**
* @author ma
* @date 2022/5/12
*/
@Component
@Setter
@Getter
@Slf4j
public class JwtTokenFilter implements GlobalFilter, Ordered {
private ObjectMapper objectMapper;
public JwtTokenFilter(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String url = exchange.getRequest().getURI().getPath();
//跳过不需要验证的路径
//可以在配置文件中配置该路径
String skipAuthUrls = "/cloud-auth/auth/login";
String skip1 = "/cloud-student/v2/api-docs";
String skip2 = "/cloud-class/v2/api-docs";
if(skipAuthUrls.equals(url) || skip1.equals(url) || skip2.equals(url)){
return chain.filter(exchange);
}
//获取token
String token = exchange.getRequest().getHeaders().getFirst("Authorization");
ServerHttpResponse resp = exchange.getResponse();
if(StringUtils.isBlank(token)){
//没有token
return authErro(resp,"请先认证");
}else{
//有token
try {
if(JwtUtil.checkToken(token,objectMapper)) {
return chain.filter(exchange);
}else{
return authErro(resp,"认证失败");
}
}catch (ExpiredJwtException e){
log.error(e.getMessage(),e);
if(e.getMessage().contains("Allowed clock skew")){
return authErro(resp,"认证过期");
}else{
return authErro(resp,"认证失败");
}
}catch (Exception e) {
log.error(e.getMessage(),e);
return authErro(resp,"认证失败");
}
}
}
private Mono<Void> authErro(ServerHttpResponse resp, String mess) {
resp.setStatusCode(HttpStatus.UNAUTHORIZED);
resp.getHeaders().add("Content-Type","application/json;charset=UTF-8");
RestResult<Object> failure = RestResultGenerator.failure(RestResultCode.AUTHORIZATION_CODE_NOT_FOUND, mess);
String returnStr = "";
try {
returnStr = objectMapper.writeValueAsString(failure);
} catch (JsonProcessingException e) {
log.error(e.getMessage(),e);
}
DataBuffer buffer = resp.bufferFactory().wrap(returnStr.getBytes(StandardCharsets.UTF_8));
return resp.writeWith(Flux.just(buffer));
}
@Override
public int getOrder() {
return -100;
}
}
实现效果
- 调用cloud-auth服务中的login接口,获取到对应的token
- 请求其他为放行路径的时候,提示认证过期
- 在请求头加入对应的token信息后,请求成功访问
JWT的工具类
/**
* @author ma
* @date 2022/5/12
*/
@Slf4j
public class JwtUtil {
@Autowired
private RestTemplate restTemplate;
public static final String KEY = "xiaoma";
/**
* 由字符串生成加密key
*
* @return
*/
public static SecretKey generalKey(){
byte[] encodedKey = Base64.decodeBase64(KEY);
SecretKeySpec key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
return key;
}
public static String createJWT(String id, String issuer, String subject, long ttlMillis) throws Exception {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成JWT的时间
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
Map<String, Object> claims = new HashMap<>();
claims.put("uid", "123456");
claims.put("user_name", "admin");
claims.put("nick_name", "X-rapido");
SecretKey key = generalKey();
JwtBuilder builder = Jwts.builder()
.setClaims(claims)
.setId(id)
.setIssuedAt(now)
.setIssuer(issuer)
.setSubject(subject)
.signWith(signatureAlgorithm, key);
// 设置过期时间
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}
return builder.compact();
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey key = generalKey();
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwt).getBody();
return claims;
}
public static boolean checkToken(String jwtToken, ObjectMapper objectMapper) throws Exception {
//TODO 根据自己的业务修改
//解析token的时候,过期了会抛出异常
Claims claims = JwtUtil.parseJWT(jwtToken);
String subject = claims.getSubject();
JwtModel jwtModel = objectMapper.readValue(subject, JwtModel.class);
/*
TODO 对jwt里面的用户信息做判断
根据自己的业务编写
在jwtModel取登陆的用户信息,与当前登陆的用户信息进行比较
*/
String tokenUsername = jwtModel.getUserName();
/*
获取token的过期时间,和当前时间作比较,如果小于当前时间,则token过期
*/
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
Date expiration = claims.getExpiration();
return true;
}
}
/**
* @author ma
* @date 2022/5/12
*/
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class JwtModel {
private String userName;
private String password;
private List<String> roleIdList;
}