Token注解实现前后端安全访问
前言
Github:https://github.com/HealerJean
1、Tocken注解
package com.duodian.youhui.admin.config.token.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 校验用户token
* 可以用于class 或者 method
* 如果在method和class都有,则以method的为准。
* @author HealerJean
*/
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Token {
/**
* 是否校验token
* @return
*/
boolean check() default true;
}
2、用户登录保存Token
@ApiOperation(value = "第二步:通过code换取网页授权access_token",
notes = "第二步:通过code换取网页授权access_token",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE,
response = ResponseBean.class
)
@ApiImplicitParams({
@ApiImplicitParam(name = "code", value = " code作为换取access_token的票据,每次用户授权带上的code将不一样,code只能使用一次,5分钟未被使用自动过期。", required =true,paramType = "query", dataType = "string"),
@ApiImplicitParam(name = "scope", value = "应用授权作用域 snsapi_base,snsapi_userinfo", required =true,paramType = "query", dataType = "string"),
@ApiImplicitParam(name = "fuWuBusinessNoId", value = "服务号 数据库中存放微信运营者的,主键,服务区号哆趣商城 3: ",paramType = "query", dataType = "int"),
@ApiImplicitParam(name = "dingYueBusinessNoId", value = "订阅号 数据库中存放微信运营者的,主键,火影情报室 2: ",paramType = "query", dataType = "int"),
@ApiImplicitParam(name = "userInfoPId", value = "推荐人Id", required =false,paramType = "query", dataType = "long")
})
@GetMapping( value = "youhui/web/authorize/redirect",produces="application/json;charset=utf-8")
@ResponseBody
public ResponseBean authorizeRedirect(String code,Long fuWuBusinessNoId,Long dingYueBusinessNoId,String scope,Long userInfoPId){
try {
return ResponseBean.buildSuccess(userInfoService.loginToken(fuWuBusinessNoId,dingYueBusinessNoId,code,scope,userInfoPId));
} catch (AppException e) {
ExceptionLogUtils.log(e, this.getClass());
return ResponseBean.buildFailure(e.getCode(),e.getMessage());
} catch (Exception e) {
ExceptionLogUtils.log(e, this.getClass());
return ResponseBean.buildFailure(e.getMessage());
}
}
service中
/**
* 用户登录返回Token,之后利用拦截器中的toke
* 获取用户当前上下文中的的id和openId
* @param fuWuBusinessNoId
* @param dingYueBusinessNoId
* @param code
* @param scope
* @param userInfoPId
* @return
*/
@Override
public String loginToken(Long fuWuBusinessNoId,Long dingYueBusinessNoId ,String code,String scope,Long userInfoPId) {
UserInfo userInfo = weChatWebService.getAccessTokenByCode(fuWuBusinessNoId,dingYueBusinessNoId,code,scope,userInfoPId);
String token = setAppToken(userInfo.getId());
return token;
}
@Override
public String setAppToken(Long userId) {
String token = DecriptUtil.LoginEncrypt(userId+"");
stringRedisTemplate.opsForValue().set(CacheKey.APP_TOKEN + userId, token, 30, TimeUnit.DAYS);
return token;
}
@Override
public boolean checkAppToken(Long userId, String token) {
String appToken = stringRedisTemplate.opsForValue().get(CacheKey.APP_TOKEN + userId);
return !EmptyUtils.isEmpty(appToken) && appToken.equals(token);
}
3、需要拦截的url打入Token
@Token
/**
* 跳转到优惠券
* @return
*/
@Token
@GetMapping(value = "itemFilter",produces = "application/json")
@ResponseBody
@ApiOperation(value = "跳转到优惠券",notes = "跳转到优惠券",
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE,
response = CouponItemGood.class)
@ApiImplicitParams({
@ApiImplicitParam(name = "userInfoId" ,value = "用户的id",dataTypeClass = Long.class,required = true,paramType = "query"),
@ApiImplicitParam(name = "timeDiff" ,value = "渠道的失效时间设置",dataTypeClass = Integer.class,paramType = "query")
})
public ResponseBean itemFilter(Long userInfoId,Integer timeDiff){
try {
return ResponseBean.buildSuccess (haoDaoKuService.itemFilter(WeChatMessageParams.HAO_DAN_KU_GAOYONG_APIKEY, userInfoId, timeDiff));
}catch (AppException e){
ExceptionLogUtils.log(e, this.getClass());
return ResponseBean.buildFailure(e.getCode(),e.getMessage());
}catch (Exception e){
ExceptionLogUtils.log(e, this.getClass());
return ResponseBean.buildFailure(e.getMessage());
}
}
4、Token拦截器
package com.duodian.youhui.admin.config.token.aop;
import com.duodian.youhui.admin.bean.ResponseBean;
import com.duodian.youhui.admin.config.ContextHolder;
import com.duodian.youhui.admin.config.token.annotation.Token;
import com.duodian.youhui.admin.moudle.user.service.UserInfoService;
import com.duodian.youhui.admin.utils.DecriptUtil;
import com.duodian.youhui.enums.exception.ErrorCodeEnum;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
* @author HealerJean
*/
@Aspect
@Component
public class TokenInterceptor {
@Resource
private UserInfoService userInfoService;
/**
* 先取method的Token,如果没有再去class的Token,如果有使用method的Token
* 从request获取token进行解密,获取userId和oid,如果解密失败,返回错误信息
* 把userId和oid放入当前上下文
*
* @param point
* @return
* @throws Throwable
*/
@Around("@annotation(com.duodian.youhui.admin.config.token.annotation.Token) || @within(com.duodian.youhui.admin.config.token.annotation.Token)")
protected Object invoke(ProceedingJoinPoint point) throws Throwable {
Token token = null;
Signature signature = point.getSignature();
if (signature instanceof MethodSignature) {
Method method = ((MethodSignature) signature).getMethod();
token = method.getAnnotation(Token.class);
}
if (token == null) {
token = point.getTarget().getClass().getAnnotation(Token.class);
}
if (token == null || !token.check()) {
return point.proceed();
}
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String tokenParam = request.getParameter("token");
// String tokenParam = request.getHeader("token");
if (tokenParam == null) {
return ResponseBean.buildFailure(ErrorCodeEnum.token认证失败) ;
}
try {
String decrip = DecriptUtil.LoginDecrypt(tokenParam) ;
if (decrip == null) {
return ResponseBean.buildFailure(ErrorCodeEnum.token认证失败) ;
}
Long userId = Long.valueOf( decrip);
//检验redis中是否还存在,并比较相等
if (!userInfoService.checkAppToken(userId, tokenParam)) {
return ResponseBean.buildFailure(ErrorCodeEnum.token认证失败) ;
}
ContextHolder.setUserId(userId);
return point.proceed();
} catch (Exception e) {
throw e;
} finally {
ContextHolder.clear();
}
}
}
5、Token加解密
package com.duodian.youhui.admin.utils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
/**
* @Desc:各种加密解密
* @Author HealerJean
* @Date 2018/5/23 下午3:12.
*/
public class DecriptUtil {
private static final String Loginkey = "jDFBfadsffsdffasdf";
private static final byte[] Loginiv = "4960432565656562".getBytes();;
private static final String Loginmode = "AES/CBC/PKCS5Padding";
public static String LoginEncrypt( String strIn) {
try {
SecretKeySpec skeySpec = getKey(Loginkey);
Cipher cipher = Cipher.getInstance(Loginmode); //"算法/模式/补码方式"
IvParameterSpec iv = new IvParameterSpec(Loginiv);////使用CBC模式,需要一个向量iv,可增加加密算法的强度
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(strIn.getBytes());
return new BASE64Encoder().encode(encrypted);
} catch (Exception e) {
return null;
}
}
public static String LoginDecrypt(String strIn) {
try {
SecretKeySpec skeySpec = getKey(Loginkey);
Cipher cipher = Cipher.getInstance(Loginmode);
IvParameterSpec iv = new IvParameterSpec(Loginiv);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] encrypted1 = new BASE64Decoder().decodeBuffer(strIn);
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original);
return originalString;
} catch (Exception e) {
return null;
}
}
private static SecretKeySpec getKey(String strKey) throws Exception {
byte[] arrBTmp = strKey.getBytes();
byte[] arrB = new byte[16]; // 创建一个空的16位字节数组(默认值为0)
for (int i = 0; i < arrBTmp.length && i < arrB.length; i++) {
arrB[i] = arrBTmp[i];
}
SecretKeySpec skeySpec = new SecretKeySpec(arrB, "AES");
return skeySpec;
}
public static String SHA1(String decript) {
try {
MessageDigest digest = MessageDigest
.getInstance("SHA-1");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
public static String SHA(String decript) {
try {
MessageDigest digest = MessageDigest
.getInstance("SHA");
digest.update(decript.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < messageDigest.length; i++) {
String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
public static String MD5(String input) {
try {
// 获得MD5摘要算法的 MessageDigest 对象
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// 使用指定的字节更新摘要
mdInst.update(input.getBytes());
// 获得密文
byte[] md = mdInst.digest();
// 把密文转换成十六进制的字符串形式
StringBuffer hexString = new StringBuffer();
// 字节数组转换为 十六进制 数
for (int i = 0; i < md.length; i++) {
String shaHex = Integer.toHexString(md[i] & 0xFF);
if (shaHex.length() < 2) {
hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
/**
* 加密
*
* @param content
* 需要加密的内容
* @param password
* 加密密码
* @return
*/
public static byte[] encryptAES(String content, String password) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, new SecureRandom(password.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
byte[] byteContent = content.getBytes("utf-8");
cipher.init(Cipher.ENCRYPT_MODE, key);// 初始化
byte[] result = cipher.doFinal(byteContent);
return result; // 加密
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}
/**
* 解密
*
* @param content
* 待解密内容
* @param password
* 解密密钥
* @return
*/
public static byte[] decryptAES(byte[] content, String password) {
try {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, new SecureRandom(password.getBytes()));
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
Cipher cipher = Cipher.getInstance("AES");// 创建密码器
cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
byte[] result = cipher.doFinal(content);
return result; // 加密
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}
/**
* BASE64解密
*
* @param key
* @return
* @throws Exception
*/
public static String decryptBASE64(String key) {
return "";
}
/**
* BASE64加密
*
* @param key
* @return
* @throws Exception
*/
public static String encryptBASE64(String key) {
return "";
}
// public static void main(String[] args) throws Exception {
//
// String Code = "1";
// String codE = DecriptUtil.LoginEncrypt(Code);
// System.out.println("原文:" + Code);
// System.out.println("密文:" + codE);
// System.out.println("解密:" + DecriptUtil.LoginDecrypt( codE));
// }
}
6、跨域解决header问题
/**
* @Desc:
* @Author HealerJean
* @Date 2018/6/21 下午8:20.
*/
@Component
@Slf4j
public class CORSInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//添加跨域CORS
// 服务器不在一起,下面这个跨域是马川 给我的,好用,注意请求头上的内容
String originHeader = request.getHeader("Origin");
response.setHeader("Access-Control-Allow-Origin", originHeader);
response.addHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With,accessToken,token,code");
response.addHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, PATCH");
return true;
}
}