公司有个合作伙伴替用户下单的需求,下单完毕需要将单据信息分享给客户看,让客户付款。由于只是一些简单的单据信息,并且没有权限校验,综合考虑安全与性能,决定使用JWT的方式实现。
JWT简介
JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。具体概念自行搜索
官网介绍如下:
https://jwt.io/introduction
主要代码实现
JWT了解原理之后可以自己实现,不过现在有很多开源的JWT库,官网推荐如下:https://jwt.io/libraries 本文用的是jose4j ,如下:https://bitbucket.org/b_c/jose4j/wiki/Home
引入jar包:
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.7.9</version>
</dependency>
这里使用枚举实现单例创建一个密钥对象,这里是我写的练练手,可以不需要,直接本地运行生成key对象直接保存
public enum SingletonRsaJsonWebKeyEnum {
//创建一个枚举对象,该对象天生为单例
INSTANCE;
private RsaJsonWebKey generateJwk() {
RsaJsonWebKey rsaJsonWebKey = null;
try {
rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048);
rsaJsonWebKey.setKeyId("www_key_customer_pay");
} catch (JoseException e) {
e.printStackTrace();
}
return rsaJsonWebKey;
}
public String getRsaJsonWebKeyJsonString() {
String keyString = "{\"kty\":\"RSA\",\"kid\":\"www_key_customer_pay\",\"n\":\"lZJmKh4hM5_B0Cw2zUajuXhi6g-ZiWoNKAYIkC4upx8B9PtE541lwu6qYYoS_gP5K1tOTgJodwK95j8kfmq2Nevr8hr9FuROic0xftOJTJrkwsun22X73d5IhvG01t0NB-S_WmYF-JRcmJ6zQD1nnFgMTHn3zhEJrxByrsad-yGb0cdzFX8R4Ae383c8dL2VWyJRTOMAqW_fuR3TpSdFd-fYmjadmLIdodsWPchR88ZeEURYLjPgYAVbtBmfe8npIWHNKyPgnuy0JWdR7FQQFXvU5c-f4wccErB_o5oGHt4Nm8-S5cC3T0yp9e1CKaSiUOTCvwFfq5cX4Rf8WDzNyQ\",\"e\":\"AQAB\",\"d\":\"VvOTxvpbDNrb3jxF45IkTgcpYa6N8G-hlFnlkoP2hRsmlReZ2A7AUNFUZX3SSq9epBDhdcI6nq0OXpLokUFSCgjL0qRT64HwEnYiRvuMvubxBDlGrOodwL6fZSuQmLRLBgK6J0BWSktdhTAFPtwzppUdGTxyje4jtg0WznoSj1H3Gx6XN98_Nut2r5hXqP1f1nsdsE1dCaTYIJnf4kTpRBoEMjLQ2qoFr77e-wSmIrc4HFOaVzJCN4NQZjx_Vi3CdV33QX3qWEypfihEC25yGH4k7MLaP7ZSzqwv2VcvB5O7DAImYrJwQSSofiNE5yXqL6g8_0zYe91ardZngy6KzQ\",\"p\":\"1UWnxrs4FdLzfi2060zfXKiEDqAh2-XLaEtlT3fUOe3Acxb0PTzRmQICwttd1pqAvqwljdapbkOHIBiGSOeQWJQEsCVxymaUVOtWxWImZmREJh8L2KoPfUIHENOmx76FmczOtTpQi4cvUnzVy45iIshC28zePTYX7l7UdxHufmM\",\"q\":\"s4msVx1gdrYiCVv58db3jfXaKYWDYI8-8Fp_KdyKCFPmayYYeWl9wUEouywLqguwKKyRo7h2lKMEcgmQp0DniZVZpFpzxBu9rkmpbsS2xHL2FDt5cueNS_xgvVS3okmrW6rz6_ufBnjXKSGRMn8ZqZs6jDDdAZ-fD9iuOmCBFOM\",\"dp\":\"JnmFdf2qdY1z7exy-gwJM58XC8hps1D2bB9F7JsyhyzUDi8y6qVBLrhFJUAL4r5GwZ8uuzLhqAm4o9qoxxg3WzOA0QJAUAJHejZmlf9J7CjkfngVXAX5-1_hBHEaYmiFk6R2Gg2mIDXIHLp2m11ZaOr13M9NvH84vAERlVV_z_8\",\"dq\":\"C9ncnGt-AJgJKyD9IK-V67L80ZzUjT0nZGo01qiOG_qdzRjYqLsD6AvLCn_fzTu0RxsNCgeVHD6efoCPIdsw7W4EWsr1tU43eRe3rW4iulj0UWWToLgUJZ3lLNo4vOer_gMM0tjrKFw1p6tlkDW_leh_Nt3K2N6We09MQOI54w0\",\"qi\":\"g66xqKV_Ci2gVgylelYk7FbypqhRsUDBrbqBZUaEK0nIW3HZGsqKB1g55vO60R4_lhftFTwrkabBtGipt6fRk3GPywj1usp3AVtR0uvBNPHGAiTYJ0hEd98uiwMQ2MteZuj3Iy-RIs9BHlN2QJVTgfZ_ulgO_viMQlfQNxrBfcA\"}\n"
;
// if (keyString == null) {
// keyString = SingletonRsaJsonWebKeyEnum.INSTANCE.generateJwk().toJson(RsaJsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);
// }
return keyString;
}
// public static void main(String[] args) {
//
// String keyString = SingletonRsaJsonWebKeyEnum.INSTANCE.generateJwk().toJson(RsaJsonWebKey.OutputControlLevel.INCLUDE_PRIVATE);
// RsaJsonWebKey rsaJsonWebKey = new RsaJsonWebKey(JsonUtil.parseJson(SingletonRsaJsonWebKeyEnum.INSTANCE.getRsaJsonWebKeyJsonString()));
//
// }
}
工具类:
@Slf4j
public class JwtUtils {
public static RsaJsonWebKey rsaJsonWebKey;
static {
try {
rsaJsonWebKey = new RsaJsonWebKey(JsonUtil.parseJson(SingletonRsaJsonWebKeyEnum.INSTANCE.getRsaJsonWebKeyJsonString()));
} catch (JoseException e) {
e.printStackTrace();
}
}
private JwtUtils() {
}
public static String createToken(Object jsonString) throws JoseException {
//
// JSON Web Token is a compact URL-safe means of representing claims/attributes to be transferred between two parties.
// This example demonstrates producing and consuming a signed JWT
//
// RsaJsonWebKey rsaJsonWebKey = SingletonRsaJsonWebKeyEnum.generateJwk();
// Give the JWK a Key ID (kid), which is just the polite thing to do
// Create the Claims, which will be the content of the JWT
JwtClaims claims = new JwtClaims();
claims.setIssuer("WWW"); // who creates the token and signs it
claims.setAudience("WWW_CUSTOMER"); // to whom the token is intended to be sent
claims.setExpirationTimeMinutesInTheFuture(48 * 60); // time when the token will expire ( minutes from now)
claims.setGeneratedJwtId(); // a unique identifier for the token
claims.setIssuedAtToNow(); // when the token was issued/created (now)
claims.setNotBeforeMinutesInThePast(2); // time before which the token is not yet valid (2 minutes ago)
claims.setSubject("测试Token"); // the subject/principal is whom the token is about
claims.setClaim("data", jsonString); // additional claims/attributes about the subject can be added
// List<String> groups = Arrays.asList("group-one", "other-group", "group-three");
// claims.setStringListClaim("groups", groups); // multi-valued claims work too and will end up as a JSON array
// A JWT is a JWS and/or a JWE with JSON claims as the payload.
// In this example it is a JWS so we create a JsonWebSignature object.
JsonWebSignature jws = new JsonWebSignature();
// The payload of the JWS is JSON content of the JWT Claims
jws.setPayload(claims.toJson());
// The JWT is signed using the private key
jws.setKey(rsaJsonWebKey.getPrivateKey());
// Set the Key ID (kid) header because it's just the polite thing to do.
// We only have one key in this example but a using a Key ID helps
// facilitate a smooth key rollover process
jws.setKeyIdHeaderValue(rsaJsonWebKey.getKeyId());
// Set the signature algorithm on the JWT/JWS that will integrity protect the claims
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
// Sign the JWS and produce the compact serialization or the complete JWT/JWS
// representation, which is a string consisting of three dot ('.') separated
// base64url-encoded parts in the form Header.Payload.Signature
// If you wanted to encrypt it, you can simply set this jwt as the payload
// of a JsonWebEncryption object and set the cty (Content Type) header to "jwt".
String jwt = jws.getCompactSerialization();
// Now you can do something with the JWT. Like send it to some other party
// over the clouds and through the interwebs.
return jwt;
}
public static JwtClaims validateToken(String jwt) {
// RsaJsonWebKey rsaJsonWebKey = SingletonRsaJsonWebKeyEnum.generateJwk();
// Use JwtConsumerBuilder to construct an appropriate JwtConsumer, which will
// be used to validate and process the JWT.
// The specific validation requirements for a JWT are context dependent, however,
// it typically advisable to require a (reasonable) expiration time, a trusted issuer, and
// and audience that identifies your system as the intended recipient.
// If the JWT is encrypted too, you need only provide a decryption key or
// decryption key resolver to the builder.
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setRequireExpirationTime() // the JWT must have an expiration time
.setAllowedClockSkewInSeconds(30) // allow some leeway in validating time based claims to account for clock skew
.setRequireSubject() // the JWT must have a subject claim
.setExpectedIssuer("WWW") // whom the JWT needs to have been issued by
.setExpectedAudience("WWW_CUSTOMER") // to whom the JWT is intended for
.setVerificationKey(rsaJsonWebKey.getKey()) // verify the signature with the public key
.setJwsAlgorithmConstraints( // only allow the expected signature algorithm(s) in the given context
AlgorithmConstraints.ConstraintType.PERMIT, AlgorithmIdentifiers.RSA_USING_SHA256) // which is only RS256 here
.build(); // create the JwtConsumer instance
JwtClaims jwtClaims = null;
try {
// Validate the JWT and process it to the Claims
jwtClaims = jwtConsumer.processToClaims(jwt);
log.info("JWT validation succeeded! " + jwtClaims);
} catch (InvalidJwtException e) {
// InvalidJwtException will be thrown, if the JWT failed processing or validation in anyway.
// Hopefully with meaningful explanations(s) about what went wrong.
log.error("Invalid JWT! " + e);
// Programmatic access to (some) specific reasons for JWT invalidity is also possible
// should you want different error handling behavior for certain conditions.
// Whether or not the JWT has expired being one common reason for invalidity
try {
if (e.hasExpired()) {
log.error("JWT expired at " + e.getJwtContext().getJwtClaims().getExpirationTime());
}
// Or maybe the audience was invalid
if (e.hasErrorCode(ErrorCodes.AUDIENCE_INVALID)) {
log.error("JWT had wrong audience: " + e.getJwtContext().getJwtClaims().getAudience());
}
}catch (MalformedClaimException e1){
log.error("验证客户支付token异常",e1);
}
throw new MallBusinessException(StatusEnum.CUSTOMER_PAY_TOKEN_AUTHENTICATION_ERROR);
}
return jwtClaims;
}
public static void main(String[] args) throws Exception {
String json = "{\"name\":\"小二\",\"age\":\"18\"}";
String jwt = createToken(json);
// String jwt = "";
JwtClaims result = validateToken(jwt);
System.out.println("开始:" + jwt);
System.out.println("结果:" + result.getClaimValue("data"));
System.out.println("结果:" + result);
System.out.println("结果:" + result.getExpirationTime());
}
}
### 测试结果
开始:eyJraWQiOiJ5empfa2V5X2N1c3RvbWVyX3BheSIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJXV1ciLCJhdWQiOiJXV1dfQ1VTVE9NRVIiLCJleHAiOjE2MzQ5Nzk2ODIsImp0aSI6ImVMVXZpYU8wUWUzR2paRy13LVlGc1EiLCJpYXQiOjE2MzQ4MDY4ODMsIm5iZiI6MTYzNDgwNjc2Mywic3ViIjoi5rWL6K-VVG9rZW4iLCJkYXRhIjoie1wibmFtZVwiOlwi5bCP5LqMXCIsXCJhZ2VcIjpcIjE4XCJ9In0.NOyEWUpFZteA5z44oUZ0_EsvW1bN73Uk4S8OqFWKMUOOfgM1t68xoDjiEnhPUnORLEdCPQBK5B_F-qiPu8uaNfS23B2Xu1hNwS_2Nig5TUO9R_izZY-ECGjQW8G7u-8dJTfGkgpnHTC3IEzdhc_pnlNxWCtUfiwrDnuk2Kzy1eXNjZDevYE0dkRaTh6s-5J58hyS3MshgLPZ2YSJpjWYZgSJObNzoypjVgasLFyx1Jx45Sb4YWEuKi888xzHQZNdksYigCDubsyy-LJ7b75wx5XtdWKto0Xk_s8Cby38zV64VxTS51zQIxcNay6VMgv3qn0TkcbQNYZg6BSXfVlWFw
结果:{“name”:“小二”,“age”:“18”}
结果:JWT Claims Set:{iss=WWW, aud=WWW_CUSTOMER, exp=1634979682, jti=eLUviaO0Qe3GjZG-w-YFsQ, iat=1634806883, nbf=1634806763, sub=测试Token, data={“name”:“小二”,“age”:“18”}}
结果:NumericDate{1634979682 -> 2021-10-23 下午05时01分22秒}