淺談Java 三種方式實(shí)現(xiàn)接口校驗(yàn)
本文介紹了Java 三種方式實(shí)現(xiàn)接口校驗(yàn),主要包括AOP,MVC攔截器,分享給大家,具體如下:
方法一:AOP
代碼如下定義一個(gè)權(quán)限注解
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;
/**
* 權(quán)限注解
* Created by Hamming on 2016/12/
*/
@Target(ElementType.METHOD)//這個(gè)注解是應(yīng)用在方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessToken {
/* String userId();
String token();*/
}
獲取頁面請(qǐng)求中的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(); //執(zhí)行連接點(diǎn)方法
//獲取執(zhí)行方法的參數(shù)
return object;
}else {
return ResultApp.error(3,"token失效");
}
}
}
token驗(yàn)證類 存儲(chǔ)用到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登陸驗(yàn)證
* 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以字節(jié)流形式存入redis
*
* @param date 失效時(shí)間
* @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();
// 計(jì)算失效秒,7889400秒三個(gè)月
Date temp = new Date();
long interval = (date.getTime() - temp.getTime())/1000;
JedisUtils.set(at+appId ,token,(int)interval);
return token;
}
/**
* 驗(yàn)證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"/>
驗(yàn)證權(quán)限方法使用 直接用注解就可以了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","參數(shù)錯(cuò)誤");
String json = JSONtoJSONString(re);
return json;
}else {
return null;
}
}
}
方法二: AOP方法2
1.定義一個(gè)查詢父類,里面包含到authToken跟usedId兩個(gè)屬性,所有需要校驗(yàn)用戶的請(qǐng)求的查詢參數(shù)都繼承這個(gè)查詢父類,之所以會(huì)有這個(gè)userId,是因?yàn)槲覀冃r?yàn)得到用戶之后,需要根據(jù)用戶Id獲取一些用戶數(shù)據(jù)的,所以在AOP層我們就回填了這個(gè)參數(shù)了,這樣也不會(huì)影響之前的代碼邏輯(這個(gè)可能跟我的業(yè)務(wù)需求有關(guān)了)
public class AuthSearchVO {
public String authToken; //校驗(yàn)字符串
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.定義一個(gè)方法級(jí)的注解,所有需要校驗(yàn)的請(qǐng)求都加上這個(gè)注解,用于AOP的攔截(當(dāng)然你也可以攔截所有控制器的請(qǐng)求)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AuthToken {
String type();
}
3.AOP處理,之所以會(huì)將注解作為參數(shù)傳進(jìn)來,是因?yàn)榭紤]到可能會(huì)有多個(gè)APP的校驗(yàn),可以利用注解的type屬性加以區(qū)分
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(); //獲取攔截方法的參數(shù)
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())){//包含校驗(yàn)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);//校驗(yàn)用戶
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);//獲取到校驗(yàn)Token
AppUser user = appUserService.getUserByAuthToken(authTokenStr);
if(user != null){
userIdField.set(arg, user.getId());
}else{
throw new BizException(ErrorMessage.CHECK_AUTHTOKEN_FAIL);
}
}
}
}
4.最后就是在配置文件中配置這個(gè)AOP了(因?yàn)槲覀兊膕pring版本跟aspect版本有點(diǎn)出入,導(dǎo)致用不了基于注解的方式)
<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>
最后給出測(cè)試代碼,這樣的代碼就優(yōu)雅很多了
@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攔截器
服務(wù)器:
拼接token之外所有參數(shù),最后拼接token_key,做MD5,與token參數(shù)比對(duì)
如果token比對(duì)失敗返回狀態(tài)碼 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>
客戶端:
拼接請(qǐng)求接口的所有參數(shù),最后拼接token_key,做MD5,作為token參數(shù)
請(qǐng)求樣例:http://127.0.0.1:8080/interface/api?key0=param0&key1=param1&token=md5(concat(param0, param1))
api測(cè)試頁面,用到了Bootstrap和AngularJS,還有一個(gè)js的hex_md5函數(shù)
<!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>
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
springboot整合mail實(shí)現(xiàn)郵箱的發(fā)送功能
本文分步驟給大家介紹springboot整合mail實(shí)現(xiàn)郵箱的發(fā)送功能,代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2021-09-09
解決Mybatis的serverTimezone時(shí)區(qū)出現(xiàn)問題
這篇文章主要介紹了解決Mybatis的serverTimezone時(shí)區(qū)出現(xiàn)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
Spring-基于Spring使用自定義注解及Aspect實(shí)現(xiàn)數(shù)據(jù)庫切換操作
這篇文章主要介紹了Spring-基于Spring使用自定義注解及Aspect實(shí)現(xiàn)數(shù)據(jù)庫切換操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-09-09
java如何實(shí)現(xiàn)基于opencv全景圖合成實(shí)例代碼
全景圖相信大家應(yīng)該都不陌生,下面這篇文章主要給大家介紹了關(guān)于java如何實(shí)現(xiàn)基于opencv全景圖合成的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07
JavaWeb實(shí)現(xiàn)注冊(cè)用戶名檢測(cè)
這篇文章主要為大家詳細(xì)介紹了JavaWeb實(shí)現(xiàn)注冊(cè)用戶名檢測(cè),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08
一文教會(huì)你如何搭建vue+springboot項(xiàng)目
最近在搗鼓?SpringBoot?與?Vue?整合的項(xiàng)目,所以下面這篇文章主要給大家介紹了關(guān)于如何通過一篇文章教會(huì)你搭建vue+springboot項(xiàng)目,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05
Mybatis之typeAlias配置的3種方式小結(jié)
這篇文章主要介紹了Mybatis之typeAlias配置的3種方式小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01

