提交 eca58dbf 编写于 作者: sinat_25235033's avatar sinat_25235033

modify jwt processor,引入 jjwt

上级 8d5221d0
......@@ -13,6 +13,7 @@
<properties>
<slf4j.version>1.7.21</slf4j.version>
<jjwt.version>0.9.0</jjwt.version>
</properties>
<dependencies>
<dependency>
......@@ -20,6 +21,17 @@
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!-- java版本升级少包-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
</dependencies>
<build>
......
......@@ -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
......
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<String> 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<String> ownRoles = (List<String>)var.getOwnRoles();
List<String> supportRoles = (List<String>)var.getSupportRoles();
return supportRoles == null || supportRoles.isEmpty()
|| supportRoles.stream().anyMatch(role -> ownRoles.contains(role));
}
@Override
......
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
......
package com.usthe.sureness.provider;
/**
* @author tomsun28
* @date 23:18 2019-04-02
*/
public interface Account {
String getAppId();
String getPassword();
String getSalt();
}
package com.usthe.sureness.provider;
/**
* @author tomsun28
* @date 23:02 2019-04-02
*/
public interface AccountProvider {
Account loadAccount(String appId);
}
......@@ -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<String> roles;
private String targetResource;
......
......@@ -32,8 +32,11 @@ public class DefaultSubjectFactory implements SubjectFactory {
String principal = (String)auToken.getPrincipal();
List<String> roles = (List<String>)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");
}
}
}
......@@ -34,7 +34,11 @@ public class JwtSubjectToken implements SubjectAuToken {
*/
private List<String> roles;
/**
*
* 所访问资源地址
*/
private String targetUri;
/**
* 所访问资源他支持的角色
*/
private List<String> 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<String>) var1;
}
}
......@@ -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<String> roles;
private List<String> 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) {
}
}
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<String, Object> 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<java.lang.String,java.lang.Object>
*/
@SuppressWarnings("unchecked")
public static Map<String, Object> 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<String> split(String str) {
Set<String> set = new HashSet<>();
if (str == null || "".equals(str)) {
return set;
}
Arrays.asList(str.split(","));
return set;
}
}
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<j; i++) {
byte byte0 = md[i];
str[k++] = md5String[byte0 >>> 4 & 0xf];
str[k++] = md5String[byte0 & 0xf];
}
// 返回加密后的字符串
return new String(str);
}catch (Exception e) {
LOGGER.warn(e.getMessage(),e);
return null;
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册