forked from rongchao/epmet-cloud-rizhao
14 changed files with 462 additions and 6 deletions
@ -0,0 +1,26 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<parent> |
||||
|
<artifactId>epmet-commons</artifactId> |
||||
|
<groupId>com.epmet</groupId> |
||||
|
<version>2.0.0</version> |
||||
|
</parent> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
|
||||
|
<artifactId>epmet-commons-security</artifactId> |
||||
|
|
||||
|
<dependencies> |
||||
|
<dependency> |
||||
|
<groupId>com.epmet</groupId> |
||||
|
<artifactId>epmet-commons-tools</artifactId> |
||||
|
<version>2.0.0</version> |
||||
|
</dependency> |
||||
|
<dependency> |
||||
|
<groupId>io.jsonwebtoken</groupId> |
||||
|
<artifactId>jjwt</artifactId> |
||||
|
<version>0.7.0</version> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
</project> |
@ -0,0 +1,112 @@ |
|||||
|
/** |
||||
|
* Copyright (c) 2018 人人开源 All rights reserved. |
||||
|
* <p> |
||||
|
* https://www.renren.io
|
||||
|
* <p> |
||||
|
* 版权所有,侵权必究! |
||||
|
*/ |
||||
|
|
||||
|
package com.epmet.commons.security.jwt; |
||||
|
|
||||
|
import io.jsonwebtoken.Claims; |
||||
|
import io.jsonwebtoken.Jwts; |
||||
|
import io.jsonwebtoken.SignatureAlgorithm; |
||||
|
import org.joda.time.DateTime; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* Jwt工具类 |
||||
|
* |
||||
|
* @author Mark sunlightcs@gmail.com |
||||
|
* @since 1.0.0 |
||||
|
*/ |
||||
|
@Component |
||||
|
public class JwtTokenUtils { |
||||
|
private static final Logger logger = LoggerFactory.getLogger(JwtTokenUtils.class); |
||||
|
|
||||
|
public Claims getClaimByToken(String token, String secret) { |
||||
|
try { |
||||
|
return Jwts.parser() |
||||
|
.setSigningKey(secret) |
||||
|
.parseClaimsJws(token) |
||||
|
.getBody(); |
||||
|
} catch (Exception e) { |
||||
|
logger.debug("validate is token error, token = " + token, e); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return java.util.Date |
||||
|
* @param token |
||||
|
* @Author yinzuomei |
||||
|
* @Description 获取token的有效期截止时间 |
||||
|
* @Date 2020/3/18 22:17 |
||||
|
**/ |
||||
|
public Date getExpiration(String token, String secret){ |
||||
|
try { |
||||
|
return Jwts.parser() |
||||
|
.setSigningKey(secret) |
||||
|
.parseClaimsJws(token) |
||||
|
.getBody().getExpiration(); |
||||
|
} catch (Exception e) { |
||||
|
logger.debug("validate is token error, token = " + token, e); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @return java.lang.String |
||||
|
* @Author yinzuomei |
||||
|
* @Description 根据app+client+userId生成token |
||||
|
* @Date 2020/3/18 22:29 |
||||
|
**/ |
||||
|
public String createToken(Map<String, Object> claims, int expire, String secret) { |
||||
|
return Jwts.builder() |
||||
|
.setHeaderParam("typ", "JWT") |
||||
|
.setClaims(claims) |
||||
|
.setIssuedAt(new Date()) |
||||
|
.setExpiration(DateTime.now().plusSeconds(expire).toDate()) |
||||
|
.signWith(SignatureAlgorithm.HS512, secret) |
||||
|
.compact(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* token是否过期 |
||||
|
* |
||||
|
* @return true:过期 |
||||
|
*/ |
||||
|
public boolean isTokenExpired(Date expiration) { |
||||
|
return expiration.before(new Date()); |
||||
|
} |
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
Map<String, Object> map=new HashMap<>(); |
||||
|
map.put("app","gov"); |
||||
|
map.put("client","wxmp"); |
||||
|
map.put("userId","100526ABC"); |
||||
|
String tokenStr=Jwts.builder() |
||||
|
.setHeaderParam("typ", "JWT") |
||||
|
.setClaims(map) |
||||
|
.setIssuedAt(new Date()) |
||||
|
.setExpiration(DateTime.now().plusSeconds(604800).toDate()) |
||||
|
.signWith(SignatureAlgorithm.HS512, "7016867071f0ebf1c46f123eaaf4b9d6[elink.epmet]") |
||||
|
.compact(); |
||||
|
System.out.println(tokenStr); |
||||
|
Claims claims= Jwts.parser() |
||||
|
.setSigningKey("7016867071f0ebf1c46f123eaaf4b9d6[elink.epmet]") |
||||
|
.parseClaimsJws(tokenStr) |
||||
|
.getBody(); |
||||
|
System.out.println("app="+ claims.get("app")); |
||||
|
System.out.println("client="+ claims.get("client")); |
||||
|
System.out.println("userId="+ claims.get("userId")); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,76 @@ |
|||||
|
package com.epmet.commons.security.sign.openapi; |
||||
|
|
||||
|
import com.epmet.commons.tools.utils.Md5Util; |
||||
|
|
||||
|
import java.util.Arrays; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
import java.util.Set; |
||||
|
|
||||
|
/** |
||||
|
* OpenApi签名工具 |
||||
|
*/ |
||||
|
public class OpenApiSignUtils { |
||||
|
|
||||
|
/** |
||||
|
* @Description 创建签名 |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.22 16:47 |
||||
|
*/ |
||||
|
public static String createSign(Map<String, String> contentMap, String signKey) { |
||||
|
String str2beSigned = mapToSignStr(contentMap); |
||||
|
str2beSigned = str2beSigned.concat("&signKey=").concat(signKey); |
||||
|
return Md5Util.md5(str2beSigned); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @Description 验签 |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.22 16:51 |
||||
|
*/ |
||||
|
public static boolean checkSign(Map<String, String> contentMap, String signKey) { |
||||
|
String newSign = createSign(contentMap, signKey); |
||||
|
return newSign.equals(contentMap.get("sign")); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* @Description map转化为签名明文 |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.22 16:47 |
||||
|
*/ |
||||
|
public static String mapToSignStr(Map<String, String> map) { |
||||
|
Set<String> keySet = map.keySet(); |
||||
|
String[] keyArray = (String[])keySet.toArray(new String[keySet.size()]); |
||||
|
Arrays.sort(keyArray); |
||||
|
StringBuilder sb = new StringBuilder(); |
||||
|
|
||||
|
for(int i = 0; i < keyArray.length; ++i) { |
||||
|
String key = keyArray[i]; |
||||
|
String val = (String)map.get(key); |
||||
|
if (val != null && val.trim().length() > 0 && !"sign".equals(key)) { |
||||
|
if (!sb.toString().isEmpty()) { |
||||
|
sb.append("&"); |
||||
|
} |
||||
|
|
||||
|
sb.append(key).append("=").append((String)map.get(key)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return sb.toString(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
public static void main(String[] args) { |
||||
|
|
||||
|
HashMap<String, String> content = new HashMap<>(); |
||||
|
content.put("appId", "7d98b8af2d05752b4225709c4cfd4bd0"); |
||||
|
|
||||
|
String secret = "3209ee9f41704482be1a1fb5873a25376f2899191ca846119d44168316bc3e44"; |
||||
|
|
||||
|
String sign = createSign(content, secret); |
||||
|
System.out.println(sign); |
||||
|
} |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
package com.epmet.dto.form; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
|
||||
|
import javax.validation.constraints.NotBlank; |
||||
|
|
||||
|
@Data |
||||
|
public class AccessTokenFormDTO { |
||||
|
|
||||
|
public interface GetAccessTokenGroup {} |
||||
|
|
||||
|
// 签名字符串密文
|
||||
|
@NotBlank(message = "签名字段不能为空", groups = { GetAccessTokenGroup.class }) |
||||
|
private String sign; |
||||
|
|
||||
|
// 应用id
|
||||
|
@NotBlank(message = "AppId字段不能为空", groups = { GetAccessTokenGroup.class }) |
||||
|
private String appId; |
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
package com.epmet.config; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.context.annotation.Configuration; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
@Component |
||||
|
@Data |
||||
|
public class OpenApiConfig { |
||||
|
|
||||
|
@Value("${openApi.accessToken.expire}") |
||||
|
private int accessTokenExpire; |
||||
|
|
||||
|
} |
@ -0,0 +1,88 @@ |
|||||
|
package com.epmet.controller; |
||||
|
|
||||
|
import com.epmet.commons.security.sign.openapi.OpenApiSignUtils; |
||||
|
import com.epmet.commons.tools.exception.EpmetErrorCode; |
||||
|
import com.epmet.commons.tools.exception.ExceptionUtils; |
||||
|
import com.epmet.commons.tools.exception.RenException; |
||||
|
import com.epmet.commons.tools.redis.RedisKeys; |
||||
|
import com.epmet.commons.tools.redis.RedisUtils; |
||||
|
import com.epmet.commons.tools.utils.ConvertUtils; |
||||
|
import com.epmet.commons.tools.utils.Result; |
||||
|
import com.epmet.commons.tools.validator.ValidatorUtils; |
||||
|
import com.epmet.dto.form.AccessTokenFormDTO; |
||||
|
import com.epmet.feign.EpmetCommonServiceOpenFeignClient; |
||||
|
import com.epmet.service.OpenApiAccessTokenService; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.slf4j.Logger; |
||||
|
import org.slf4j.LoggerFactory; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.web.bind.annotation.PostMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
|
||||
|
import java.beans.IntrospectionException; |
||||
|
import java.lang.reflect.InvocationTargetException; |
||||
|
|
||||
|
@RestController |
||||
|
@RequestMapping("open-api") |
||||
|
public class OpenApiAccessTokenController { |
||||
|
|
||||
|
@Autowired |
||||
|
private OpenApiAccessTokenService openApiAccessTokenService; |
||||
|
|
||||
|
@Autowired |
||||
|
private EpmetCommonServiceOpenFeignClient commonServiceOpenFeignClient; |
||||
|
|
||||
|
@Autowired |
||||
|
private RedisUtils redisUtils; |
||||
|
|
||||
|
private Logger logger = LoggerFactory.getLogger(OpenApiAccessTokenController.class); |
||||
|
|
||||
|
/** |
||||
|
* @Description 获取AccessToken |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.23 09:52 |
||||
|
*/ |
||||
|
@PostMapping("get-access-token") |
||||
|
public Result<String> getAccessToken(@RequestBody AccessTokenFormDTO input) { |
||||
|
// 1.校验参数
|
||||
|
ValidatorUtils.validateEntity(input); |
||||
|
String appId = input.getAppId(); |
||||
|
|
||||
|
// 2.取secret
|
||||
|
String secret = (String)redisUtils.get(RedisKeys.getExternalAppSecretKey(appId)); |
||||
|
if (StringUtils.isBlank(secret)) { |
||||
|
Result<String> result = commonServiceOpenFeignClient.getSecret(appId); |
||||
|
if (!result.success()) { |
||||
|
throw new RenException("调用common service查询secret失败"); |
||||
|
} |
||||
|
secret = result.getData(); |
||||
|
if (StringUtils.isBlank(secret)) { |
||||
|
throw new RenException(String.format("根据appId%s没有找到对应的secret", appId)); |
||||
|
} |
||||
|
redisUtils.set(RedisKeys.getExternalAppSecretKey(appId), secret); |
||||
|
} |
||||
|
|
||||
|
// 3.验签
|
||||
|
try { |
||||
|
if (!OpenApiSignUtils.checkSign(ConvertUtils.entityToMap(input), secret)) { |
||||
|
throw new RenException(EpmetErrorCode.OPEN_API_SIGN_ERROR.getCode(), EpmetErrorCode.OPEN_API_SIGN_ERROR.getMsg()); |
||||
|
} |
||||
|
} catch (RenException e) { |
||||
|
// 如果是自己抛出的异常则继续抛出
|
||||
|
throw e; |
||||
|
} catch (Exception e) { |
||||
|
// 是其他意外发生的异常
|
||||
|
String errorStackTrace = ExceptionUtils.getErrorStackTrace(e); |
||||
|
logger.error("验签发生未知异常:{}", errorStackTrace); |
||||
|
throw new RenException("验签发生未知异常,请查看ext服务详细日志"); |
||||
|
} |
||||
|
|
||||
|
//4.生成token
|
||||
|
String accessToken = openApiAccessTokenService.getAccessToken(appId, secret); |
||||
|
return new Result<String>().ok(accessToken); |
||||
|
} |
||||
|
|
||||
|
} |
@ -0,0 +1,15 @@ |
|||||
|
package com.epmet.service; |
||||
|
|
||||
|
/** |
||||
|
* access token的service |
||||
|
*/ |
||||
|
public interface OpenApiAccessTokenService { |
||||
|
|
||||
|
/** |
||||
|
* @Description 获取AccessToken |
||||
|
* @return |
||||
|
* @author wxz |
||||
|
* @date 2021.03.22 22:57 |
||||
|
*/ |
||||
|
String getAccessToken(String appId, String secret); |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
package com.epmet.service.impl; |
||||
|
|
||||
|
import com.epmet.commons.security.jwt.JwtTokenUtils; |
||||
|
import com.epmet.commons.tools.exception.EpmetErrorCode; |
||||
|
import com.epmet.commons.tools.exception.RenException; |
||||
|
import com.epmet.commons.tools.redis.RedisKeys; |
||||
|
import com.epmet.commons.tools.redis.RedisUtils; |
||||
|
import com.epmet.commons.tools.utils.Result; |
||||
|
import com.epmet.commons.tools.utils.SpringContextUtils; |
||||
|
import com.epmet.config.OpenApiConfig; |
||||
|
import com.epmet.feign.EpmetCommonServiceOpenFeignClient; |
||||
|
import com.epmet.service.OpenApiAccessTokenService; |
||||
|
import org.apache.commons.lang3.StringUtils; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.data.redis.core.RedisTemplate; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
|
||||
|
@Service |
||||
|
public class OpenApiAccessTokenServiceImpl implements OpenApiAccessTokenService { |
||||
|
|
||||
|
@Autowired |
||||
|
private JwtTokenUtils jwtTokenUtils; |
||||
|
|
||||
|
@Autowired |
||||
|
private OpenApiConfig openApiConfig; |
||||
|
|
||||
|
@Autowired |
||||
|
private RedisUtils redisUtils; |
||||
|
|
||||
|
@Override |
||||
|
public String getAccessToken(String appId, String secret) { |
||||
|
HashMap<String, Object> claim = new HashMap<>(); |
||||
|
claim.put("appId", appId); |
||||
|
|
||||
|
String token = jwtTokenUtils.createToken(claim, openApiConfig.getAccessTokenExpire(), secret); |
||||
|
|
||||
|
// 缓存token
|
||||
|
redisUtils.set(RedisKeys.getOpenApiAccessTokenKey(appId), token, openApiConfig.getAccessTokenExpire()); |
||||
|
|
||||
|
return token; |
||||
|
} |
||||
|
} |
Loading…
Reference in new issue