springboot实现简单的鉴权校验
背景
最近公司的项目在跟第三方对接,第三方要通过消息平台服务来向我们的系统推送消息,最一开始是给了一套方案,但是他们说没有登录态,所以开发了一个不走系统集成的登录鉴权,但是还是需要一个简单的鉴权来进行控制和获取一些信息。因此记录一下
我的项目版本为:
需求
我这里在开发的时候使用的方案是请求接口时候传递appKey和appSecret,然后使用拦截器拦截,从数据库中获取对应appKey的信息来进行校验,同时添加了时间进行校验,防止重提交。
实现
以下是拦截器的实现
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
@Component
public class AuthInterceptor implements HandlerInterceptor{
// 此处可以换成从数据库中获取
private final Map<String, AuthConfig> apiKeys = new HashMap<>();
public AuthService() {
// 初始化一些测试用的API密钥
apiKeys.put("app001", new AuthConfig("app001", "secret123456", true, "aaa"));
apiKeys.put("app002", new AuthConfig("app002", "secret789012", true, "aaa"));
}
@Override
public boolean preHandle(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull Object handler) throws Exception {
// 获取请求头中的认证信息
String accessToken = request.getHeader("AccessToken");
String authorization = request.getHeader("Authorization");
// 验证必需的认证信息
if (accessToken == null || authorization == null) {
sendErrorResponse(response, 401, "缺少认证信息,需要AccessToken和Authorization");
return false;
}
// 解析Authorization获取appId和时间戳
AuthUtil.AuthInfo authInfo = AuthUtil.parseAuthorization(authorization);
String appKey = authInfo.getAppKey();
String timestamp = authInfo.getTimestamp();
// 在此处获取配置,是可以把一些额外的参数代到request中,方便接口获取,比如租户信息
// 此处可以换成从对应的服务中获取
AuthConfig config = apiKeys.get(appId);
// 验证认证信息
if (!validateAuth(accessToken, config, timestamp)) {
sendErrorResponse(response, 403, "认证失败");
return false;
}
// 认证成功,将appCode存入request属性中供后续使用
// 获取appId并存储到request属性中
request.setAttribute("tId", config.getTId());
return true;
}
// 返回错误的信息
private void sendErrorResponse(HttpServletResponse response, int status, String message) throws Exception {
response.setStatus(status);
response.setContentType("application/json;charset=UTF-8");
Map<String, Object> errorResponse = Maps.newHashMap();
errorResponse.put("code", status);
errorResponse.put("message", message);
errorResponse.put("timestamp", System.currentTimeMillis());
ObjectMapper objectMapper = new ObjectMapper();
response.getWriter().write(objectMapper.writeValueAsString(errorResponse));
}
}
这里简单给一下AuthConfig的结构
import com.alibaba.fastjson2.JSON;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class AuthConfig{
/**
* 第三方app_key
*/
private String appKey;
/**
* 第三方app_secret
*/
private String appSecret;
/**
* 有效标识
*/
private Boolean valid;
/**
* 额外参数
*/
private String aaa;
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
然后是验证的逻辑,这里我简单封了一个工具类,如下所示
public class AuthUtil {
private static final String TIMESTAMP_FORMAT = "yyyyMMddHHmmss";
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern(TIMESTAMP_FORMAT);
/**
* Base64解码
*/
public static String base64Decode(String encoded) {
try {
return Base64.decodeStr(encoded);
} catch (Exception e) {
throw new RuntimeException("Base64解码失败", e);
}
}
/**
* 生成AccessToken
* 使用 sha256 加密(appKey + appSecret + 时间戳)
*/
public static String generateAccessToken(String appKey, String appSecret, String timestamp) {
return SecureUtil.sha256(appKey + appSecret + timestamp);
}
/**
* 解析Authorization获取appKey和时间戳
*/
public static AuthInfo parseAuthorization(String authorization) {
try {
String decoded = base64Decode(authorization);
String[] parts = decoded.split(":");
if (parts.length != 2) {
throw new RuntimeException("Authorization格式错误");
}
return new AuthInfo(parts[0], parts[1]);
} catch (Exception e) {
throw new RuntimeException("解析Authorization失败", e);
}
}
/**
* 验证时间戳是否在有效期内(5分钟)
*/
public static boolean isTimestampValid(String timestamp) {
try {
LocalDateTime timestampDateTime = LocalDateTime.parse(timestamp, FORMATTER);
LocalDateTime currentDateTime = LocalDateTime.now();
// 计算时间差(秒)
long diffSeconds = Math.abs(java.time.Duration.between(timestampDateTime, currentDateTime).getSeconds());
// 5分钟内有效
return diffSeconds <= 300;
} catch (Exception e) {
return false;
}
}
/**
* 认证信息类
*/
@Getter
@AllArgsConstructor
public static class AuthInfo {
private String appKey;
private String timestamp;
}
}
然后就是实现校验的逻辑,此方法放置在拦截器中
@Component
public class AuthInterceptor implements HandlerInterceptor{
// 前面是刚才的拦截实现
private boolean validateAuth(String accessToken, MsgThirdPartyAuthConfigDTO config, String timestamp) {
try {
// 1. 验证时间戳有效性
if (!AuthUtil.isTimestampValid(timestamp)) {
return false;
}
// 2. 根据appId获取应用信息
if (config == null || !config.getValid()) {
return false;
}
// 3. 使用appId + appSecret + 时间戳生成期望的AccessToken
String expectedAccessToken = AuthUtil.generateAccessToken(config.getAppKey(), config.getAppSecret(), timestamp);
// 4. 比对AccessToken
return expectedAccessToken.equals(accessToken);
} catch (Exception e) {
return false;
}
}
}
此时就可以实现拦截和校验了
扩展
因为此鉴权是仅针对系统中单独的某些接口进行校验,所以还需要配置一下路由
@Configuration
public class AuthConfig implements WebMvcConfigurer {
@Resource
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
// 需要鉴权的路径
.addPathPatterns("xxxxx")
// 排除鉴权的路径
.excludePathPatterns("xxxxx");
}
}
后记
总归,核心的理念就是首先通过时间校验,然后从authorization中读取到key,再生成对应的token进行比对,以达成校验的目的
嘿嘿,又水一篇

浙公网安备 33010602011771号