Java 接口校验 浅谈Java 三种方式实现接口校验
BarryW 人气:0本文介绍了Java 三种方式实现接口校验,主要包括AOP,MVC拦截器,分享给大家,具体如下:
方法一:AOP
代码如下定义一个权限注解
package com.thinkgem.jeesite.common.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 权限注解 * Created by Hamming on 2016/12/ */ @Target(ElementType.METHOD)//这个注解是应用在方法上 @Retention(RetentionPolicy.RUNTIME) public @interface AccessToken { /* String userId(); String token();*/ }
获取页面请求中的ID token
@Aspect @Component public class AccessTokenAspect { @Autowired private ApiService apiService; @Around("@annotation(com.thinkgem.jeesite.common.annotation.AccessToken)") public Object doAccessCheck(ProceedingJoinPoint pjp) throws Throwable{ HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); String id = request.getParameter("id"); String token = request.getParameter("token"); boolean verify = apiService.verifyToken(id,token); if(verify){ Object object = pjp.proceed(); //执行连接点方法 //获取执行方法的参数 return object; }else { return ResultApp.error(3,"token失效"); } } }
token验证类 存储用到redis
package com.thinkgem.jeesite.common.service; import com.thinkgem.jeesite.common.utils.JedisUtils; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.impl.crypto.MacProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import redis.clients.jedis.Jedis; import java.io.*; import java.security.Key; import java.util.Date; /** *token登陆验证 * Created by Hamming on 2016/12/ */ @Service public class ApiService { private static final String at="accessToken"; public static Key key; // private Logger logger = LoggerFactorygetLogger(getClass()); /** * 生成token * Key以字节流形式存入redis * * @param date 失效时间 * @param appId AppId * @return */ public String generateToken(Date date, String appId){ Jedis jedis = null; try { jedis = JedisUtils.getResource(); byte[] buf = jedis.get("api:key".getBytes()); if (buf == null) { // 建新的key key = MacProvider.generateKey(); ByteArrayOutputStream bao = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bao); oos.writeObject(key); buf = bao.toByteArray(); jedis.set("api:key".getBytes(), buf); } else { // 重用老key key = (Key) new ObjectInputStream(new ByteArrayInputStream(buf)).readObject(); } }catch (IOException io){ // System.out.println(io); }catch (ClassNotFoundException c){ // System.out.println(c); }catch (Exception e) { // logger.error("ApiService", "generateToken", key, e); } finally { JedisUtils.returnResource(jedis); } String token = Jwts.builder() .setSubject(appId) .signWith(SignatureAlgorithm.HS512, key) .setExpiration(date) .compact(); // 计算失效秒,7889400秒三个月 Date temp = new Date(); long interval = (date.getTime() - temp.getTime())/1000; JedisUtils.set(at+appId ,token,(int)interval); return token; } /** * 验证token * @param appId AppId * @param token token * @return */ public boolean verifyToken(String appId, String token) { if( appId == null|| token == null){ return false; } Jedis jedis = null; try { jedis = JedisUtils.getResource(); if (key == null) { byte[] buf = jedis.get("api:key".getBytes()); if(buf==null){ return false; } key = (Key) new ObjectInputStream(new ByteArrayInputStream(buf))readObject(); } Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody().getSubject().equals(appId); return true; } catch (Exception e) { // logger.error("ApiService", "verifyToken", key, e); return false; } finally { JedisUtils.returnResource(jedis); } } /** * 获取token * @param appId * @return */ public String getToken(String appId) { Jedis jedis = null; try { jedis = JedisUtils.getResource(); return jedis.get(at+appId); } catch (Exception e) { // logger.error("ApiService", "getToken", e); return ""; } finally { JedisUtils.returnResource(jedis); } } }
spring aop配置
<!--aop --> <!-- 扫描注解bean --> <context:component-scan base-package="com.thinkgem.jeesite.common.aspect"/> <aop:aspectj-autoproxy proxy-target-class="true"/>
验证权限方法使用 直接用注解就可以了AccessToken
例如
package com.thinkgem.jeesite.modules.app.web.pay; import com.alibaba.fastjson.JSON; import com.thinkgem.jeesite.common.annotation.AccessToken; import com.thinkgem.jeesite.common.base.ResultApp; import com.thinkgem.jeesite.modules.app.service.pay.AppAlipayConfService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import java.util.HashMap; import java.util.Map; /** * 支付接口 * Created by Hamming on 2016/12/ */ @Controller @RequestMapping(value = "/app/pay") public class AppPayModule { @Autowired private AppAlipayConfService appAlipayConfService; @RequestMapping(value = "/alipay", method = RequestMethodPOST, produces="application/json") @AccessToken @ResponseBody public Object alipay(String orderId){ if(orderId ==null){ Map re = new HashMap<>(); re.put("result",3); re.put("msg","参数错误"); String json = JSONtoJSONString(re); return json; }else { return null; } } }
方法二: AOP方法2
1.定义一个查询父类,里面包含到authToken跟usedId两个属性,所有需要校验用户的请求的查询参数都继承这个查询父类,之所以会有这个userId,是因为我们校验得到用户之后,需要根据用户Id获取一些用户数据的,所以在AOP层我们就回填了这个参数了,这样也不会影响之前的代码逻辑(这个可能跟我的业务需求有关了)
public class AuthSearchVO { public String authToken; //校验字符串 public Integer userId; //APP用户Id public final String getAuthToken() { return authToken; } public final void setAuthToken(String authToken) { this.authToken = authToken; } public final Integer getUserId() { return userId; } public final void setUserId(Integer userId) { this.userId = userId; } @Override public String toString() { return "SearchVO [authToken=" + authToken + ", userId=" + userId + "]"; } }
2.定义一个方法级的注解,所有需要校验的请求都加上这个注解,用于AOP的拦截(当然你也可以拦截所有控制器的请求)
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface AuthToken { String type(); }
3.AOP处理,之所以会将注解作为参数传进来,是因为考虑到可能会有多个APP的校验,可以利用注解的type属性加以区分
public class AuthTokenAOPInterceptor { @Resource private AppUserService appUserService; private static final String authFieldName = "authToken"; private static final String userIdFieldName = "userId"; public void before(JoinPoint joinPoint, AuthToken authToken) throws Throwable{ Object[] args = joinPoint.getArgs(); //获取拦截方法的参数 boolean isFound = false; for(Object arg : args){ if(arg != null){ Class<?> clazz = arg.getClass();//利用反射获取属性值 Field[] fields = clazz.getDeclaredFields(); int authIndex = -1; int userIdIndex = -1; for(int i = 0; i < fields.length; i++){ Field field = fields[i]; field.setAccessible(true); if(authFieldName.equals(field.getName())){//包含校验Token authIndex = i; }else if(userIdFieldName.equals(field.getName())){//包含用户Id userIdIndex = i; } } if(authIndex >= 0 & userIdIndex >= 0){ isFound = true; authTokenCheck(fields[authIndex], fields[userIdIndex], arg, authToken);//校验用户 break; } } } if(!isFound){ throw new BizException(ErrorMessage.CHECK_AUTHTOKEN_FAIL); } } private void authTokenCheck(Field authField, Field userIdField, Object arg, AuthToken authToken) throws Exception{ if(String.class == authField.getType()){ String authTokenStr = (String)authField.get(arg);//获取到校验Token AppUser user = appUserService.getUserByAuthToken(authTokenStr); if(user != null){ userIdField.set(arg, user.getId()); }else{ throw new BizException(ErrorMessage.CHECK_AUTHTOKEN_FAIL); } } } }
4.最后就是在配置文件中配置这个AOP了(因为我们的spring版本跟aspect版本有点出入,导致用不了基于注解的方式)
<bean id="authTokenAOPInterceptor" class="com.distinct.app.web.common.auth.AuthTokenAOPInterceptor"/> <aop:config proxy-target-class="true"> <aop:pointcut id="authCheckPointcut" expression="@annotation(authToken)"/> <aop:aspect ref="authTokenAOPInterceptor" order="1"> <aop:before method="before" pointcut-ref="authCheckPointcut"/> </aop:aspect> </aop:config>
最后给出测试代码,这样的代码就优雅很多了
@RequestMapping(value = "/appointments", method = { RequestMethod.GET }) @ResponseBody @AuthToken(type="disticntApp") public List<AppointmentVo> getAppointments(AppointmentSearchVo appointmentSearchVo) { List<AppointmentVo> appointments = appointmentService.getAppointment(appointmentSearchVo.getUserId(), appointmentSearchVo); return appointments; }
方法三: MVC拦截器
服务器:
拼接token之外所有参数,最后拼接token_key,做MD5,与token参数比对
如果token比对失败返回状态码 500
public class APIInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Log.info(request); String token = request.getParameter("token"); // token is not needed when debug if(token == null) return true; // !! remember to comment this when deploy on server !! Enumeration paraKeys = request.getParameterNames(); String encodeStr = ""; while (paraKeys.hasMoreElements()) { String paraKey = (String) paraKeys.nextElement(); if(paraKey.equals("token")) break; String paraValue = request.getParameter(paraKey); encodeStr += paraValue; } encodeStr += Default.TOKEN_KEY; Log.out(encodeStr); if ( ! token.equals(DigestUtils.md5Hex(encodeStr))) { response.setStatus(500); return false; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { Log.info(request); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } }
spring-config.xml配置中加入
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/api/*" /> <bean class="cn.web.interceptor.APIInterceptor" /> </mvc:interceptor> </mvc:interceptors>
客户端:
拼接请求接口的所有参数,最后拼接token_key,做MD5,作为token参数
请求样例:http://127.0.0.1:8080/interface/api?key0=param0&key1=param1&token=md5(concat(param0, param1))
api测试页面,用到了Bootstrap和AngularJS,还有一个js的hex_md5函数
<!doctype html> <html ng-app> <head> <meta charset="UTF-8"> <title>API test</title> <link href="../css/bootstrap.min.css" rel="external nofollow" rel="stylesheet"> <script src="../js/md5.min.js"></script> <script src="../js/angular.min.js"></script> <script> function API(url){ this.url = arguments[0]; this.params = Array.prototype.slice.call(arguments, 1, arguments.length); this.request = function(params){ var addr = url; var values = Array.prototype.slice.call(arguments, 1, arguments.length); if(params[0] != undefined && values[0] != undefined && values[0] != '') addr += '?' + params[0] + "=" + values[0]; for(var i=1; i < valueslength; i++) if(params[i] != undefined && values[i] != undefined && values[i] != '') addr += "&" + params[i] + "=" + values[i]; return addr; } } function APIListCtrl($scope) { $scope.md5 = hex_md5; $scope.token_key = "9ae5r06fs8"; $scope.concat = function(){ var args = Array.prototype.slice.call(arguments, 0, arguments.length); args.push($scope.token_key); return args.join(""); } $scope.apilist = [ new API("account/login", "username", "pwd"), new API("account/register", "username", "pwd", "tel", "code"), ] ; } </script> </head> <body> <div ng-controller="APIListCtrl"> <div> Search: <input type="text" ng-model="search"><hr> token_key <input type="text" ng-model="token_key"> md5 <input type="text" ng-model="str"> {{md5(str)}} </div> <hr> <div ng-repeat="api in apilist | filter:search" > <form action="{{api.url}}" method="post"> <a href="{{api.request(api.params, value0, value1, value2, value3, value4, value5, value6, value7, value8, value9)}}" rel="external nofollow" > {{api.request(api.params, value0, value1, value2, value3, value4, value5, value6, value7, value8, value9)}} </a> <br> {{concat(value0, value1, value2, value3, value4, value5, value6, value7, value8, value9)}} <br> {{api.params[0]}} <input id="{{api.params[0]}}" name="{{api.params[0]}}" ng-model="value0" ng-hide="api.params[0]==undefined"> {{api.params[1]}} <input id="{{api.params[1]}}" name="{{api.params[1]}}" ng-model="value1" ng-hide="api.params[1]==undefined"> {{api.params[2]}} <input id="{{api.params[2]}}" name="{{api.params[2]}}" ng-model="value2" ng-hide="api.params[2]==undefined"> {{api.params[3]}} <input id="{{api.params[3]}}" name="{{api.params[3]}}" ng-model="value3" ng-hide="api.params[3]==undefined"> {{api.params[4]}} <input id="{{api.params[4]}}" name="{{api.params[4]}}" ng-model="value4" ng-hide="api.params[4]==undefined"> {{api.params[5]}} <input id="{{api.params[5]}}" name="{{api.params[5]}}" ng-model="value5" ng-hide="api.params[5]==undefined"> {{api.params[6]}} <input id="{{api.params[6]}}" name="{{api.params[6]}}" ng-model="value6" ng-hide="api.params[6]==undefined"> {{api.params[7]}} <input id="{{api.params[7]}}" name="{{api.params[7]}}" ng-model="value7" ng-hide="api.params[7]==undefined"> {{api.params[8]}} <input id="{{api.params[8]}}" name="{{api.params[8]}}" ng-model="value8" ng-hide="api.params[8]==undefined"> {{api.params[9]}} <input id="{{api.params[9]}}" name="{{api.params[9]}}" ng-model="value9" ng-hide="api.params[9]==undefined"> token <input id="token" name="token" value="{{md5(concat(value0, value1, value2, value3, value4, value5, value6, value7, value8, value9))}}"> <input type="submit" class="btn" ng-hide="api.params[0]==undefined"> </form> <hr> </div> </div> </body> </html>
加载全部内容