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