在一个安卓App项目中访问java后台接口,对于请求接口安全性跟用户登录状态验证.接口安全性采用签名的方式用户登录状态采用token的方式具体实现思路如下(小小菜鸟欢迎指正)
1.java后台安全性配置:secret(密钥),appId(客户端标识),signParam(签名参数account_token),timeOut(超时时间)
2.设置拦截器,拦截器主要定义五个功能
- 作用一:设置字符编码(所用请求均为post请求)
- 作用二:分析url,url中包含"api"的请求参数中必须带有appid,account_token否则接口调用是吧
- 作用三:签名验证,签名主要是由appid,secret,versionCode 经md5加密生成的一个字符串
- 作用四:根据时间戳判断接口失效时间,在后台定义一个失效时间,客户端请求时带有时间戳
- 作用五:包含"user"字符串的url需要验证用户的登录状态,并重置redis用户:account_token状态
- 作用六:验证客户端版本提示客户端升级
签名比对的方法:客户端跟服务端都用同一个密钥,客户端请求后台接口时会根据account_toke+appId+版本号+密钥MD5加密一个签名字符串,后台按照同样的方法生成一个加密字符串,两个签名字符串进行比对相等才可以调用接口
3.用户登录功能:在用户登录成功后,用uuid随机生成一个字符串,userId,account_token相互对应设置时长,存入到redis中,并返回给客户端用户登录状态都要根据这account_token进行判断
总结:客户端请求必须携带的参数:appid,account_token,sign,timestamp
拦截器代码:
public class ApiSignVerityInterceptor implements HandlerInterceptor {
private String secret;//密钥
private String appid;
private String sginParam;//account_token
private String appidParam;
private int timeOut;//超时时间
private RedisUtil redis;
public ApiSignVerityInterceptor(String secret, String appid,
String sginParam, String appidParam, int timeOut, RedisUtil redis) {
super();
this.secret = secret;
this.appid = appid;
this.sginParam = sginParam;
this.appidParam = appidParam;
this.timeOut = timeOut;
this.redis = redis;
}
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler) throws Exception {
request.setCharacterEncoding("UTF-8");
String url = request.getRequestURI();
String contextPath = request.getContextPath();
url = url.replace(contextPath, "");
//包含api的请求必须带有sginParam,appidParam,timestamp
if (url.contains("/api")) {
MessageResult result = new MessageResult();
String sgin = request.getParameter(sginParam);
String appidStr = request.getParameter(appidParam);
String timestamp = request.getParameter("timestamp");
if (!appid.equals(appidStr)) {
result.setErrorMsg("规则请求,接口结构不完整");
ResourceUtil.writeJSON(result, response);
return false;
}
//验证签名
String urlApiSgin = ResourceUtil.getUrlApiSgin(request, sginParam, appidParam, secret);
if (!urlApiSgin.equals(sgin)) {
result.setErrorMsg("规则请求,接口签名错误");
ResourceUtil.writeJSON(result, response);
return false;
}
//验证接口时效
if ("".equals(timestamp) || timestamp == null) {
timestamp = String.valueOf(System.currentTimeMillis() + timeOut
* 2 * 60 * 1000);
}
long nowTime = System.currentTimeMillis();
long apiTime = new Long(timestamp) + timeOut * 60 * 1000;
if (nowTime > apiTime) {
result.setErrorMsg("规则请求,接口无效");
ResourceUtil.writeJSON(result, response);
return false;
}
//验证版本
String reqVersionCode = request.getHeader(ResourceUtil.VERSION);
Object version = redis.get(ResourceUtil.VERSION); //value: { versionCode: "1", title: "ios13", detail: "1\1\2\34\4", url: 'http://xxxxx/xxxx/xxx.apk'}
if(version != null){
JSONObject versionJSON = (JSONObject)version;
Integer versionCode = versionJSON.getInteger("versionCode");
if (versionCode != null) {
if (versionCode > Integer.parseInt(reqVersionCode)) {
result.setErrorMsg("APP版本需升级");
result.setVersion(false);
result.setVersionDetail(versionJSON);
ResourceUtil.writeJSON(result, response);
return false;
}
}
}
//用户登录状态
if (url.contains("/user")) {
String account_token = request.getHeader(ResourceUtil.ACCOUNT_TOKEN);
boolean hasKey = redis.hasKey(account_token);
if (!hasKey) {
result.setLogin(false);
result.setErrorMsg("用户登录失效,请重新登录");
ResourceUtil.writeJSON(result, response);
return false;
}
redis.expire(account_token, ResourceUtil.EXPIRES_IN);
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}
}
生成签名代码:
public class ResourceUtil {
public static final String ACCOUNT_TOKEN = "account_token";
public static final String VERSION = "version";
public static final Integer EXPIRES_IN = 60 * 60 * 24 * 7;
public static final String PERMIT = "company/licens/";
public static final String DETIL_IMAGE = "commodity/detail/";
public static final String COVER = "commodity/cover/";
public static final String DEMAND_IMAGE = "demand/detail/";
public static final String DEMAND_COVER = "demand/cover/";
public static final String USERFACE = "face/user";
public static final String USER_COVER = "user/cover/";
public static final String USER_TEMPLET = "user/templet/";
public static void writeJSON(Object result, HttpServletResponse response)
throws IOException {
String value = JSON.toJSONString(result,
SerializerFeature.BrowserCompatible,
SerializerFeature.WriteClassName,
SerializerFeature.DisableCircularReferenceDetect);
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
response.getWriter().print(value);
}
public static String getUrlApiSgin(HttpServletRequest request,String sginParam, String appidParam, String secret) {
String sgin = null;
Enumeration<String> parameterNames = request.getParameterNames();
Map<String, String> values = new Hashtable<String, String>();
while (parameterNames.hasMoreElements()) {
String paramName = (String) parameterNames.nextElement();
if (!sginParam.equals(paramName) && !appidParam.equals(paramName)
&& !ACCOUNT_TOKEN.equals(paramName)) {
String value = request.getParameter(paramName);
values.put(paramName, value);
}
}
if (values.size() > 0) {
List<Map.Entry<String, String>> list = new ArrayList<Map.Entry<String, String>>(
values.entrySet());
Collections.sort(list, new Comparator<Map.Entry<String, String>>() {
@Override
public int compare(Entry<String, String> o1,
Entry<String, String> o2) {
return (o1.getKey().compareTo(o2.getKey()));
}
});
String sginStr = secret;
for (Entry<String, String> entry : list) {
sginStr += entry.getKey() + entry.getValue();
}
sginStr += ResourceUtil.VERSION + request.getHeader(ResourceUtil.VERSION).toString();
sgin = MD5Util.MD5(sginStr).toUpperCase();
}
return sgin;
}
}
登录功能代码:
if (login != null) {
int status = Integer.parseInt(login.get("status")
.toString());
if (status != -1) {
String user_id = login.get("user_id").toString();
Object token_exists = redis.get(user_id);
if (token_exists != null) {
redis.del(token_exists.toString());
}
String account_token = UUIDUtils.getEncryUUID();
TokenModel model = new TokenModel(user_id, account_token,
ResourceUtil.EXPIRES_IN);
redis.set(account_token, getContext(model),
ResourceUtil.EXPIRES_IN);
redis.set(user_id,
account_token, ResourceUtil.EXPIRES_IN);
result.setModel(model);
result.getResult().put("account", login.get("account").toString());
result.commit();
} else {
result.setErrorMsg("账号违规操作,已被禁用!");
}
}
拦截器配置:
@Configuration
public class WebInitConfig extends WebMvcConfigurerAdapter {
@Value("${frms.zbpls.api.secret}")
private String secret;
@Value("${frms.zbpls.api.appid:123456}")
private String appid;
@Value("${frms.zbpls.api.sginParam:sgin}")
private String sginParam;
@Value("${frms.zbpls.api.appidParam:appid}")
private String appidParam;
@Value("${frms.zbpls.api.timeOut: 1}")
private int timeOut;
@Autowired
@Qualifier("systemRedis")
private RedisTemplate<String, Object> redisSystemTemplate;
@Override
public void addInterceptors(InterceptorRegistry registry) {
/* RedisUtil redis = new RedisUtil(redisSystemTemplate);
registry.addInterceptor(new ApiSignVerityInterceptor(secret, appid,
sginParam, appidParam, timeOut, redis)).addPathPatterns("/api/**");
super.addInterceptors(registry);*/
}
}