Commit 651bb9ab by 罗长华

JwtFilter -> SecurityFilter 整合

parent a6aca6bb
...@@ -22,11 +22,10 @@ import io.geekidea.springbootplus.config.properties.ShiroProperties; ...@@ -22,11 +22,10 @@ import io.geekidea.springbootplus.config.properties.ShiroProperties;
import io.geekidea.springbootplus.framework.shiro.cache.AppLoginRedisService; import io.geekidea.springbootplus.framework.shiro.cache.AppLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.exception.ShiroConfigException; import io.geekidea.springbootplus.framework.shiro.exception.ShiroConfigException;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtCredentialsMatcher; import io.geekidea.springbootplus.framework.shiro.jwt.JwtCredentialsMatcher;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtFilter; import io.geekidea.springbootplus.framework.shiro.jwt.SecurityFilter;
import io.geekidea.springbootplus.framework.shiro.jwt.realm.JwtRealmAppUser; import io.geekidea.springbootplus.framework.shiro.jwt.realm.JwtRealmAppUser;
import io.geekidea.springbootplus.framework.shiro.service.ShiroLoginService; import io.geekidea.springbootplus.framework.shiro.service.ShiroLoginService;
import io.geekidea.springbootplus.framework.shiro.signature.ApplicationService; import io.geekidea.springbootplus.framework.shiro.signature.ApplicationService;
import io.geekidea.springbootplus.framework.shiro.signature.SignatureAuthFilter;
import io.geekidea.springbootplus.framework.shiro.signature.SignatureAuthRealm; import io.geekidea.springbootplus.framework.shiro.signature.SignatureAuthRealm;
import io.geekidea.springbootplus.framework.util.IniUtil; import io.geekidea.springbootplus.framework.util.IniUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
...@@ -214,8 +213,7 @@ public class ShiroConfig { ...@@ -214,8 +213,7 @@ public class ShiroConfig {
*/ */
private Map<String, Filter> getFilterMap(ShiroLoginService shiroLoginService, ApplicationService applicationService) { private Map<String, Filter> getFilterMap(ShiroLoginService shiroLoginService, ApplicationService applicationService) {
Map<String, Filter> filterMap = new LinkedHashMap<>(); Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put(JWT_FILTER_NAME, new JwtFilter(shiroLoginService)); filterMap.put(JWT_FILTER_NAME, new SecurityFilter(shiroLoginService, applicationService));
filterMap.put("signatureAuthFilter", new SignatureAuthFilter(applicationService));
return filterMap; return filterMap;
} }
...@@ -278,9 +276,6 @@ public class ShiroConfig { ...@@ -278,9 +276,6 @@ public class ShiroConfig {
// 如果启用shiro,则设置最后一个设置为JWTFilter,否则全部路径放行 // 如果启用shiro,则设置最后一个设置为JWTFilter,否则全部路径放行
if (shiroProperties.isEnable()) { if (shiroProperties.isEnable()) {
filterChainDefinitionMap.put("/imClient/registerClient", "signatureAuthFilter");
filterChainDefinitionMap.put("/token/getToken", "signatureAuthFilter");
filterChainDefinitionMap.put("/user/modifyUser", "signatureAuthFilter");
filterChainDefinitionMap.put("/**", JWT_FILTER_NAME); filterChainDefinitionMap.put("/**", JWT_FILTER_NAME);
} else { } else {
filterChainDefinitionMap.put("/**", ANON); filterChainDefinitionMap.put("/**", ANON);
......
...@@ -19,22 +19,28 @@ package io.geekidea.springbootplus.framework.shiro.jwt; ...@@ -19,22 +19,28 @@ package io.geekidea.springbootplus.framework.shiro.jwt;
import io.geekidea.springbootplus.framework.common.api.ApiCode; import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult; import io.geekidea.springbootplus.framework.common.api.ApiResult;
import io.geekidea.springbootplus.framework.shiro.service.ShiroLoginService; import io.geekidea.springbootplus.framework.shiro.service.ShiroLoginService;
import io.geekidea.springbootplus.framework.shiro.signature.Application;
import io.geekidea.springbootplus.framework.shiro.signature.ApplicationService;
import io.geekidea.springbootplus.framework.shiro.signature.SignatureAuthToken;
import io.geekidea.springbootplus.framework.shiro.signature.SignatureChecker;
import io.geekidea.springbootplus.framework.shiro.util.JwtTokenUtil; import io.geekidea.springbootplus.framework.shiro.util.JwtTokenUtil;
import io.geekidea.springbootplus.framework.shiro.util.JwtUtil; import io.geekidea.springbootplus.framework.shiro.util.JwtUtil;
import io.geekidea.springbootplus.framework.util.HttpServletResponseUtil; import io.geekidea.springbootplus.framework.util.HttpServletResponseUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.util.WebUtils; import org.apache.shiro.web.util.WebUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/** /**
* Shiro JWT授权过滤器 * Shiro JWT授权过滤器
* *
...@@ -43,12 +49,17 @@ import javax.servlet.http.HttpServletResponse; ...@@ -43,12 +49,17 @@ import javax.servlet.http.HttpServletResponse;
* @since 1.3.0.RELEASE * @since 1.3.0.RELEASE
**/ **/
@Slf4j @Slf4j
public class JwtFilter extends AuthenticatingFilter { public class SecurityFilter extends AuthenticatingFilter {
private final ShiroLoginService shiroLoginService; private final ShiroLoginService shiroLoginService;
public JwtFilter(ShiroLoginService shiroLoginService) { private static final String AUTHORIZATION = "Authorization";
private ApplicationService applicationService;
public SecurityFilter(ShiroLoginService shiroLoginService, ApplicationService applicationService) {
this.shiroLoginService = shiroLoginService; this.shiroLoginService = shiroLoginService;
this.applicationService = applicationService;
} }
/** /**
...@@ -62,21 +73,65 @@ public class JwtFilter extends AuthenticatingFilter { ...@@ -62,21 +73,65 @@ public class JwtFilter extends AuthenticatingFilter {
*/ */
@Override @Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) { protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {
// 从http请求头中取得token
String token = JwtTokenUtil.getToken();
if (StringUtils.isBlank(token)) {
throw new AuthenticationException("token不能为空");
}
if (JwtUtil.isExpired(token)) {
throw new AuthenticationException("JWT Token已过期,token:" + token);
}
// 从redis 获取jwt数据 // 从请求头中取出x-im-from
JwtToken jwtToken = shiroLoginService.getJwtTokenForRedis(token); ShiroHttpServletRequest ShiroHttpServletRequest = (ShiroHttpServletRequest) servletRequest;
if (jwtToken == null) {
throw new AuthenticationException("Redis Token不存在,token:" + token); HttpServletRequest request =
(HttpServletRequest) ShiroHttpServletRequest.getRequest();
String from = request.getHeader("x-im-from");
if ("server".equals(from)) {
// 服务端发起的请求,校验签名
String authorization = request.getHeader(AUTHORIZATION);
if (StringUtils.isBlank(authorization)) {
throw new AuthenticationException("token不能为空");
}
// 校验Signature是否完整 合规
if (!authorization.startsWith("WECLOUD-IM AppKey")) {
throw new AuthenticationException("token错误");
}
if (!authorization.contains(", Signature:")) {
throw new AuthenticationException("token 未包含签名信息");
}
String appKey = getAppKeyFromAuthorization(authorization);
String clientSignature = getSignatureFromAuthorization(authorization);
// 校验通过 获取并校验Application信息
Application application = applicationService.getApplication(appKey);
if (application == null) {
throw new AuthenticationException("application does not exist");
}
if (!application.isActive()) {
throw new AuthenticationException("application is unavailable");
}
Long appId = application.getId();
String appSecret = application.getAppSecret();
SignatureChecker.check(request, clientSignature, appSecret);
// 构建SignatureAuthToken
return new SignatureAuthToken(appKey, appId);
} else {
// 客户端发起的请求,校验token
// 从http请求头中取得token
String token = JwtTokenUtil.getToken();
if (StringUtils.isBlank(token)) {
throw new AuthenticationException("token不能为空");
}
if (JwtUtil.isExpired(token)) {
throw new AuthenticationException("JWT Token已过期,token:" + token);
}
// 从redis 获取jwt数据
JwtToken jwtToken = shiroLoginService.getJwtTokenForRedis(token);
if (jwtToken == null) {
throw new AuthenticationException("Redis Token不存在,token:" + token);
}
return JwtToken.build(token, jwtToken.getSalt(), jwtToken.getExpireSecond(), jwtToken.getClientId(), jwtToken.getAppKey(), jwtToken.getPlatform());
} }
return JwtToken.build(token, jwtToken.getSalt(), jwtToken.getExpireSecond(), jwtToken.getClientId(), jwtToken.getAppKey(), jwtToken.getPlatform());
} }
...@@ -144,9 +199,11 @@ public class JwtFilter extends AuthenticatingFilter { ...@@ -144,9 +199,11 @@ public class JwtFilter extends AuthenticatingFilter {
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
String url = WebUtils.toHttp(request).getRequestURI(); String url = WebUtils.toHttp(request).getRequestURI();
log.info("鉴权成功,token:{},url:{}", token, url); log.info("鉴权成功,token:{},url:{}", token, url);
// 刷新token if (token instanceof JwtToken) {
JwtToken jwtToken = (JwtToken) token; // 刷新token
HttpServletResponse httpServletResponse = WebUtils.toHttp(response); JwtToken jwtToken = (JwtToken) token;
HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
}
// shiroLoginService.refreshToken(jwtToken, httpServletResponse); // shiroLoginService.refreshToken(jwtToken, httpServletResponse);
return true; return true;
} }
...@@ -165,4 +222,18 @@ public class JwtFilter extends AuthenticatingFilter { ...@@ -165,4 +222,18 @@ public class JwtFilter extends AuthenticatingFilter {
log.error("登录失败,token:" + token + ",error:" + e.getMessage(), e); log.error("登录失败,token:" + token + ",error:" + e.getMessage(), e);
return false; return false;
} }
private String getAppKeyFromAuthorization(String authorization) {
// 处理token
String[] tokenInfo = authorization.split(",");
// appkey 格式是固定的,直接替换
return tokenInfo[0].replace("WECLOUD-IM AppKey:", "").trim();
}
private String getSignatureFromAuthorization(String authorization) {
// 处理token
String[] tokenInfo = authorization.split(",");
// 客户端传入的签名信息
return tokenInfo[1].replace("Signature:", "").trim();
}
} }
package io.geekidea.springbootplus.framework.shiro.signature;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.NotNull;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.util.WebUtils;
/**
* shiro 用户签名认证
* @Author luozh
* @Date 2022年04月14日 16:43
* @Version 1.0
*/
@Slf4j
public class SignatureAuthFilter extends AuthenticatingFilter {
private static final String AUTHORIZATION = "Authorization";
private ApplicationService applicationService;
public SignatureAuthFilter(@NotNull ApplicationService applicationService) {
if (applicationService == null) {
throw new IllegalArgumentException("application service must not be null");
}
this.applicationService = applicationService;
}
@Override
protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
ShiroHttpServletRequest ShiroHttpServletRequest = (ShiroHttpServletRequest) servletRequest;
HttpServletRequest request =
(HttpServletRequest) ShiroHttpServletRequest.getRequest();
String authorization = request.getHeader(AUTHORIZATION);
if (StringUtils.isBlank(authorization)) {
throw new AuthenticationException("token不能为空");
}
// 校验Signature是否完整 合规
if (!authorization.startsWith("WECLOUD-IM AppKey")) {
throw new AuthenticationException("token错误");
}
if (!authorization.contains(", Signature:")) {
throw new AuthenticationException("token 未包含签名信息");
}
String appKey = getAppKeyFromAuthorization(authorization);
String clientSignature = getSignatureFromAuthorization(authorization);
// 校验通过 获取并校验Application信息
Application application = applicationService.getApplication(appKey);
if (application == null) {
throw new AuthenticationException("application does not exist");
}
if (!application.isActive()) {
throw new AuthenticationException("application is unavailable");
}
Long appId = application.getId();
String appSecret = application.getAppSecret();
SignatureChecker.check(request, clientSignature, appSecret);
// 构建SignatureAuthToken
return new SignatureAuthToken(appKey, appId);
}
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
String url = WebUtils.toHttp(request).getRequestURI();
log.info("isAccessAllowed url:{}", url);
if (this.isLoginRequest(request, response)) {
return true;
}
boolean allowed = false;
try {
allowed = executeLogin(request, response);
} catch (IllegalStateException e) { //not found any token
log.error("Token不能为空", e);
} catch (Exception e) {
log.error("访问错误", e);
}
return true;
}
@Override
protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
return false;
}
private String getAppKeyFromAuthorization(String authorization) {
// 处理token
String[] tokenInfo = authorization.split(",");
// appkey 格式是固定的,直接替换
return tokenInfo[0].replace("WECLOUD-IM AppKey:", "").trim();
}
private String getSignatureFromAuthorization(String authorization) {
// 处理token
String[] tokenInfo = authorization.split(",");
// 客户端传入的签名信息
return tokenInfo[1].replace("Signature:", "").trim();
}
}
...@@ -12,5 +12,7 @@ public interface ImHeaders extends HttpHeaders { ...@@ -12,5 +12,7 @@ public interface ImHeaders extends HttpHeaders {
static final String WECLOUD_SIGN = "x-wecloud-sign"; static final String WECLOUD_SIGN = "x-wecloud-sign";
static final String X_IM_FROM = "x-im-from";
static final String IM_PREFIX = "x-im-"; static final String IM_PREFIX = "x-im-";
} }
...@@ -14,6 +14,7 @@ import com.wecloud.im.sdk.common.RequestMessage; ...@@ -14,6 +14,7 @@ import com.wecloud.im.sdk.common.RequestMessage;
import com.wecloud.im.sdk.common.auth.Credentials; import com.wecloud.im.sdk.common.auth.Credentials;
import com.wecloud.im.sdk.exception.WecloudException; import com.wecloud.im.sdk.exception.WecloudException;
import static com.wecloud.im.sdk.internal.ImHeaders.X_IM_FROM;
import static com.wecloud.im.sdk.utils.HttpHeaders.CONTENT_TYPE; import static com.wecloud.im.sdk.utils.HttpHeaders.CONTENT_TYPE;
/** /**
...@@ -44,6 +45,8 @@ public abstract class WecloudImOperation { ...@@ -44,6 +45,8 @@ public abstract class WecloudImOperation {
if (HttpMethod.POST.equals(request.getMethod())) { if (HttpMethod.POST.equals(request.getMethod())) {
request.addHeader(CONTENT_TYPE, "application/json; charset=utf-8"); request.addHeader(CONTENT_TYPE, "application/json; charset=utf-8");
} }
// 添加来源请求头
request.addHeader(X_IM_FROM, "server");
// 添加签名请求头 // 添加签名请求头
RequestSigner signer = new RequestSigner(request.getMethod().name(), request.getEndpoint(), credentials); RequestSigner signer = new RequestSigner(request.getMethod().name(), request.getEndpoint(), credentials);
signer.sign(request); signer.sign(request);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment