SpringBoot使用Filter实现签名认证鉴权的示例代码

网友投稿 273 2023-01-19

SpringBoot使用Filter实现签名认证鉴权的示例代码

情景说明

鉴权,有很多方案,如:SpringSecurity、Shiro、拦截器、过滤器等等。如果只是对一些URL进行认证鉴权的话,我们完

全没必要引入SpringSecurity或Shiro等框架,使用拦截器或过滤器就足以实现需求。

        本文介绍如何使用过滤器Filter实现URL签名认证鉴权。

本人测试软硬件环境:Windows10、Eclipse、SpringBoot、JDK1.8

准备工作

第一步:在pom.xml中引入相关依赖

org.springframework.boot

spring-boot-starter

org.springframework.boot

spring-boot-starter-test

test

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-devtools

org.apache.directory.studio

org.apache.commons.codec

1.8

第二步:在系统配置文件application.properties中配置相关参数,一会儿代码中需要用到

# ip白名单(多个使用逗号分隔)

permitted-ips = 169.254.205.177, 169.254.133.33, 10.8.109.31, 0:0:0:0:0:0:0:1

# secret

secret = JustryDeng

第三步:准备获取客户端IP的工具类

import java.net.InetAddress;

import java.net.UnknownHostException;

import javax.servlet.http.HttpServletRequest;

/**

* 获取发出request请求的客户端ip

* 注:如果是自己发出的请求,那么获取的是自己的ip

* 摘录自https://blog.csdn.net/byy8023/article/details/80499038

*

* 注意事项:

* 如果使用此工具,获取到的不是客户端的ip地址;而是虚拟机的ip地址(d当客户端安装有VMware时,可能出现此情况);

* 那么需要在客户端的[控制面板\网络和 Internet\网络连接]中禁用虚拟机网络适配器

*

* @author JustryDeng

* @DATE 2018年9月10日 下午8:56:48

*/

public class IpUtil {

public static String getIpAddr(HttpServletRequest request) {

String ipAddress = null;

try {

ipAddress = request.getHeader("x-forwarded-for");

if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {

ipAddress = request.getHeader("Proxy-Client-IP");

}

if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {

ipAddress = request.getHeader("WL-Proxy-Client-IP");

}

if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {

ipAddress = request.getRemoteAddr();

if (ipAddress.equals("127.0.0.1")) {

// 根据网卡取本机配置的IP

InetAddress inet = null;

try {

inet = InetAddress.getLocalHost();

} catch (UnknownHostException e) {

e.printStackTrace();

}

ipAddress = inet.getHostAddress();

}

}

// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割

if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()

// = 15

if (ipAddress.indexOf(",") > 0) {

ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));

}

}

} catch (Exception e) {

ipAddress="";

}

return ipAddress;

}

}

第四步:准备MD5加密工具类

import java.io.UnsupportedEncodingException;

import java.security.MessageDigest;

import java.security.NoSuchAlgorithmException;

import org.apache.commons.codec.binary.Hex;

/**

* MD5加密工具类

*

* @author JustryDeng 参考自ShaoJJ的MD5加密工具类

* @DATE 2018年9月11日 下午2:14:21

*/

public class MDUtils {

/**

* 加密

*

* @param origin

* 要被加密的字符串

* @param charsetname

* 加密字符,如UTF-8

* @DATE 2018年9月11日 下午2:12:51

*/

public static String MD5EncodeForHex(String origin, String charsetname)

throws UnsupportedEncodingException, NoSuchAlgorithmException {

return MD5EncodeForHex(origin.getBytes(charsetname));

}

public static String MD5EncodeForHex(byte[] origin) throws NoSuchAlgorithmException {

return Hex.encodeHexString(digest("MD5", origin));

}

/**

* 指定加密算法

*

* @throws NoSuchAlgorithmException

* @DATE 2018年9月11日 下午2:11:58

*/

private static byte[] digest(String algorithm, byte[] source) throws NoSuchAlgorithmException {

MessageDigest md;

md = MessageDigest.getInstance(algorithm);

return md.digest(source);

}

}

第五步:简单编写一个Controller,方便后面的测试

SpringBoot使用Filter实现签名认证鉴权 --- 逻辑代码

第一步:编写过滤器

import java.io.BufferedReader;

import java.io.ByteArrayInputStream;

import java.io.IOException;

import java.io.InputStreamReader;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ReadListener;

import javax.servlet.ServletException;

import javax.servlet.ServletInputStream;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.annotation.WebFilter;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletRequestWrapper;

import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Value;

import com.aspire.util.IpUtil;

import com.aspire.util.MDUtils;

/**

* SpringBoot使用拦截器实现签名认证(鉴权)

* @WebFilter注解指定要被过滤的URL

* 一个URL会被多个过滤器过滤时,还可以使用@Order(x)来指定过滤request的先后顺序,x数字越小越先过滤

*

* @author JustryDeng

* @DATE 2018年9月11日 下午1:18:29

*/

@WebFilter(urlPatterns = { "/authen/test1", "/authen/test2", "/authen/test3"})

public class SignAutheFilter implements Filter {

private static Logger logger = LoggerFactory.getLogger(SignAutheFilter.class);

@Value("${permitted-ips}")

private String[] permittedIps;

@Value("${secret}")

private String secret;

@Override

public void init(FilterConfig filterConfig) throws ServletException {

}

@Override

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)

throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

try {

String authorization = request.getHeader("Authorization");

logger.info("getted Authorization is ---> " + authorization);

String[] info = authorization.split(",");

// 获取客户端ip

String ip = IpUtil.getIpAddr(request);

logger.info("getted ip is ---> " + ip);

/*

* 读取请求体中的数据(字符串形式)

* 注:由于同一个流不能读取多次;如果在这里读取了请求体中的数据,那么@RequestBody中就不能读取到了

* 会抛出异常并提示getReader() has already been called for this request

* 解决办法:先将读取出来的流数据存起来作为一个常量属性.然后每次读的时候,都需要先将这个属性值写入,再读出.

* 即每次获取的其实是不同的流,但是获取到的数据都是一样的.

* 这里我们借助HttpServletRequestWrapper类来实现

* 注:此方法涉及到流的读写、耗性能;

*/

MyRequestWrapper mrw = new MyRequestWrapper(request);

String bodyString = mrw.getBody();

logger.info("getted requestbody data is ---> " + bodyString);

// 获取几个相关的字符

// 由于authorization类似于

// cardid="1234554321",timestamp="9897969594",signature="a69eae32a0ec746d5f6bf9bf9771ae36"

// 这样的,所以逻辑是下面这样的

int cardidIndex = info[0].indexOf("=") + 2;

String cardid = info[0].substring(cardidIndex, info[0].length() - 1);

logger.info("cardid is ---> " + cardid);

int timestampIndex = info[1].indexOf("=") + 2;

String timestamp = info[1].substring(timestampIndex, info[1].length() - 1);

int signatureIndex = info[2].indexOf("=") + 2;

String signature = info[2].substring(signatureIndex, info[2].length() - 1);

String tmptString = MDUtils.MD5EncodeForHex(timestamp + secret + bodyString, "UTF-8")

.toUpperCase();

logger.info("getted ciphertext is ---> {}, correct ciphertext is ---> {}",

signature , tmptString);

// 判断该ip是否合法

boolean containIp = false;

for (String string : permittedIps) {

if (string.equals(ip)) {

containIp = true;

break;

}

}

// 再判断Authorization内容是否正确,进而判断是否最终放行

boolean couldPass = containIp && tmptString.equals(signature);

if (couldPass) {

// 放行

chain.doFilter(mrw, response);

return;

}

response.sendError(403, "Forbidden");

} catch (Exception e) {

logger.error("AxbAuthenticationFilter -> " + e.getMessage(), e);

response.sendError(403, "Forbidden");

}

}

@Override

public void destroy() {

}

}

/**

* 辅助类 ---> 变相使得可以多次通过(不同)流读取相同数据

*

* @author JustryDeng

* @DATE 2018年9月11日 下午7:13:52

*/

class MyRequestWrapper extends HttpServletRequestWrapper {

private final String body;

public String getBody() {

return body;

}

public MyRequestWrapper(final HttpServletRequest request) throws IOException {

super(request);

StringBuilder sb = new StringBuilder();

String line;

BufferedReader reader = request.getReader();

while ((line = reader.readLine()) != null) {

sb.append(line);

}

body = sb.toString();

}

@Override

public ServletInputStream getInputStream() throws IOException {

final ByteArrayInputStream bais = new ByteArrayInputStream(body.getBytes());

return new ServletInputStream() {

/*

* 重写ServletInputStream的父类InputStream的方法

*/

@Override

public int read() throws IOException {

return bais.read();

}

@Override

public boolean isFinished() {

return false;

}

@Override

public boolean isReady() {

return false;

}

@Override

public void setReadListener(ReadListener listener) {

}

};

}

@Override

public BufferedReader getReader() throws IOException {

return new BufferedReader(new InputStreamReader(this.getInputStream()));

}

}

第二步:在项目的启动类上添加@ServletComponentScan注解,使允许扫描

ServletnmbtDYM组件(过滤器、监听器等)。

测试一下

测试说明

客户端ip在我们设置的ip白名单里面 且 timestamp + secret + bodyStringMD5加密后的字段与请求头域中传过来的signature值相同时,才算鉴权通过。

说明:

1.ip白名单 本示例中是设置在服务端的相应服务的系统配置文件application.properties中的。

        2.secret 是客户端一方和服务端一方 定好的一个用来MD5加密的   数,secret本身不进行传输。

        3.bodyString是服务端通过客户端的request获取到的请求体中的数据。

        4.signature是客户端加密后的值,服务端只需对原始数据进行和客户端进一模一样的加密,

           将加密结果和传导服务端的signature进行比对,一样则鉴权通过。

启动项目,使用postman测试一下

给出程序打印的日志,更容易理解

提示:由于本人测试时,我的电脑既是服务器又是客户端,所以获取到了那样的ip。

注:当ip或Authorization值中任意一个或两个 不满足条件时,会返回给前端403(见:SignAutheFilter中的相关代码),

     这里就不给出效果图了。

由测试结果可知:签名鉴权成功!

测试项目代码托管链接: https://github.com/JustryDeng/PublicRepository ​

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SpringBoot自定义注解API数据加密和签名校验
下一篇:华为云开放api接口(华为云api网关)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~