feat:退出登录添加JWT至黑名单失效

This commit is contained in:
haoxr 2020-09-27 00:38:55 +08:00
parent fdf50325c2
commit da7a012d7d
9 changed files with 62 additions and 78 deletions

View File

@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.youlai.auth.exception.CustomOAuth2Exception;
import com.youlai.common.core.result.ResultCodeEnum;
import lombok.SneakyThrows;

View File

@ -1,7 +1,7 @@
package com.youlai.auth.component;
import com.youlai.auth.exception.CustomOAuth2Exception;
import com.youlai.common.core.result.ResultCodeEnum;
import com.youlai.common.core.result.ResultCode;
import lombok.SneakyThrows;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@ -63,12 +63,12 @@ public class OAuth2WebResponseExceptionTranslator implements WebResponseExceptio
private static class InvalidException extends OAuth2Exception {
public InvalidException() {
super(ResultCodeEnum.USER_PASSWORD_ERROR.getMsg());
super(ResultCode.USERNAME_OR_PASSWORD_ERROR.getMsg());
}
@Override
public String getOAuth2ErrorCode() {
return ResultCodeEnum.USER_PASSWORD_ERROR.getCode();
return ResultCode.USERNAME_OR_PASSWORD_ERROR.getCode();
}
@Override

View File

@ -1,40 +1,38 @@
package com.youlai.auth.controller;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.JWSObject;
import com.youlai.admin.api.dto.UserDTO;
import com.youlai.auth.domain.Oauth2Token;
import com.youlai.common.core.constant.AuthConstants;
import com.youlai.common.core.result.Result;
import com.youlai.common.core.result.ResultCode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.annotations.ApiIgnore;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
import java.text.ParseException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Api(tags = "认证中心认证登录")
@Api(tags = "认证中心")
@RestController
@RequestMapping("/oauth")
@AllArgsConstructor
public class AuthController {
@Resource
private TokenEndpoint tokenEndpoint;
@Autowired
private RedisTemplate redisTemplate;
@ApiOperation("Oauth2获取token")
@ -61,14 +59,19 @@ public class AuthController {
}
@DeleteMapping("/logout")
public Result logout(HttpServletRequest request) throws ParseException {
String token = request.getHeader(AuthConstants.JWT_TOKEN_HEADER);
JWSObject jwsObject = JWSObject.parse(token);
String payload = jwsObject.getPayload().toString(); // jwt 载体部分
UserDTO userDTO = JSONUtil.toBean(payload, UserDTO.class);
redisTemplate.opsForValue().set("", "");
return null;
public Result logout(HttpServletRequest request) {
String payload = request.getHeader(AuthConstants.JWT_PAYLOAD_KEY);
JSONObject jsonObject = JSONUtil.parseObj(payload);
String jti = jsonObject.getStr("jti"); // JWT唯一标识
long exp = jsonObject.getLong("exp"); // JWT过期时间戳
long currentTimeSeconds = System.currentTimeMillis() / 1000;
if (exp < currentTimeSeconds) { // token已过期
return Result.custom(ResultCode.INVALID_TOKEN_OR_EXPIRED);
}
redisTemplate.opsForValue().set(AuthConstants.TOKEN_BLACKLIST_PREFIX + jti, null, (exp - currentTimeSeconds), TimeUnit.SECONDS);
return Result.success();
}
}

View File

@ -20,7 +20,6 @@ import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.*;
/**

View File

@ -2,7 +2,7 @@ package com.youlai.gateway.component;
import cn.hutool.json.JSONUtil;
import com.youlai.common.core.result.Result;
import com.youlai.common.core.result.ResultCodeEnum;
import com.youlai.common.core.result.ResultCode;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@ -29,7 +29,7 @@ public class CustomServerAccessDeniedHandler implements ServerAccessDeniedHandle
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin","*");
response.getHeaders().set("Cache-Control","no-cache");
String body= JSONUtil.toJsonStr(Result.custom(ResultCodeEnum.USER_ACCESS_UNAUTHORIZED));
String body= JSONUtil.toJsonStr(Result.custom(ResultCode.USER_ACCESS_UNAUTHORIZED));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer));
}

View File

@ -2,7 +2,7 @@ package com.youlai.gateway.component;
import cn.hutool.json.JSONUtil;
import com.youlai.common.core.result.Result;
import com.youlai.common.core.result.ResultCodeEnum;
import com.youlai.common.core.result.ResultCode;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@ -29,7 +29,7 @@ public class CustomServerAuthenticationEntryPoint implements ServerAuthenticatio
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin", "*");
response.getHeaders().set("Cache-Control", "no-cache");
String body = JSONUtil.toJsonStr(Result.custom(ResultCodeEnum.USER_ACCOUNT_UNAUTHENTICATED));
String body = JSONUtil.toJsonStr(Result.custom(ResultCode.USER_ACCOUNT_UNAUTHENTICATED));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer));
}

View File

@ -6,14 +6,12 @@ import com.youlai.common.core.constant.AuthConstants;
import com.youlai.gateway.component.AuthorizationManager;
import com.youlai.gateway.component.CustomServerAuthenticationEntryPoint;
import com.youlai.gateway.component.CustomServerAccessDeniedHandler;
import com.youlai.gateway.filter.WhiteListRemoveJwtFilter;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;

View File

@ -1,26 +1,42 @@
package com.youlai.gateway.filter;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.nimbusds.jose.JWSObject;
import com.youlai.common.core.constant.AuthConstants;
import com.youlai.common.core.result.Result;
import com.youlai.common.core.result.ResultCode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.nio.charset.Charset;
/**
* JWT转换成用户信息过滤器
* 黑名单token过滤器
*/
@Component
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {
@Autowired
private RedisTemplate redisTemplate;
@SneakyThrows
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
@ -31,9 +47,25 @@ public class AuthGlobalFilter implements GlobalFilter, Ordered {
token = token.replace(AuthConstants.JWT_TOKEN_PREFIX, Strings.EMPTY);
JWSObject jwsObject = JWSObject.parse(token);
String payload = jwsObject.getPayload().toString();
// 黑名单token(登出修改密码)校验
JSONObject jsonObject = JSONUtil.parseObj(payload);
String jti = jsonObject.getStr("jti"); // JWT唯一标识
Boolean isBlack = redisTemplate.hasKey(AuthConstants.TOKEN_BLACKLIST_PREFIX + jti);
if (isBlack) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.OK);
response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
response.getHeaders().set("Access-Control-Allow-Origin", "*");
response.getHeaders().set("Cache-Control", "no-cache");
String body = JSONUtil.toJsonStr(Result.custom(ResultCode.INVALID_TOKEN_OR_EXPIRED));
DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
return response.writeWith(Mono.just(buffer));
}
ServerHttpRequest request = exchange.getRequest().mutate()
.header(AuthConstants.USER_TOKEN_HEADER, payload)
.header(AuthConstants.JWT_TOKEN_HEADER,token)
.header(AuthConstants.JWT_PAYLOAD_KEY, payload)
.build();
exchange = exchange.mutate().request(request).build();
return chain.filter(exchange);

View File

@ -1,47 +0,0 @@
package com.youlai.gateway.filter;
import com.youlai.common.core.constant.AuthConstants;
import com.youlai.gateway.config.WhiteListConfig;
import lombok.AllArgsConstructor;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.List;
/**
* 白名单路径移除JWT请求头
*/
@Component
@AllArgsConstructor
public class WhiteListRemoveJwtFilter implements WebFilter {
private WhiteListConfig whiteListConfig;
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
URI uri = request.getURI();
PathMatcher pathMatcher = new AntPathMatcher();
//白名单路径移除JWT请求头
List<String> ignoreUrls = whiteListConfig.getUrls();
for (String ignoreUrl : ignoreUrls) {
if (pathMatcher.match(ignoreUrl, uri.getPath())) {
request = exchange.getRequest().mutate()
.header(AuthConstants.JWT_TOKEN_HEADER, Strings.EMPTY).build();
exchange = exchange.mutate().request(request).build();
return chain.filter(exchange);
}
}
return chain.filter(exchange);
}
}