springboot使⽤SHA256加密算法对接⼝访问控制
使⽤场景
由于业务系统较多,且存在很多个提供给第三⽅系统的查询接⼝,第三⽅系统属于外部系统,个别系统在调⽤内部系统接⼝时可能存在安全风险,在周五项⽬组评审后决定,在对外提供的接⼝中,请求⽅需要添加私钥请求校验,我⽅使⽤ SHA256 算法计算签名,然后进⾏Base64 encode,最后再进⾏urlEncode,来得到最终的签名。周末闲来⽆事,简单研究⼀波(基于内⽹中其他系统已有的类似功能,结合外⽹资料)。
学习使⽤
springboot结构创建⼀个简单的springboot项⽬,⽬录结构如下:
由于并不是对所有接⼝进⾏过滤验证,所以决定添加⾃定义注解⽤来判定是否需要鉴权:
@Documented
@Inherited
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
boolean validate()default true;//是否需要鉴权
}
需要创建⼀个controller,假设是对外提供的接⼝服务:
@Controller
public class TestController {
@Auth
@GetMapping("/auth")
private String myTest(){
System.out.println("");
return"ok";
}
}
因为需要对接⼝进⾏过滤拦截,所以需要创建,通过继承HandlerInterceptorAdapter(待深⼊学习)实现的过滤拦截:
public class AuthSercurityInterceptor extends HandlerInterceptorAdapter {
@Value("${auth.secret.info:12345}")
private String secret;
@Value("${d:true}")
private Boolean needAuth;
//HEADER Authorization
private static final String INFO_TIME ="timestamp";
private static final String INFO_SIGN ="sign";
private static final String AUTH_HEADER ="Authorization";
public String getSecret(){
return secret;
}
public void setSecret(String secret){
this.secret = secret;
}
public Boolean getNeedAuth(){
return needAuth;
}
public void setNeedAuth(Boolean needAuth){
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {
if(needAuth==null||!needAuth){
return true;
}
if(!Class().isAssignableFrom(HandlerMethod.class)){
return true;
}
Auth auth =((HandlerMethod)handler).getMethodAnnotation(Auth.class);
if(auth==null||!auth.validate()){
return true;
}
String authorization = Header(AUTH_HEADER);
System.out.println("authorization is :"+ authorization);
String[] info = im().split(",");
if(info==null||info.length<1){
throw new Exception("error .....");
}
String timestamp = null;
String sign = null;
for(int i =0; i < info.length; i++){
String str = info[i].trim();
if(StringUtils.isEmpty(str)){
continue;
}
String[] strSplit = str.split("=");
if(strSplit==null||strSplit.length!=2){
continue;
}
String key = strSplit[0];
String value = strSplit[1];
if(INFO_TIME.equalsIgnoreCase(key)){
timestamp = value;
System.out.println("timestamp is :"+ timestamp);
}
if(INFO_SIGN.equalsIgnoreCase(key)){
sign = value;
System.out.println("sign is :"+ sign);
}
}
if(StringUtils.isEmpty(timestamp)||StringUtils.isEmpty(sign)){
throw new Exception("error timestamp or sign is null");
throw new Exception("error timestamp or sign is null");
}
String sha256Str = SHA256Str(secret,timestamp);
System.out.println("sha256str is :"+ sha256Str);
if(StringUtils.isEmpty(sha256Str)){
throw new Exception("sha256Str is null ...");
}
if(!sha256Str.equals(sign)){
throw new Exception("");
}
return super.preHandle(request,response,handler);
}
}
因为新增了⾃定义,所以需要将⾃定义进⾏配置:
AuthConfig.java
@Configuration
public class AuthConfig extends WebMvcConfigurationSupport {
@Bean//⾃定义的AuthSercurityInterceptor
public AuthSercurityInterceptor authSercurityInterceptor(){
return new AuthSercurityInterceptor();
}
@Override//进⾏注册添加AuthSercurityInterceptor
protected void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(authSercurityInterceptor()).addPathPatterns("/**");
super.addInterceptors(registry);
}
}
SHA256 算法计算签名,然后进⾏Base64 encode,最后再进⾏urlEncode,来得到最终的签名:
public class SHAUtils {
public static final String ENCODE_TYPE_HMAC_SHA_256 ="HmacSHA256";
public static final String ENCODE_UTF_8_LOWER ="utf-8";
public static final String ENCODE_UTF_8_UPPER ="UTF-8";
public static String getSHA256Str(String secret,String message)throws Exception {
if(StringUtils.isEmpty(secret)){
return null;
}
String encodeStr;
try{
//HMAC_SHA256 加密
Mac HMAC_SHA256 = Instance(ENCODE_TYPE_HMAC_SHA_256);
SecretKeySpec secre_spec =new Bytes(ENCODE_UTF_8_UPPER),ENCODE_TYPE_HMAC_SHA_256);            HMAC_SHA256.init(secre_spec);
byte[] bytes = HMAC_SHA256.Bytes(ENCODE_UTF_8_UPPER));
if(bytes==null&&bytes.length<1){
return null;
}
//字节转换为16进制字符串
String SHA256 =byteToHex(bytes);
if(StringUtils.isEmpty(SHA256)){
return null;
}
//base64
String BASE64 = Encoder().Bytes(ENCODE_UTF_8_UPPER));
if(StringUtils.isEmpty(BASE64)){
return null;
}
//url encode
encodeStr = de(BASE64,ENCODE_UTF_8_LOWER);
}catch(Exception e){
throw new Exception("get 256 info error ....");
}
return encodeStr;
}
private static String byteToHex(byte[] bytes){
if(bytes==null){
return null;
}
StringBuffer stringBuffer =new StringBuffer();
String temp=null;
for(int i =0; i <bytes.length ; i++){
temp = HexString(bytes[i]&0xff);
if(temp.length()==1){
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
String();
}
}
运⾏程序,在d:true设置为false的时候,接⼝正常访问和返回,
改为true后,就请求接⼝的时候就要传递Authorization在请求头中,要包含timestamp和sign,否则会报空指针异常,假如对接系统⽅⽬前还不知道具体怎样传参数才能请求到接⼝,随意在请求头的Authorization加上例如timestamp=1590829472,sign=/auth后去请求,因为秘钥不对,所以⽆法请求到接⼝。
authorization is :timestamp=1590829472,sign=/auth
timestamp is :1590829472
sign is :/auth
2020-05-3017:56:05.632 ERROR 13616---[nio-8080-exec-2] C.[.[.[/].[dispatcherServlet]: Servlet.service()for servlet [dispatcherServlet] in conte xt with path [] threw exception [Request processing failed; nested exception is java.lang.Exception: ] with root cause
java.lang.Exception:
将最终的sha256str作为签名信息交给经过授权的对接第三⽅,让他们在请求头Authorization,将新提供的信息添加上
timestamp=1590829472,sign=ZGQwNTY0MDNiYzM3OWI5ZmEyMGY2ZDU0ZTk0NzBhMzc5ODgyZDY4OWM2YWJmNzYy NzM2YmZlMzY0ZjMyYmE2Mw%3D%3D,即可成功请求到接⼝信息。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。