API接口鉴权

2 篇文章 0 订阅
1 篇文章 0 订阅

接口鉴权

由于api接口直接暴露在服务器上容易被恶意攻击,所有使用接口鉴权来拦截恶意请求
使用时间戳+appId+secret+参数排序生成MD5加密传输.被调用api接口使用AOP切面添加注解鉴权,

MD5是一种不可逆的加密算法‌,这意味着一旦数据被MD5加密,就无法直接从加密后的结果还原出原始数据。尽管MD5加密本身是不可逆的,但它可以通过一些方法来实现特定的应用,如数据完整性校验和密码存储等。
 ‌1.数据完整性校验‌:通过将消息体排序、拼接后进行MD5加密,生成一个签名。发送方将原始消息体和签名一起发送给接收方。接收方收到消息后,用相同的方法计算签名,并与接收到的签名进行比较。如果两者匹配,说明消息在传输过程中没有被篡改,从而保证了数据的完整性‌
 ‌2.密码存储‌:虽然MD5不可逆,但它仍然被用于密码存储,尤其是在早期系统中。这是因为MD5加密速度快,适合大量数据的加密处理。然而,由于MD5的安全性较低,现代系统更倾向于使用更安全的哈希算法(如SHA-256)来存储密码哈希值‌2。
尽管MD5有上述应用,但需要注意的是,由于MD5算法的设计特性,它存在固有的安全缺陷,如哈希冲突、压缩性和预计算表等问题,这些都可能影响到其安全性。因此,对于需要高安全性的应用场景,建议使用更强大的加密算法来保护数据安全‌
此外,虽然存在一些尝试性的破解方法(如字典攻击、彩虹表攻击和穷举攻击),但这些方法的有效性取决于攻击者的资源和计算能力。因此,对于关键的应用和数据,应考虑使用更安全的加密算法来保护数据安全‌

一.调用接口方法

1.调用接口方法类-添加接口鉴权参数

public class AliHuiFeiRefundOrder {
	private final static String DETAIL_URL = "http://192.168.0.66:12886/order/list";
	public static void demo(){
		 Map<String, String> requestParams = getRequestParams("/order/list");
         requestParams.put("startTime",startTime);
         requestParams.put("endTime",endTime);
         requestParams.put("siteName","2342as");
         requestParams.put("status", "3");
         String token = createToken(requestParams);
         requestParams.put("token",token);
         HttpResponse response = HttpRequest.get(DETAIL_URL)
                 .formStr(requestParams)
                 .execute();
         if (HttpStatus.SUCCESS == response.getStatus()) {
             String result = response.body();
             log.info("拉取结果[{}--{}]==>{}",startTime,endTime,result);
         }
	}
}

2.封装接口鉴权公共请求参数ApiTokenReqParamUtil类

package com.bgn.common.util;

import com.bgn.common.core.utils.uuid.UUID;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author: lxq
 * @Date: 2024/06/24/15:17
 * @Description: 封装接口鉴权公共请求参数
 * 生成 appId + secret
 */

public class ApiTokenReqParamUtil {
	//生成 app_secret 密钥
	private final static String SERVER_NAME = "demo_api_123456";
	private final static String[] chars = new String[]{"a", "b", "c", "d", "e", "f",
			"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
			"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
			"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
			"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
			"W", "X", "Y", "Z"};

	public static Map<String, String> getRequestParams(String baseUrl) {
		Map<String, String> params = new HashMap<>(1 << 2);
		String appId = "7A***Nkp";
		String secret = "73****************************55c4";
		long currentTime = System.currentTimeMillis();
		params.put("appId",appId);
		params.put("baseUrl",baseUrl);
		params.put("createTime", String.valueOf(currentTime));
		params.put("secret",secret);
		return params;
	}

	public static void main(String[] args) {
		String appId = getAppId();
		String appSecret = getAppSecret(appId);
		System.out.println("appId: "+appId);
		System.out.println("appSecret: "+appSecret);
	}

	/**
	 * 短8位UUID思想其实借鉴微博短域名的生成方式,但是其重复概率过高,而且每次生成4个,需要随即选取一个。
	 * 本算法利用62个可打印字符,通过随机生成32位UUID,由于UUID都为十六进制,所以将UUID分成8组,每4个为一组,然后通过模62操作,结果作为索引取出字符,
	 * 这样重复率大大降低。
	 * 经测试,在生成一千万个数据也没有出现重复,完全满足大部分需求。
	 * @return
	 */
	public static String getAppId() {
		StringBuffer shortBuffer = new StringBuffer();
		String uuid = UUID.randomUUID().toString().replace("-", "");
		for (int i = 0; i < 8; i++) {
			String str = uuid.substring(i * 4, i * 4 + 4);
			int x = Integer.parseInt(str, 16);
			shortBuffer.append(chars[x % 0x3E]);
		}
		return shortBuffer.toString();

	}

