一、需求背景
在使用的token的场景中有需要使用签名来生产token的场景,这主要是为了解决token唯一性和可靠性的问题。现在来看看怎么生成签名。
实现要求:
1、签名就要唯一性。
2、签名具有可校验性。
3、签名不可篡改。
二、实现源码赏析:
1、我们先生成一个接口来定义签名的类型。
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.crypto;
public interface JwtSigner {
String sign(String jwtWithoutSignature);
}
2、我们实现一下上面的接口JwtSigner。
主要思路就是一些编码和算法的导入。从源码上看,构造方式就有4种。
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl.crypto;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.lang.Assert;
import java.nio.charset.Charset;
import java.security.Key;
public class DefaultJwtSigner implements JwtSigner {
private static final Charset US_ASCII = Charset.forName("US-ASCII");
private final Signer signer;
private final Encoder<byte[], String> base64UrlEncoder;
@Deprecated
public DefaultJwtSigner(SignatureAlgorithm alg, Key key) {
this(DefaultSignerFactory.INSTANCE, alg, key, Encoders.BASE64URL);
}
public DefaultJwtSigner(SignatureAlgorithm alg, Key key, Encoder<byte[], String> base64UrlEncoder) {
this(DefaultSignerFactory.INSTANCE, alg, key, base64UrlEncoder);
}
@Deprecated
public DefaultJwtSigner(SignerFactory factory, SignatureAlgorithm alg, Key key) {
this(factory, alg, key, Encoders.BASE64URL);
}
public DefaultJwtSigner(SignerFactory factory, SignatureAlgorithm alg, Key key, Encoder<byte[], String> base64UrlEncoder) {
Assert.notNull(factory, "SignerFactory argument cannot be null.");
Assert.notNull(base64UrlEncoder, "Base64Url Encoder cannot be null.");
this.base64UrlEncoder = base64UrlEncoder;
this.signer = factory.createSigner(alg, key);
}
@Override
public String sign(String jwtWithoutSignature) {
byte[] bytesToSign = jwtWithoutSignature.getBytes(US_ASCII);
byte[] signature = signer.sign(bytesToSign);
return base64UrlEncoder.encode(signature);
}
}
3.如何使用。
调用方式如下:
JwtSigner signer = createSigner(algorithm, key);
/*
* Copyright (C) 2014 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodec;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.crypto.DefaultJwtSigner;
import io.jsonwebtoken.impl.crypto.JwtSigner;
import io.jsonwebtoken.impl.lang.LegacyServices;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoder;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.io.SerializationException;
import io.jsonwebtoken.io.Serializer;
import io.jsonwebtoken.lang.Assert;
import io.jsonwebtoken.lang.Collections;
import io.jsonwebtoken.lang.Strings;
import io.jsonwebtoken.security.InvalidKeyException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Date;
import java.util.Map;
public class DefaultJwtBuilder implements JwtBuilder {
private Header header;
private Claims claims;
private String payload;
private SignatureAlgorithm algorithm;
private Key key;
private Serializer<Map<String,?>> serializer;
private Encoder<byte[], String> base64UrlEncoder = Encoders.BASE64URL;
private CompressionCodec compressionCodec;
@Override
public JwtBuilder serializeToJsonWith(Serializer<Map<String,?>> serializer) {
Assert.notNull(serializer, "Serializer cannot be null.");
this.serializer = serializer;
return this;
}
@Override
public JwtBuilder base64UrlEncodeWith(Encoder<byte[], String> base64UrlEncoder) {
Assert.notNull(base64UrlEncoder, "base64UrlEncoder cannot be null.");
this.base64UrlEncoder = base64UrlEncoder;
return this;
}
@Override
public JwtBuilder setHeader(Header header) {
this.header = header;
return this;
}
@Override
public JwtBuilder setHeader(Map<String, Object> header) {
this.header = new DefaultHeader(header);
return this;
}
@Override
public JwtBuilder setHeaderParams(Map<String, Object> params) {
if (!Collections.isEmpty(params)) {
Header header = ensureHeader();
for (Map.Entry<String, Object> entry : params.entrySet()) {
header.put(entry.getKey(), entry.getValue());
}
}
return this;
}
protected Header ensureHeader() {
if (this.header == null) {
this.header = new DefaultHeader();
}
return this.header;
}
@Override
public JwtBuilder setHeaderParam(String name, Object value) {
ensureHeader().put(name, value);
return this;
}
@Override
public JwtBuilder signWith(Key key) throws InvalidKeyException {
Assert.notNull(key, "Key argument cannot be null.");
SignatureAlgorithm alg = SignatureAlgorithm.forSigningKey(key);
return signWith(key, alg);
}
@Override
public JwtBuilder signWith(Key key, SignatureAlgorithm alg) throws InvalidKeyException {
Assert.notNull(key, "Key argument cannot be null.");
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
alg.assertValidSigningKey(key); //since 0.10.0 for https://github.com/jwtk/jjwt/issues/334
createSigner(alg, key); // since 0.11.5: fail fast if key cannot be used for alg.
this.algorithm = alg;
this.key = key;
return this;
}
@Override
public JwtBuilder signWith(SignatureAlgorithm alg, byte[] secretKeyBytes) throws InvalidKeyException {
Assert.notNull(alg, "SignatureAlgorithm cannot be null.");
Assert.notEmpty(secretKeyBytes, "secret key byte array cannot be null or empty.");
Assert.isTrue(alg.isHmac(), "Key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.");
SecretKey key = new SecretKeySpec(secretKeyBytes, alg.getJcaName());
return signWith(key, alg);
}
@Override
public JwtBuilder signWith(SignatureAlgorithm alg, String base64EncodedSecretKey) throws InvalidKeyException {
Assert.hasText(base64EncodedSecretKey, "base64-encoded secret key cannot be null or empty.");
Assert.isTrue(alg.isHmac(), "Base64-encoded key bytes may only be specified for HMAC signatures. If using RSA or Elliptic Curve, use the signWith(SignatureAlgorithm, Key) method instead.");
byte[] bytes = Decoders.BASE64.decode(base64EncodedSecretKey);
return signWith(alg, bytes);
}
@Override
public JwtBuilder signWith(SignatureAlgorithm alg, Key key) {
return signWith(key, alg);
}
@Override
public JwtBuilder compressWith(CompressionCodec compressionCodec) {
Assert.notNull(compressionCodec, "compressionCodec cannot be null");
this.compressionCodec = compressionCodec;
return this;
}
@Override
public JwtBuilder setPayload(String payload) {
this.payload = payload;
return this;
}
protected Claims ensureClaims() {
if (this.claims == null) {
this.claims = new DefaultClaims();
}
return this.claims;
}
@Override
public JwtBuilder setClaims(Claims claims) {
this.claims = claims;
return this;
}
@Override
public JwtBuilder setClaims(Map<String, ?> claims) {
this.claims = new DefaultClaims(claims);
return this;
}
@Override
public JwtBuilder addClaims(Map<String, Object> claims) {
ensureClaims().putAll(claims);
return this;
}
@Override
public JwtBuilder setIssuer(String iss) {
if (Strings.hasText(iss)) {
ensureClaims().setIssuer(iss);
} else {
if (this.claims != null) {
claims.setIssuer(iss);
}
}
return this;
}
@Override
public JwtBuilder setSubject(String sub) {
if (Strings.hasText(sub)) {
ensureClaims().setSubject(sub);
} else {
if (this.claims != null) {
claims.setSubject(sub);
}
}
return this;
}
@Override
public JwtBuilder setAudience(String aud) {
if (Strings.hasText(aud)) {
ensureClaims().setAudience(aud);
} else {
if (this.claims != null) {
claims.setAudience(aud);
}
}
return this;
}
@Override
public JwtBuilder setExpiration(Date exp) {
if (exp != null) {
ensureClaims().setExpiration(exp);
} else {
if (this.claims != null) {
//noinspection ConstantConditions
this.claims.setExpiration(exp);
}
}
return this;
}
@Override
public JwtBuilder setNotBefore(Date nbf) {
if (nbf != null) {
ensureClaims().setNotBefore(nbf);
} else {
if (this.claims != null) {
//noinspection ConstantConditions
this.claims.setNotBefore(nbf);
}
}
return this;
}
@Override
public JwtBuilder setIssuedAt(Date iat) {
if (iat != null) {
ensureClaims().setIssuedAt(iat);
} else {
if (this.claims != null) {
//noinspection ConstantConditions
this.claims.setIssuedAt(iat);
}
}
return this;
}
@Override
public JwtBuilder setId(String jti) {
if (Strings.hasText(jti)) {
ensureClaims().setId(jti);
} else {
if (this.claims != null) {
claims.setId(jti);
}
}
return this;
}
@Override
public JwtBuilder claim(String name, Object value) {
Assert.hasText(name, "Claim property name cannot be null or empty.");
if (this.claims == null) {
if (value != null) {
ensureClaims().put(name, value);
}
} else {
if (value == null) {
this.claims.remove(name);
} else {
this.claims.put(name, value);
}
}
return this;
}
@Override
public String compact() {
if (this.serializer == null) {
// try to find one based on the services available
// TODO: This util class will throw a UnavailableImplementationException here to retain behavior of previous version, remove in v1.0
// use the previous commented out line instead
this.serializer = LegacyServices.loadFirst(Serializer.class);
}
if (payload == null && Collections.isEmpty(claims)) {
payload = "";
}
if (payload != null && !Collections.isEmpty(claims)) {
throw new IllegalStateException("Both 'payload' and 'claims' cannot both be specified. Choose either one.");
}
Header header = ensureHeader();
JwsHeader jwsHeader;
if (header instanceof JwsHeader) {
jwsHeader = (JwsHeader) header;
} else {
//noinspection unchecked
jwsHeader = new DefaultJwsHeader(header);
}
if (key != null) {
jwsHeader.setAlgorithm(algorithm.getValue());
} else {
//no signature - plaintext JWT:
jwsHeader.setAlgorithm(SignatureAlgorithm.NONE.getValue());
}
if (compressionCodec != null) {
jwsHeader.setCompressionAlgorithm(compressionCodec.getAlgorithmName());
}
String base64UrlEncodedHeader = base64UrlEncode(jwsHeader, "Unable to serialize header to json.");
byte[] bytes;
try {
bytes = this.payload != null ? payload.getBytes(Strings.UTF_8) : toJson(claims);
} catch (SerializationException e) {
throw new IllegalArgumentException("Unable to serialize claims object to json: " + e.getMessage(), e);
}
if (compressionCodec != null) {
bytes = compressionCodec.compress(bytes);
}
String base64UrlEncodedBody = base64UrlEncoder.encode(bytes);
String jwt = base64UrlEncodedHeader + JwtParser.SEPARATOR_CHAR + base64UrlEncodedBody;
if (key != null) { //jwt must be signed:
JwtSigner signer = createSigner(algorithm, key);
String base64UrlSignature = signer.sign(jwt);
jwt += JwtParser.SEPARATOR_CHAR + base64UrlSignature;
} else {
// no signature (plaintext), but must terminate w/ a period, see
// https://tools.ietf.org/html/draft-ietf-oauth-json-web-token-25#section-6.1
jwt += JwtParser.SEPARATOR_CHAR;
}
return jwt;
}
/*
* @since 0.5 mostly to allow testing overrides
*/
protected JwtSigner createSigner(SignatureAlgorithm alg, Key key) {
return new DefaultJwtSigner(alg, key, base64UrlEncoder);
}
@Deprecated // remove before 1.0 - call the serializer and base64UrlEncoder directly
protected String base64UrlEncode(Object o, String errMsg) {
Assert.isInstanceOf(Map.class, o, "object argument must be a map.");
Map m = (Map)o;
byte[] bytes;
try {
bytes = toJson(m);
} catch (SerializationException e) {
throw new IllegalStateException(errMsg, e);
}
return base64UrlEncoder.encode(bytes);
}
@SuppressWarnings("unchecked")
@Deprecated //remove before 1.0 - call the serializer directly
protected byte[] toJson(Object object) throws SerializationException {
Assert.isInstanceOf(Map.class, object, "object argument must be a map.");
Map m = (Map)object;
return serializer.serialize(m);
}
}
java 对外系统接口设计原则
Java 对外系统接口设计原则主要包括以下几点:
- 明确性:接口的名称和方法应该清晰明确,能够准确反映其功能和用途。避免使用模糊或过于技术性的术语。
- 简洁性:接口设计应尽可能简洁,避免不必要的复杂性。每个接口都应有一个明确的目的,并尽量减少不必要的参数和方法。
- 一致性:接口的设计风格和规范应保持一致,以便使用者能够轻松理解和使用。这包括命名规范、参数传递方式、错误处理等。
- 安全性:接口设计时需要考虑安全性问题,包括数据加密、身份验证、访问控制等。确保只有授权的客户端可以访问接口,并防止未经授权的访问和数据泄露。
- 可扩展性:接口设计应考虑未来的扩展需求。当业务发生变化或需要添加新功能时,接口应能够灵活地适应这些变化。
- 稳定性:接口应具有稳定性,确保在各种情况下都能稳定运行,避免因外部因素导致接口异常或崩溃。
- 错误处理:接口应具备完善的错误处理机制,能够捕获和处理异常情况,并向调用者提供清晰的错误提示和解决方案。
- 文档化:为接口提供详细的文档说明,包括接口的用途、参数说明、返回值、异常处理等信息,以便使用者能够快速理解和使用。
- 版本控制:随着业务的发展和需求的变更,接口可能会进行升级或更改。应实施版本控制策略,以便在不影响现有使用者的前提下进行更新和升级。
- 测试与验证:对接口进行充分的测试和验证,确保其功能正常、性能良好、安全可靠。可以使用自动化测试工具进行测试,以确保接口在不同场景下的稳定性和可靠性。
遵循以上原则可以帮助设计出高质量、易于使用和维护的Java对外系统接口,提高系统的可维护性和可扩展性。