书单推荐:成为Java顶级程序员架构师 ,这20来本(高薪)必看点击获取
ǰÑÔ
´ÓÈ¥Äê10Ô·ݵ½ÏÖÔÚæµÄûʱ¼äд²©¿ÍÁË£¬½ñÌì¾Í˦¸ø´ó¼ÒÒ»¸ö¸É»õ°É£¡£¡£¡
½üÀ´ºÜ¶àÈËÎʵ½ÏÂÃæµÄÎÊÌâ
- ÎÒÃDz»ÏëÔÚÿ¸öController·½·¨ÊÕµ½×Ö·û´®±¨ÎĺóÔÙµ÷ÓÃÒ»´Î½âÃÜ£¬ËäÈ»¿ÉÒÔÍê³É£¬µ«ÊǺÜlow£¬ÇÒÈç¹ûÏë²»ÔÙʹÓüӽâÃÜ£¬ÐÞ¸ÄÆðÀ´ºÜÊÇÂé·³¡£
- ÎÒÃÇÏëÔÚʹÓÃRest¹¤¾ß»òswaggerÇëÇóµÄʱºò²»½øÐмӽâÃÜ£¬¶øÔÚappµ÷ÓõÄʱºò´¦Àí¼Ó½âÃÜ£¬Õâ¿ÉÈçºÎ²Ù×÷¡£
Õë¶ÔÒÔÉϵÄÎÊÌ⣬ÏÂÃæÖ±½Ó¸ø³ö½â¾ö·½°¸£º
ʵÏÖ˼·
- APPµ÷ÓÃAPIµÄʱºò£¬Èç¹ûÐèÒª¼Ó½âÃܵĽӿڣ¬ÐèÒªÔÚhttpHeaderÖиø³ö¼ÓÃÜ·½Ê½£¬Èçheader[encodeMethod]¡£
- Rest¹¤¾ß»òswaggerÇëÇóµÄʱºòÎÞÐèÖ¸¶¨´Ëheader¡£
- ºó¶ËAPIÊÕµ½requestºó£¬ÅжÏheaderÖеÄencodeMethod×ֶΣ¬Èç¹ûÓÐÖµ£¬ÔòÈÏΪÊÇÐèÒª½âÃÜ£¬·ñÔò¾ÍÈÏΪÊÇÃ÷ÎÄ¡£
Ô¼¶¨
ΪÁ˾«¼ò·ÖÏí¼¼Êõ£¬ÏÈÔ¼¶¨Ö»´¦ÀíPOSTÉÏ´«JSON(application/json)Êý¾ÝµÄ¼Ó½âÃÜ´¦Àí¡£
ÇëÇó½âÃÜʵÏÖ·½Ê½
1. Ïȶ¨Òåcontroller
@Controller
@RequestMapping("/api/demo")
public class MyDemoController {
@RequestDecode
@ResponseBody
@RequestMapping(value = "user", method = RequestMethod.POST)
public ResponseDto addUser(
@RequestBody User user
) throws Exception {
//TODO ...
}
}
/**
* ½âÃÜÇëÇóÊý¾Ý
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestDecode {
SecurityMethod method() default SecurityMethod.NULL;
}
¿ÉÒÔ¿´µ½ÕâÀïµÄController¶¨ÒåµÄºÜÆÕͨ£¬Ö»ÓÐÒ»¸ö¶îÍâµÄ×Ô¶¨Òå×¢½âRequestDecode£¬Õâ¸ö×¢½âÊÇΪÁËÏÂÃæµÄRequestBodyAdviceµÄʹÓá£
2. ½¨Éè×Ô¼ºµÄRequestBodyAdvice
ÓÐÁËÉÏÃæµÄÈë¿Ú¶¨Ò壬½ÓÏÂÀ´´¦Àí½âÃÜÕâ¼þÊ£¬Ä¿µÄºÜÃ÷È·£º
1. ÊÇ·ñÐèÒª½âÃÜÅжÏhttpHeaderÖеÄencodeMethod×ֶΡ£
2. ÔÚ½øÈëcontroller֮ǰ¾Í½âÃÜÍê³É£¬ÊÇcontroller´¦ÀíÂß¼ÎÞ¸ÐÖª¡£
DecodeRequestBodyAdvice.java
@Slf4j
@Component
@ControllerAdvice(basePackages = "com.xxx.hr.api.controller")
public class DecodeRequestBodyAdvice implements RequestBodyAdvice {
@Value("${hrapi.aesKey}")
String aesKey;
@Value("${hrapi.googleKey}")
String googleKey;
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return methodParameter.getMethodAnnotation(RequestDecode.class) != null
&& methodParameter.getParameterAnnotation(RequestBody.class) != null;
}
@Override
public Object handleEmptyBody(Object body, HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage request, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
RequestDecode requestDecode = parameter.getMethodAnnotation(RequestDecode.class);
if (requestDecode == null) {
return request;//controller·½·¨²»ÒªÇó¼Ó½âÃÜ
}
String appId = request.getHeaders().getFirst(com.xxx.hr.bean.constant.HttpHeaders.APP_ID);//ÕâÀïÊÇÀ©Õ¹£¬¿ÉÒÔÖªµÀÀ´Ô´·½£¨È翪·Åƽ̨ʹÓã©
String encodeMethod = request.getHeaders().getFirst(com.xxx.hr.bean.constant.HttpHeaders.ENCODE_METHOD);
if (StringUtils.isEmpty(encodeMethod)) {
return request;
}
SecurityMethod encodeMethodEnum = SecurityMethod.getByCode(encodeMethod);
//ÕâÀïÁé»îµÄ¿ÉÒÔÖ§³Öµ½¶àÖÖ¼Ó½âÃÜ·½Ê½
switch (encodeMethodEnum) {
case NULL:
break;
case AES: {
InputStream is = request.getBody();
ByteBuf buf = PooledByteBufAllocator.DEFAULT.heapBuffer();
int ret = -1;
int len = 0;
while((ret = is.read()) > 0) {
buf.writeByte(ret);
len ++;
}
String body = buf.toString(0, len, xxxSecurity.DEFAULT_CHARSET);
buf.release();
String temp = null;
try {
temp = XxxSecurity.aesDecodeData(body, aesKey, googleKey, new CheckCallBack() {
@Override
public boolean isRight(String data) {
return data != null && (data.startsWith("{") || data.startsWith("["));
}
});
log.info("½âÃÜÍê³É: {}", temp);
return new DecodedHttpInputMessage(request.getHeaders(), new ByteArrayInputStream(temp.getBytes("UTF-8")));
} catch (DecodeException e) {
log.warn("½âÃÜʧ°Ü appId: {}, Name:{} ´ý½âÃÜÃÜÎÄ: {}", appId, partnerName, body, e);
throw e;
}
}
}
return request;
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
static class DecodedHttpInputMessage implements HttpInputMessage {
HttpHeaders headers;
InputStream body;
public DecodedHttpInputMessage(HttpHeaders headers, InputStream body) {
this.headers = headers;
this.body = body;
}
@Override
public InputStream getBody() throws IOException {
return body;
}
@Override
public HttpHeaders getHeaders() {
return headers;
}
}
}
ÖÁ´Ë¼Ó½âÃÜÍê³ÉÁË¡£
¡ª¡ª¡ª¡ª¡ª¡ª¡ª¡ª-»ªÀö·Ö¸îÏß ¡ª¡ª¡ª¡ª¡ª¡ª¡ª¡ª¡ª¨C
ÏìÓ¦¼ÓÃÜ
ÏÂÃæ¸½¼þÒ»ÏÂÏìÓ¦¼ÓÃܹý³Ì£¬Ä¿µÄ
1. ControllerÂß¼´úÂëÎÞ¸ÐÖª
2. ¿ÉÒÔÒ»¼ü¿ª¹ØÏìÓ¦¼ÓÃÜ
¶¨ÒåController
@ResponseEncode
@ResponseBody
@RequestMapping(value = "employee", method = RequestMethod.GET)
public ResponseDto<UserEEInfo> userEEInfo(
@ApiParam("Óû§±àºÅ") @RequestParam(HttpHeaders.APPID) Long userId
) {
//TODO ...
}
/**
* ¼ÓÃÜÏìÓ¦Êý¾Ý
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseEncode {
SecurityMethod method() default SecurityMethod.NULL;
}
ÕâÀïµÄController¶¨ÒåµÄÒ²ºÜÆÕͨ£¬Ö»ÓÐÒ»¸ö¶îÍâµÄ×Ô¶¨Òå×¢½âResponseEncode£¬Õâ¸ö×¢½âÊÇΪÁËÏÂÃæµÄResponseBodyAdviceµÄʹÓá£
½¨Éè×Ô¼ºµÄResponseBodyAdvice
ÕâÀïÔ¼¶¨½«ÏìÓ¦µÄDTOÐòÁл¯ÎªJSON¸ñʽÊý¾Ý£¬È»ºóÔÙ¼ÓÃÜ£¬×îºóÔÚÏìÓ¦¸øÇëÇ󷽡£
@Slf4j
@Component
@ControllerAdvice(basePackages = "com.xxx.hr.api.controller")
public class EncodeResponseBodyAdvice implements ResponseBodyAdvice {
@Autowired
PartnerService partnerService;
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
return returnType.getMethodAnnotation(ResponseEncode.class) != null;
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
ResponseEncode responseEncode = returnType.getMethodAnnotation(ResponseEncode.class);
String uid = request.getHeaders().getFirst(HttpHeaders.PARTNER_UID);
if (uid == null) {
uid = request.getHeaders().getFirst(HttpHeaders.APP_ID);
}
PartnerConfig config = partnerService.getConfigByAppId(uid);
if (responseEncode.method() == SecurityMethod.NULL || responseEncode.method() == SecurityMethod.AES) {
if (config == null) {
return ResponseDto.rsFail(ResponseCode.E_403, "ÉÌ»§²»´æÔÚ");
}
String temp = JSON.toJSONString(body);
log.debug("´ý¼ÓÃÜÊý¾Ý: {}", temp);
String encodedBody = XxxSecurity.aesEncodeData(temp, config.getEncryptionKey(), config.getGoogleKey());
log.debug("¼ÓÃÜÍê³É: {}", encodedBody);
response.getHeaders().set(HttpHeaders.ENCODE_METHOD, HttpHeaders.VALUE.AES);
response.getHeaders().set(HttpHeaders.HEADER_CONTENT_TYPE, HttpHeaders.VALUE.APPLICATION_BASE64_JSON_UTF8);
response.getHeaders().remove(HttpHeaders.SIGN_METHOD);
return encodedBody;
}
return body;
}
}
ÍØÕ¹
ÓÉÉÏÃæµÄʵÏÖ£¬ÈçºÎʵÏÖRSAÑé֤ǩÃûÄØ£¿Õâ¸ö¾Í¼òµ¥ÁË£¬Çë¿´·Ö½â¡£
Ä¿µÄ»¹ÊǺܼòµ¥£¬½øÀ´¼õÉÙ¶ÔÒµÎñÂß¼µÄÈëÇÖ¡£
Ê×ÏÈÉ趨һÏÂÄÇЩÇëÇóÐèÒªÑé֤ǩÃû
@RequestSign
@ResponseEncode
@ResponseBody
@RequestMapping(value = "employee", method = RequestMethod.GET)
public ResponseDto<UserEEInfo> userEEInfo(
@RequestParam(HttpHeaders.UID) String uid
) {
//TODO ...
}
ÕâÀﻹÊÇʹÓÃÒ»¸ö×¢½âRequestSign£¬È»ºóÔÙʵÏÖÒ»¸öSignInterceptor¼´¿ÉÍê³É£º
@Slf4j
@Component
public class SignInterceptor implements HandlerInterceptor {
@Autowired
PartnerService partnerService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod method = (HandlerMethod) handler;
RequestSign requestSign = method.getMethodAnnotation(RequestSign.class);
if (requestSign == null) {
return true;
}
String appId = request.getHeader(HttpHeaders.APP_ID);
ValidateUtils.notTrimEmptyParam(appId, "Header[appId]");
PartnerConfig config = partnerService.getConfigByAppId(appId);
ValidateUtils.notNull(config, Code.E_400, "ÉÌ‘ô²»´æÔÚ");
String partnerName = partnerService.getPartnerName(appId);
String sign = request.getParameter(HttpHeaders.SIGN);
String signMethod = request.getParameter(HttpHeaders.SIGN_METHOD);
signMethod = (signMethod == null) ? "RSA" : signMethod;
Map<String, String[]> parameters = request.getParameterMap();
ValidateUtils.notTrimEmptyParam(sign, "sign");
if ("RSA".equals(signMethod)) {
sign = sign.replaceAll(" ", "+");
boolean isOK = xxxxSecurity.signVerifyRequest(parameters, config.getRsaPublicKey(), sign, config.getSecurity());
if (isOK) {
log.info("ÑéÖ¤ÉÌ»§Ç©Ãûͨ¹ý {}[{}] ", appId, partnerName);
return true;
} else {
log.warn("ÑéÖ¤ÉÌ»§Ç©Ãûʧ°Ü {}[{}] ", appId, partnerName);
}
} else {
throw new SignVerifyException("Ôݲ»Ö§³Ö¸ÃÇ©Ãû");
}
throw new SignVerifyException("Ç©ÃûУÑéʧ°Ü");
}
@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 enum SecurityMethod {
NULL,
AES,
RSA,
DES,
DES3,
SHA1,
MD5
;
}
×¢½â¶¨Ò壺
/**
* ÇëÇóÊý¾ÝÊý¾ÝÐèÒª½âÃÜ
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestDecode {
SecurityMethod method() default SecurityMethod.NULL;
}
/**
* ÇëÇóÊý¾ÝÐèÒªÑéÇ©
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestSign {
SecurityMethod method() default SecurityMethod.RSA;
}
/**
* Êý¾ÝÏìÓ¦ÐèÒª¼ÓÃÜ
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseEncode {
SecurityMethod method() default SecurityMethod.NULL;
}
/**
* ÏìÓ¦Êý¾ÝÐèÒªÉú³ÉÇ©Ãû
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ResponseSign {
SecurityMethod method() default SecurityMethod.NULL;
}
aesDecodeData
/**
* AES ½âÃÜÊý¾Ý
*
* @param data ´ý½âÃÜÊý¾Ý
* @param aesKey AES ÃÜÔ¿(BASE64)
* @param googleAuthKey GoogleAuthKey(BASE64)
* @param originDataSign ÔʼÊý¾Ýmd5Ç©Ãû
* @return
*/
public static String aesDecodeDataEx(String data, String aesKey, String googleAuthKey, String originDataSign) {
return aesDecodeData(data, aesKey, googleAuthKey, System.currentTimeMillis(), null, originDataSign);
}
public static String aesDecodeData(String data, String aesKey, String googleAuthKey, long tm, CheckCallBack checkCallBack, String originDataSign) {
DecodeException lastError = null;
long timeWindow = googleAuth.getTimeWindowFromTime(tm);
int window = googleAuth.getConfig().getWindowSize();
for (int i = -((window - 1) / 2); i <= window / 2; ++i) {
String googleCode = googleAuth.calculateCode16(Base64.decodeBase64(googleAuthKey), timeWindow + i);
log.debug((timeWindow + i) + " googleCode: " + googleCode);
byte[] code = googleCode.getBytes(DEFAULT_CHARSET);
byte[] iv = new byte[16];
System.arraycopy(code, 0, iv, 0, code.length);
try {
String newKey = convertKey(aesKey, iv);
String decodedData = AES.decode(data, newKey, Base64.encodeBase64String(iv));
if (checkCallBack != null && !checkCallBack.isRight(decodedData)) {
continue;
}
if (originDataSign != null) {
String sign = DigestUtils.md5Hex(decodedData);
if (!sign.equalsIgnoreCase(originDataSign)) {
continue;
}
}
return decodedData;
} catch (DecodeException e) {
lastError = e;
}
}
if (lastError == null) {
lastError = new DecodeException("Decode Failed, Error Password!");
}
throw lastError;
}
signVerifyRequest
static boolean signVerifyRequest(Map<String, String[]> parameters, String rsaPublicKey, String sign, String security) throws SignVerifyException {
String preSignData = getHttpPreSignData(parameters, security);
log.debug("´ýÑéÇ©×Ö·û´®£º" + preSignData);
return RSA.verify(preSignData.getBytes(DEFAULT_CHARSET), rsaPublicKey, sign);
}
GoogleAuth
public class GoogleAuth {
private GoogleAuthenticatorConfig config;
private GoogleAuthenticator googleAuthenticator;
public GoogleAuth() {
GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder gacb =
new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder()
.setTimeStepSizeInMillis(TimeUnit.MINUTES.toMillis(2))
.setWindowSize(3)
.setCodeDigits(8)
.setKeyRepresentation(KeyRepresentation.BASE64);
config = gacb.build();
googleAuthenticator = new GoogleAuthenticator(config);
}
public GoogleAuthenticatorConfig getConfig(){
return config;
}
public void setConfig(GoogleAuthenticatorConfig c) {
config = c;
googleAuthenticator = new GoogleAuthenticator(config);
}
/**
* ÈÏÖ¤
* @param encodedKey(Base 32/64)
* @param code
* @return ÊÇ·ñͨ¹ý
*/
public boolean authorize(String encodedKey, int code) {
return googleAuthenticator.authorize(encodedKey, code);
}
/**
* Éú³É GoogleAuth Code
* @param keyBase64
* @return
*/
public int getCodeValidCode(String keyBase64) {
int code = googleAuthenticator.getTotpPassword(keyBase64);
return code;
}
public long getTimeWindowFromTime(long time)
{
return time / this.config.getTimeStepSizeInMillis();
}
private static String formatLabel(String issuer, String accountName) {
if (accountName == null || accountName.trim().length() == 0) {
throw new IllegalArgumentException("Account name must not be empty.");
}
StringBuilder sb = new StringBuilder();
if (issuer != null) {
if (issuer.contains(":")) {
throw new IllegalArgumentException("Issuer cannot contain the \':\' character.");
}
sb.append(issuer);
sb.append(":");
}
sb.append(accountName);
return sb.toString();
}
public String getOtpAuthTotpURL(String keyBase64) throws EncoderException{
return getOtpAuthTotpURL("MLJR", "myname@mljr.com", keyBase64);
}
/**
* Éú³ÉGoogleAuthÈÏÖ¤µÄURL,±ãÓÚÉú³É¶þάÂë
* @param issuer
* @param accountName
* @param keyBase32
* @return
*/
public String getOtpAuthTotpURL(String issuer, String accountName, String keyBase32) throws EncoderException {
StringBuilder url = new StringBuilder();
url.append("otpauth://")
.append("totp")
.append("/").append(formatLabel(issuer, accountName));
Map<String, String> parameter = new HashMap<String, String>();
/**
* https://github.com/google/google-authenticator/wiki/Key-Uri-Format
* The secret parameter is an arbitrary key value encoded in Base32 according to RFC 3548.
*/
parameter.put("secret", keyBase32);
if (issuer != null) {
if (issuer.contains(":")) {
throw new IllegalArgumentException("Issuer cannot contain the \':\' character.");
}
parameter.put("issuer", issuer);
}
parameter.put("algorithm", "SHA1");
parameter.put("digits", String.valueOf(config.getCodeDigits()));
parameter.put("period", String.valueOf(TimeUnit.MILLISECONDS.toSeconds(config.getTimeStepSizeInMillis())));
URLCodec urlCodec = new URLCodec();
if (!parameter.isEmpty()) {
url.append("?");
for(String key : parameter.keySet()) {
String value = parameter.get(key);
if (value == null){
continue;
}
value = urlCodec.encode(value);
url.append(key).append("=").append(value).append("&");
}
}
return url.toString();
}
private static final String DEFAULT_RANDOM_NUMBER_ALGORITHM = "SHA1PRNG";
private static final String DEFAULT_RANDOM_NUMBER_ALGORITHM_PROVIDER = "SUN";
private static final String HMAC_HASH_FUNCTION = "HmacSHA1";
private static final String HMAC_MD5_FUNCTION = "HmacMD5";
/**
* »ùÓÚʱ¼ä Éú³É16λµÄ code
* @param key
* @param tm
* @return
*/
public String calculateCode16(byte[] key, long tm)
{
// Allocating an array of bytes to represent the specified instant
// of time.
byte[] data = new byte[8];
long value = tm;
// Converting the instant of time from the long representation to a
// big-endian array of bytes (RFC4226, 5.2. Description).
for (int i = 8; i-- > 0; value >>>= 8)
{
data[i] = (byte) value;
}
// Building the secret key specification for the HmacSHA1 algorithm.
SecretKeySpec signKey = new SecretKeySpec(key, HMAC_HASH_FUNCTION);
try
{
// Getting an HmacSHA1 algorithm implementation from the JCE.
Mac mac = Mac.getInstance(HMAC_HASH_FUNCTION);
// Initializing the MAC algorithm.
mac.init(signKey);
// Processing the instant of time and getting the encrypted data.
byte[] hash = mac.doFinal(data);
// Building the validation code performing dynamic truncation
// (RFC4226, 5.3. Generating an HOTP value)
int offset = hash[hash.length - 1] & 0xB;
// We are using a long because Java hasn't got an unsigned integer type
// and we need 32 unsigned bits).
long truncatedHash = 0;
for (int i = 0; i < 8; ++i)
{
truncatedHash <<= 8;
// Java bytes are signed but we need an unsigned integer:
// cleaning off all but the LSB.
truncatedHash |= (hash[offset + i] & 0xFF);
}
truncatedHash &= Long.MAX_VALUE;
truncatedHash %= 10000000000000000L;
// module with the maximum validation code value.
// Returning the validation code to the caller.
return String.format("%016d", truncatedHash);
} catch (InvalidKeyException e) {
throw new GoogleAuthenticatorException("The operation cannot be "
+ "performed now.");
} catch (NoSuchAlgorithmException ex) {
// We're not disclosing internal error details to our clients.
throw new GoogleAuthenticatorException("The operation cannot be "
+ "performed now.");
}
}
}
GoogleAuthÆäËû´úÂë ¿´ÕâÀï
ÒÔÉϾÍÊDZ¾ÎĵÄÈ«²¿ÄÚÈÝ£¬Ï£Íû¶Ô´ó¼ÒµÄѧϰÓÐËù°ïÖú£¬Ò²Ï£Íû´ó¼Ò¶à¶àÖ§³Ö½Å±¾Ö®¼Ò¡£