diff --git a/core/pom.xml b/core/pom.xml
index d11a791c042334c801d7d51aff32ca854c2a0fc5..64d01ee9e1dba34d17cbaf17bb2000afd41a9844 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -13,6 +13,7 @@
1.7.21
+ 0.9.0
@@ -20,6 +21,17 @@
slf4j-api
${slf4j.version}
+
+ io.jsonwebtoken
+ jjwt
+ ${jjwt.version}
+
+
+
+ javax.xml.bind
+ jaxb-api
+ 2.3.0
+
diff --git a/core/src/main/java/com/usthe/sureness/processor/BaseProcessor.java b/core/src/main/java/com/usthe/sureness/processor/BaseProcessor.java
index 016381e56919f4fe9b8057045750a71e7bac64ad..9eab66b0e81ed777aa9a98d27c0fb932836ce402 100644
--- a/core/src/main/java/com/usthe/sureness/processor/BaseProcessor.java
+++ b/core/src/main/java/com/usthe/sureness/processor/BaseProcessor.java
@@ -40,15 +40,17 @@ public abstract class BaseProcessor implements Processor{
* description 认证会调用的接口,在这里面完成认证
* @param var 1
* @return boolean
+ * @throws SurenessAuthenticationException when发生认证相关异常
*/
- public abstract boolean authenticated (SubjectAuToken var);
+ public abstract boolean authenticated (SubjectAuToken var) throws SurenessAuthenticationException;
/**
* description 鉴权会调用的接口,在这里面完成鉴权
* @param var 1
* @return boolean
+ * @throws SurenessAuthorizationException when发生鉴权相关异常
*/
- public abstract boolean authorized(SubjectAuToken var);
+ public abstract boolean authorized(SubjectAuToken var) throws SurenessAuthorizationException;
/**
* description 当认证鉴权完成通过后,通过token创建subject
diff --git a/core/src/main/java/com/usthe/sureness/processor/support/JwtProcessor.java b/core/src/main/java/com/usthe/sureness/processor/support/JwtProcessor.java
index b62cb47bbe211d51b67100ef7bbbb5a9ce3a7c02..9386be72e7dbf3c929a27bfc361841bff434cfd4 100644
--- a/core/src/main/java/com/usthe/sureness/processor/support/JwtProcessor.java
+++ b/core/src/main/java/com/usthe/sureness/processor/support/JwtProcessor.java
@@ -1,9 +1,23 @@
package com.usthe.sureness.processor.support;
import com.usthe.sureness.processor.BaseProcessor;
+import com.usthe.sureness.processor.exception.IncorrectCredentialsException;
+import com.usthe.sureness.processor.exception.SurenessAuthenticationException;
+import com.usthe.sureness.processor.exception.SurenessAuthorizationException;
import com.usthe.sureness.subject.Subject;
import com.usthe.sureness.subject.SubjectAuToken;
import com.usthe.sureness.subject.support.JwtSubjectToken;
+import com.usthe.sureness.util.JsonWebTokenUtil;
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.ExpiredJwtException;
+import io.jsonwebtoken.MalformedJwtException;
+import io.jsonwebtoken.SignatureException;
+import io.jsonwebtoken.UnsupportedJwtException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.List;
/**
* 支持 appId + jwt 的token的处理器实例
@@ -12,6 +26,8 @@ import com.usthe.sureness.subject.support.JwtSubjectToken;
*/
public class JwtProcessor extends BaseProcessor {
+ private static final Logger LOG = LoggerFactory.getLogger(JwtProcessor.class);
+
@Override
public boolean canSupportAuTokenClass(Class> var) {
return var != null && var == JwtSubjectToken.class;
@@ -23,13 +39,45 @@ public class JwtProcessor extends BaseProcessor {
}
@Override
- public boolean authenticated(SubjectAuToken var) {
- return false;
+ public boolean authenticated(SubjectAuToken var) throws SurenessAuthenticationException {
+ String jwt = (String) var.getCredentials();
+ if (jwt == null || "".equals(jwt)) {
+ throw new IncorrectCredentialsException("this jwt credential is null");
+ }
+ Claims claims = null;
+ try {
+ claims = JsonWebTokenUtil.parseJwt(jwt, JsonWebTokenUtil.SECRET_KEY);
+ } catch (SignatureException | UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
+ // JWT令牌错误
+ if (LOG.isInfoEnabled()) {
+ LOG.info("jwtProcessor authenticated fail, user: {}, jwt: {}",
+ var.getPrincipal(), jwt, e);
+ }
+ throw new IncorrectCredentialsException("this jwt error:" + e.getMessage());
+ } catch (ExpiredJwtException e) {
+ // JWT 令牌过期
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("jwtProcessor authenticated expired, user: {}, jwt: {}",
+ var.getPrincipal(), jwt, e);
+ }
+ }
+ if (claims != null) {
+ String roles = claims.get("roles", String.class);
+ if (roles != null) {
+ List roleList = Arrays.asList(roles.split(","));
+ var.setSupportRoles(roleList);
+ }
+ }
+ return true;
}
+ @SuppressWarnings("unchecked")
@Override
- public boolean authorized(SubjectAuToken var) {
- return false;
+ public boolean authorized(SubjectAuToken var) throws SurenessAuthorizationException {
+ List ownRoles = (List)var.getOwnRoles();
+ List supportRoles = (List)var.getSupportRoles();
+ return supportRoles == null || supportRoles.isEmpty()
+ || supportRoles.stream().anyMatch(role -> ownRoles.contains(role));
}
@Override
diff --git a/core/src/main/java/com/usthe/sureness/processor/support/PasswordProcessor.java b/core/src/main/java/com/usthe/sureness/processor/support/PasswordProcessor.java
index 6b85a21f9c4fe243169bf53db9240e9ddbef0f66..99f91ea600441c3d349df8cdfcea912e229c32a7 100644
--- a/core/src/main/java/com/usthe/sureness/processor/support/PasswordProcessor.java
+++ b/core/src/main/java/com/usthe/sureness/processor/support/PasswordProcessor.java
@@ -1,9 +1,16 @@
package com.usthe.sureness.processor.support;
import com.usthe.sureness.processor.BaseProcessor;
+import com.usthe.sureness.processor.exception.IncorrectCredentialsException;
+import com.usthe.sureness.processor.exception.SurenessAuthenticationException;
+import com.usthe.sureness.processor.exception.SurenessAuthorizationException;
+import com.usthe.sureness.processor.exception.UnknownAccountException;
+import com.usthe.sureness.provider.Account;
+import com.usthe.sureness.provider.AccountProvider;
import com.usthe.sureness.subject.Subject;
import com.usthe.sureness.subject.SubjectAuToken;
import com.usthe.sureness.subject.support.PasswordSubjectToken;
+import com.usthe.sureness.util.Md5Util;
/**
* 支持 username password 类型token的处理器实例
@@ -12,6 +19,9 @@ import com.usthe.sureness.subject.support.PasswordSubjectToken;
*/
public class PasswordProcessor extends BaseProcessor {
+
+ private AccountProvider accountProvider;
+
@Override
public boolean canSupportAuTokenClass(Class> var) {
return var != null && var == PasswordSubjectToken.class;
@@ -25,13 +35,22 @@ public class PasswordProcessor extends BaseProcessor {
}
@Override
- public boolean authenticated(SubjectAuToken var) {
- return false;
+ public boolean authenticated(SubjectAuToken var) throws SurenessAuthenticationException {
+ String appId = (String) var.getPrincipal();
+ Account account = accountProvider.loadAccount(appId);
+ if (account == null) {
+ throw new UnknownAccountException("no the account: " + appId);
+ }
+ String password = Md5Util.md5((String) var.getCredentials() + account.getSalt());
+ if (password == null || !password.equals(account.getPassword())) {
+ throw new IncorrectCredentialsException("incorrect password");
+ }
+ return true;
}
@Override
- public boolean authorized(SubjectAuToken var) {
- return false;
+ public boolean authorized(SubjectAuToken var) throws SurenessAuthorizationException {
+ return true;
}
@Override
diff --git a/core/src/main/java/com/usthe/sureness/provider/Account.java b/core/src/main/java/com/usthe/sureness/provider/Account.java
new file mode 100644
index 0000000000000000000000000000000000000000..c817fd378a4c3fbab4f24da915bc9fb5196d8a9e
--- /dev/null
+++ b/core/src/main/java/com/usthe/sureness/provider/Account.java
@@ -0,0 +1,18 @@
+package com.usthe.sureness.provider;
+
+
+/**
+ * @author tomsun28
+ * @date 23:18 2019-04-02
+ */
+public interface Account {
+
+
+ String getAppId();
+
+ String getPassword();
+
+ String getSalt();
+
+}
+
diff --git a/core/src/main/java/com/usthe/sureness/provider/AccountProvider.java b/core/src/main/java/com/usthe/sureness/provider/AccountProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..a0e7fc033f614a38ee48a157d0d8bba55f949c2c
--- /dev/null
+++ b/core/src/main/java/com/usthe/sureness/provider/AccountProvider.java
@@ -0,0 +1,11 @@
+package com.usthe.sureness.provider;
+
+/**
+ * @author tomsun28
+ * @date 23:02 2019-04-02
+ */
+public interface AccountProvider {
+
+ Account loadAccount(String appId);
+
+}
diff --git a/core/src/main/java/com/usthe/sureness/subject/support/DefaultSubject.java b/core/src/main/java/com/usthe/sureness/subject/support/DefaultSubject.java
index f27d36c020862aad8da8dbd87b8c25a3b5c06f35..b06a6d619d6c62882e87aa6023b3a21ac4688982 100644
--- a/core/src/main/java/com/usthe/sureness/subject/support/DefaultSubject.java
+++ b/core/src/main/java/com/usthe/sureness/subject/support/DefaultSubject.java
@@ -61,7 +61,7 @@ public class DefaultSubject implements Subject {
return new Builder();
}
- private static class Builder {
+ public static class Builder {
private String principal;
private List roles;
private String targetResource;
diff --git a/core/src/main/java/com/usthe/sureness/subject/support/DefaultSubjectFactory.java b/core/src/main/java/com/usthe/sureness/subject/support/DefaultSubjectFactory.java
index cea66b1990ba088af551d85ba82cbdad460ba0cb..2df49edbbd7d3c593a03330687214f1afc4b4d5f 100644
--- a/core/src/main/java/com/usthe/sureness/subject/support/DefaultSubjectFactory.java
+++ b/core/src/main/java/com/usthe/sureness/subject/support/DefaultSubjectFactory.java
@@ -32,8 +32,11 @@ public class DefaultSubjectFactory implements SubjectFactory {
String principal = (String)auToken.getPrincipal();
List roles = (List)auToken.getOwnRoles();
String targetUri = (String)auToken.getTargetResource();
- Subject subject = new DefaultSubject().setPrincipal(principal)
- .setRoles(roles).setTargetResource(targetUri);
+ Subject subject = DefaultSubject.getBuilder()
+ .setTargetResource(targetUri)
+ .addRoles(roles)
+ .setPrincipal(principal)
+ .build();
// 将subject 绑定到localThread变量中
ThreadContext.bind(subject);
// 如果是网关认证中心, 之后可以考虑把subject绑定到request请求中,供子系统使用
@@ -61,4 +64,5 @@ public class DefaultSubjectFactory implements SubjectFactory {
throw new SurenessNoInitException("the subjectFactory not complete ye");
}
}
+
}
diff --git a/core/src/main/java/com/usthe/sureness/subject/support/JwtSubjectToken.java b/core/src/main/java/com/usthe/sureness/subject/support/JwtSubjectToken.java
index 8b6e09d0d5a0ed14d4168cc058e5f55efe043c45..713b41c992099fbc0ca28f87cae2a5cbc6a02428 100644
--- a/core/src/main/java/com/usthe/sureness/subject/support/JwtSubjectToken.java
+++ b/core/src/main/java/com/usthe/sureness/subject/support/JwtSubjectToken.java
@@ -34,7 +34,11 @@ public class JwtSubjectToken implements SubjectAuToken {
*/
private List roles;
/**
- *
+ * 所访问资源地址
+ */
+ private String targetUri;
+ /**
+ * 所访问资源他支持的角色
*/
private List supportRoles;
@@ -50,21 +54,21 @@ public class JwtSubjectToken implements SubjectAuToken {
@Override
public Object getOwnRoles() {
- return null;
+ return this.roles;
}
@Override
public Object getTargetResource() {
- return null;
+ return this.targetUri;
}
@Override
public Object getSupportRoles() {
- return null;
+ return this.supportRoles;
}
@Override
public void setSupportRoles(Object var1) {
-
+ this.supportRoles = (List) var1;
}
}
diff --git a/core/src/main/java/com/usthe/sureness/subject/support/PasswordSubjectToken.java b/core/src/main/java/com/usthe/sureness/subject/support/PasswordSubjectToken.java
index 9da2eb8e4738b89912e0aff017b426b23b297a72..135a2150e9d147999f1792193bc20bf20589f195 100644
--- a/core/src/main/java/com/usthe/sureness/subject/support/PasswordSubjectToken.java
+++ b/core/src/main/java/com/usthe/sureness/subject/support/PasswordSubjectToken.java
@@ -13,40 +13,38 @@ public class PasswordSubjectToken implements SubjectAuToken {
private static final long serialVersionUID = 1L;
private String appId;
private String password;
- private String timestamp;
private String host;
- private String tokenKey;
+ private String targetUri;
private List roles;
private List supportRoles;
@Override
public Object getPrincipal() {
- return null;
+ return this.appId;
}
@Override
public Object getCredentials() {
- return null;
+ return this.password;
}
@Override
public Object getOwnRoles() {
- return null;
+ return this.roles;
}
@Override
public Object getTargetResource() {
- return null;
+ return this.targetUri;
}
@Override
public Object getSupportRoles() {
- return null;
+ return supportRoles;
}
@Override
public void setSupportRoles(Object var1) {
-
}
}
diff --git a/core/src/main/java/com/usthe/sureness/util/JsonWebTokenUtil.java b/core/src/main/java/com/usthe/sureness/util/JsonWebTokenUtil.java
new file mode 100644
index 0000000000000000000000000000000000000000..6d61246d013254359cf3c6fff8719dfe5161c18a
--- /dev/null
+++ b/core/src/main/java/com/usthe/sureness/util/JsonWebTokenUtil.java
@@ -0,0 +1,199 @@
+package com.usthe.sureness.util;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.impl.DefaultHeader;
+import io.jsonwebtoken.impl.DefaultJwsHeader;
+import io.jsonwebtoken.impl.TextCodec;
+import io.jsonwebtoken.impl.compression.DefaultCompressionCodecResolver;
+import io.jsonwebtoken.lang.Assert;
+
+
+import javax.xml.bind.DatatypeConverter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author tomsun28
+ * @date 16:29 2018/3/8
+ */
+public class JsonWebTokenUtil {
+
+ public static final String SECRET_KEY = "?::4343fdf4fdf6cvf):";
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+ private static final int COUNT_2 = 2;
+ private static CompressionCodecResolver codecResolver = new DefaultCompressionCodecResolver();
+
+ private JsonWebTokenUtil() {
+
+ }
+
+ /**
+ * json web token 签发
+ * @param id 令牌ID
+ * @param subject 用户ID
+ * @param issuer 签发人
+ * @param period 有效时间(毫秒)
+ * @param roles 访问主张-角色
+ * @param permissions 访问主张-权限
+ * @param algorithm 加密算法
+ * @return java.lang.String
+ */
+ public static String issueJWT(String id, String subject, String issuer, Long period, String roles, String permissions, SignatureAlgorithm algorithm) {
+ // 当前时间戳
+ Long currentTimeMillis = System.currentTimeMillis();
+ // 秘钥
+ byte[] secreKeyBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);
+ JwtBuilder jwtBuilder = Jwts.builder();
+ if (id != null) {
+ jwtBuilder.setId(id);
+ }
+ if (subject != null) {
+ jwtBuilder.setSubject(subject);
+ }
+ if (issuer != null) {
+ jwtBuilder.setIssuer(issuer);
+ }
+ // 设置签发时间
+ jwtBuilder.setIssuedAt(new Date(currentTimeMillis));
+ // 设置到期时间
+ if (null != period) {
+ jwtBuilder.setExpiration(new Date(currentTimeMillis+period*1000));
+ }
+ if (roles != null) {
+ jwtBuilder.claim("roles", roles);
+ }
+ if (permissions != null) {
+ jwtBuilder.claim("perms", permissions);
+ }
+ // 压缩,可选GZIP
+ jwtBuilder.compressWith(CompressionCodecs.DEFLATE);
+ // 加密设置
+ jwtBuilder.signWith(algorithm,secreKeyBytes);
+ return jwtBuilder.compact();
+ }
+
+ /**
+ * 解析JWT的Payload
+ */
+ public static String parseJwtPayload(String jwt){
+ Assert.hasText(jwt, "JWT String argument cannot be null or empty.");
+ String base64UrlEncodedHeader = null;
+ String base64UrlEncodedPayload = null;
+ String base64UrlEncodedDigest = null;
+ int delimiterCount = 0;
+ StringBuilder sb = new StringBuilder(128);
+ for (char c : jwt.toCharArray()) {
+ if (c == '.') {
+ CharSequence tokenSeq = io.jsonwebtoken.lang.Strings.clean(sb);
+ String token = tokenSeq!=null?tokenSeq.toString():null;
+
+ if (delimiterCount == 0) {
+ base64UrlEncodedHeader = token;
+ } else if (delimiterCount == 1) {
+ base64UrlEncodedPayload = token;
+ }
+
+ delimiterCount++;
+ sb.setLength(0);
+ } else {
+ sb.append(c);
+ }
+ }
+ if (delimiterCount != COUNT_2) {
+ String msg = "JWT strings must contain exactly 2 period characters. Found: " + delimiterCount;
+ throw new MalformedJwtException(msg);
+ }
+ if (sb.length() > 0) {
+ base64UrlEncodedDigest = sb.toString();
+ }
+ if (base64UrlEncodedPayload == null) {
+ throw new MalformedJwtException("JWT string '" + jwt + "' is missing a body/payload.");
+ }
+ // =============== Header =================
+ Header header = null;
+ CompressionCodec compressionCodec = null;
+ if (base64UrlEncodedHeader != null) {
+ String origValue = TextCodec.BASE64URL.decodeToString(base64UrlEncodedHeader);
+ Map m = readValue(origValue);
+ if (base64UrlEncodedDigest != null) {
+ header = new DefaultJwsHeader(m);
+ } else {
+ header = new DefaultHeader(m);
+ }
+ compressionCodec = codecResolver.resolveCompressionCodec(header);
+ }
+ // =============== Body =================
+ String payload;
+ if (compressionCodec != null) {
+ byte[] decompressed = compressionCodec.decompress(TextCodec.BASE64URL.decode(base64UrlEncodedPayload));
+ payload = new String(decompressed, io.jsonwebtoken.lang.Strings.UTF_8);
+ } else {
+ payload = TextCodec.BASE64URL.decodeToString(base64UrlEncodedPayload);
+ }
+ return payload;
+ }
+
+ /**
+ * 验签JWT
+ *
+ * @param jwt json web token
+ */
+ public static Claims parseJwt(String jwt, String appKey) throws ExpiredJwtException, UnsupportedJwtException, MalformedJwtException, SignatureException, IllegalArgumentException {
+ return Jwts.parser()
+ .setSigningKey(DatatypeConverter.parseBase64Binary(appKey))
+ .parseClaimsJws(jwt)
+ .getBody();
+
+// JwtAccount jwtAccount = new JwtAccount();
+// //令牌ID
+// jwtAccount.setTokenId(claims.getId());
+// // 客户标识
+// jwtAccount.setAppId(claims.getSubject());
+// // 签发者
+// jwtAccount.setIssuer(claims.getIssuer());
+// // 签发时间
+// jwtAccount.setIssuedAt(claims.getIssuedAt());
+// // 接收方
+// jwtAccount.setAudience(claims.getAudience());
+// // 访问主张-角色
+// jwtAccount.setRoles(claims.get("roles", String.class));
+// // 访问主张-权限
+// jwtAccount.setPerms(claims.get("perms", String.class));
+ }
+
+
+ /**
+ * description 从json数据中读取格式化map
+ *
+ * @param val 1
+ * @return java.util.Map
+ */
+ @SuppressWarnings("unchecked")
+ public static Map readValue(String val) {
+ try {
+ return MAPPER.readValue(val, Map.class);
+ } catch (IOException e) {
+ throw new MalformedJwtException("Unable to read JSON value: " + val, e);
+ }
+ }
+
+ /**
+ * 分割字符串进SET
+ */
+ @SuppressWarnings("unchecked")
+ public static Set split(String str) {
+
+ Set set = new HashSet<>();
+ if (str == null || "".equals(str)) {
+ return set;
+ }
+ Arrays.asList(str.split(","));
+ return set;
+ }
+
+}
diff --git a/core/src/main/java/com/usthe/sureness/util/Md5Util.java b/core/src/main/java/com/usthe/sureness/util/Md5Util.java
new file mode 100644
index 0000000000000000000000000000000000000000..80a50cc6f6d038b04d6a6382162e55b29a69167a
--- /dev/null
+++ b/core/src/main/java/com/usthe/sureness/util/Md5Util.java
@@ -0,0 +1,51 @@
+package com.usthe.sureness.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+
+/**
+ * @author tomsun28
+ * @date 20:48 2018/2/27
+ */
+public class Md5Util {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(Md5Util.class);
+
+ public static String md5(String content) {
+ // 用于加密的字符
+ char[] md5String = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
+ try {
+ // 使用平台默认的字符集将md5String编码为byte序列,并将结果存储到一个新的byte数组中
+ byte[] byteInput = content.getBytes(StandardCharsets.UTF_8);
+
+ // 信息摘要是安全的单向哈希函数,它接收任意大小的数据,并输出固定长度的哈希值
+ MessageDigest mdInst = MessageDigest.getInstance("MD5");
+
+ // MessageDigest对象通过使用update方法处理数据,使用指定的byte数组更新摘要
+ mdInst.update(byteInput);
+
+ //摘要更新后通过调用digest() 执行哈希计算,获得密文
+ byte[] md = mdInst.digest();
+
+ //把密文转换成16进制的字符串形式
+ int j = md.length;
+ char[] str = new char[j*2];
+ int k = 0;
+ for (int i=0; i>> 4 & 0xf];
+ str[k++] = md5String[byte0 & 0xf];
+ }
+ // 返回加密后的字符串
+ return new String(str);
+
+ }catch (Exception e) {
+ LOGGER.warn(e.getMessage(),e);
+ return null;
+ }
+
+ }
+}