	/**
	 * 通过appId和内置关键词生成APP Secret
	 * @param appId
	 * @return
	 */
	public static String getAppSecret(String appId) {
		try {
			String[] array = new String[]{appId, SERVER_NAME};
			StringBuffer sb = new StringBuffer();
			// 字符串排序
			Arrays.sort(array);
			for (int i = 0; i < array.length; i++) {
				sb.append(array[i]);
			}
			String str = sb.toString();
			MessageDigest md = MessageDigest.getInstance("SHA-1");
			md.update(str.getBytes());
			byte[] digest = md.digest();

			StringBuffer hexstr = new StringBuffer();
			String shaHex = "";
			for (int i = 0; i < digest.length; i++) {
				shaHex = Integer.toHexString(digest[i] & 0xFF);
				if (shaHex.length() < 2) {
					hexstr.append(0);
				}
				hexstr.append(shaHex);
			}
			return hexstr.toString();
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
			throw new RuntimeException();
		}
	}
}

4.接口api鉴权-公共token工具类

package com.bgn.common.core.utils.sign;

import cn.hutool.core.map.MapUtil;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.Digester;

import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * @Author: lxq
 * @Date: 2024/06/24/14:02
 * @Description: 接口api鉴权-公共token工具类
 */

public class ApiTokenUtils {
	private static final Digester MD5 = new Digester(DigestAlgorithm.MD5);

	private ApiTokenUtils() {
		throw new IllegalStateException("Utility class");
	}

	public static String createToken(Map<String, String> params) {
		String appId = params.get("appId");
		String secret = params.get("secret");
		String createTime = params.get("createTime");
		String baseUrl = params.get("baseUrl");
		String original = appId + secret + baseUrl + MapUtil.sortJoin(params, "&", "=", true) + createTime;
		return MD5.digestHex(original, StandardCharsets.UTF_8);
	}
}

二.添加api接口鉴权-使用AOP添加一个注解,便于操作

1.用于接口api鉴权AuthenticationAop

package com.bgn.fliggy.aop;
import com.bgn.common.core.exception.GlobalException;
import com.bgn.fliggy.utils.AuthToken;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import com.bgn.common.core.utils.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
 * @Author: lxq
 * @Date: 2024/06/24/11:52
 * @Description: 用于接口api鉴权
 */
@Slf4j
@Aspect
@Component
public class AuthenticationAop {

	@Value("${apiAuthToken.secret}")
	public String secret;

	@Pointcut("@annotation(com.bgn.fliggy.aop.Authentication)")
	public void pointCutMethodBefore() {
		throw new UnsupportedOperationException();
	}

	@Before("pointCutMethodBefore()")
	public void doBefore(JoinPoint point) {
		MethodInvocationProceedingJoinPoint mjp = (MethodInvocationProceedingJoinPoint) point;
		MethodSignature signature = (MethodSignature) mjp.getSignature();
		Method method = signature.getMethod();
		Authentication annotation = method.getAnnotation(Authentication.class);
		if (annotation != null) {
			HttpServletRequest request = getRequest(point.getArgs());
			if (request == null) {
				throw new GlobalException("【鉴权失败】,获取HttpServletRequest");
			}
			String appId = request.getParameter("appId");
			if (StringUtils.isEmpty(appId)) {
				throw new GlobalException("【鉴权失败】,appId不存在");
			}
			final String token = request.getParameter("token");
			String createTime = request.getParameter("createTime");
			if (StringUtils.isEmpty(createTime)){
				throw new GlobalException("【鉴权失败】,createTime不存在");
			}
			Map<String, String> params = new HashMap<>(1 << 2);
			final String baseUrl = request.getRequestURI();
			request.getParameterMap().forEach((k, v) -> params.put(k, v[0]));
			params.remove("token");
			params.put("secret", secret);
			AuthToken authToken = AuthToken.createToken(appId, secret, Long.parseLong(createTime), baseUrl, params);
			log.info(authToken.getToken());
			if (!authToken.isExpired()) {
				throw new GlobalException("【鉴权失败】,token已过期");
			}
			if (!ObjectUtils.nullSafeEquals(token, authToken.getToken())) {
				throw new GlobalException("【鉴权失败】,token错误");
			}
		}
	}
	private HttpServletRequest getRequest(Object[] args) {
		for (Object o : args) {
			if (o instanceof HttpServletRequest) {
				return (HttpServletRequest) o;
			}
		}
		return null;
	}
}

2.用于api接口鉴权

package com.bgn.fliggy.aop;

import java.lang.annotation.*;
/**
 * @Author: lxq
 * @Date: 2024/06/24/11:51
 * @Description: 用于api接口鉴权
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface Authentication {
}

3. 获取参数生成token工具类

package com.bgn.fliggy.utils;


import cn.hutool.core.map.MapUtil;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.Digester;
import lombok.Getter;

import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * @Author: lxq
 * @Date: 2024/06/24/11:38
 * @Description: 获取参数生成token
 */

public class AuthToken {
	// 默认超时时间 3 分钟
	private static final long DEFAULT_EXPIRE_TIME_INTERVAL = 3*60 * 1000;
	@Getter
	private String token;
	private long createTime;
	@Getter
	private long expiredTimeInterval = DEFAULT_EXPIRE_TIME_INTERVAL;
	private static final Digester MD5 = new Digester(DigestAlgorithm.MD5);
	public AuthToken(String token, long createTime) {
		this.token = token;
		this.createTime = createTime;
	}
	public AuthToken(String token, long createTime, long expiredTimeInterval) {
		this.token = token;
		this.createTime = createTime;
		this.expiredTimeInterval = expiredTimeInterval;
	}
	public static AuthToken createToken(String appId, String secret, long createTime, String baseUrl, Map<String, String> params) {
		String original = appId + secret + baseUrl + MapUtil.sortJoin(params, "&", "=", true) + createTime;
		String token = MD5.digestHex(original, StandardCharsets.UTF_8);
		System.out.println(new AuthToken(token, createTime));
		return new AuthToken(token, createTime);
	}
	public boolean isExpired() {
		return System.currentTimeMillis() < this.createTime + DEFAULT_EXPIRE_TIME_INTERVAL;
	}
}

4.yml文件

server:
  port: 12886
  application:
    name: bgn-demo-api
  main:
    allow-bean-definition-overriding: true
#接口鉴权密文
apiAuthToken:
  secret: 73****************************55c4

5.使用的注解接口鉴权

@Slf4j
@RestController
@RequestMapping("/order")
public class DemoApiController {

    @Resource
    private DemoApiService demoApiService;
    /**
     * 查询出票订单列表
     * @return
     */
    @GetMapping(value = "/list")
    @ApiOperation("出票订单列表接口")
    @Authentication
    public AjaxResult ticketingList(@RequestParam("siteName") String siteName,
                                    @RequestParam("status") String status,
                                    @RequestParam("startTime") String startTime,
                                    @RequestParam("endTime") String endTime,
                                    HttpServletRequest request)
    {
        return demoApiService.list(siteName, status, startTime, endTime);
    }
}
在Spring中实现API接口鉴权,一般可以通过以下步骤实现: 1. 定义一个拦截器,用于在请求到达Controller前进行鉴权。 2. 在拦截器中获取请求头中的认证信息,如Token或API Key等。 3. 在拦截器中进行认证,比如检查Token是否有效、API Key是否正确等。 4. 如果认证失败,拦截器可以直接返回错误信息;如果认证成功,可以放行请求,让请求继续到达Controller。 5. 在Controller中,可以通过注解方式限制只有通过鉴权的请求才能访问某些接口。 下面是一个简单的示例代码: ```java @Component public class AuthInterceptor implements HandlerInterceptor { @Autowired private AuthService authService; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String authToken = authHeader.substring(7); if (authService.validateToken(authToken)) { return true; } } response.setStatus(HttpStatus.UNAUTHORIZED.value()); return false; } } @RestController @RequestMapping("/api") public class MyApiController { @GetMapping("/user") @PreAuthorize("hasAuthority('USER')") public ResponseEntity<User> getUser() { // implement the logic to retrieve user information } @GetMapping("/admin") @PreAuthorize("hasAuthority('ADMIN')") public ResponseEntity<Admin> getAdmin() { // implement the logic to retrieve admin information } } ``` 在上面的示例中,我们定义了一个拦截器`AuthInterceptor`,用于在请求到达Controller前进行鉴权。在拦截器中,我们获取了请求头中的Authorization信息,然后调用`authService.validateToken`方法进行验证。如果验证成功,就放行请求;如果验证失败,就返回401错误。 在Controller中,我们通过注解方式限制了只有具有`USER`或`ADMIN`权限的请求才能访问`/api/user`和`/api/admin`接口。这些权限可以通过Spring Security进行配置。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值