验证token
对外API过滤器
java
public class APIFilter implements Filter {
private static Logger logger = LogManager.getLogger(APIFilter.class);
private ICachedTokenService tokenService;
public APIFilter(ICachedTokenService tokenService) {
super();
this.tokenService = tokenService;
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
HttpServletRequest hreq = (HttpServletRequest) req;
HttpServletResponse hres = (HttpServletResponse) resp;
//设置一些Header其他信息
hres.setHeader("Access-Control-Allow-Origin", hreq.getHeader("Origin"));
hres.setHeader("Access-Control-Allow-Credentials", "true");
hres.setHeader("Access-Control-Allow-Methods", hreq.getMethod());
hres.setHeader("Access-Control-Max-Age", "86400");
hres.setHeader("Access-Control-Allow-Headers", "*");
//不校验权限
if(hreq.getRequestURI().contains("/unauth") || hreq.getRequestURI().contains("/question")){
chain.doFilter(req, resp);
return;
}
// 如果是OPTIONS则结束请求
if (HttpMethod.OPTIONS.toString().equals(hreq.getMethod())) {
hres.setStatus(HttpStatus.NO_CONTENT.value());
return;
}
try{
//从请求cookie中获取access_token
AccessTokenUser user = null;
String access_token=hreq.getParameter("access_token");
if(StringUtils.isBlank(access_token)){
access_token= hreq.getHeader("access_token");
}
if(StringUtils.isBlank(access_token)){
access_token=this.getCookieByCookieName("access_token", hreq);
}
//获取用户
if(StringUtils.isNotBlank(access_token)){
String token=access_token;
user = ThreadLocalCache.fetchAPIData(
"AccessTokenUserByAccessToken:"+token+",true",
() -> {
ApiResultDTO<AccessTokenUser> tokenUserResp= tokenService.getAccessTokenUserByAccessToken(token, true);
if(tokenUserResp.isSuccessed()) {
return tokenUserResp;
}else {
String errorMsg = "访问令牌服务发生错误,错误码:"+tokenUserResp.getStatus()+",错误信息:"+tokenUserResp.getStatusMsg();
logger.error(errorMsg);
throw new RuntimeException(errorMsg);
}
}
);
//判断是否存在这样的用户
if(user==null) logger.error("请求地址{}根据access_token【{}】无法获取登录用户。",hreq.getRequestURI(),access_token);
}
if(user==null){
throw new BusinessException("登录已过期,请重新登录!", ApiResultDTO.STATUS_UNAUTH);
}
LoginInitUtils.afterLogin(false,hreq,hres,user);
chain.doFilter(req, resp);
return;
} catch (Exception e) {
ApiResultDTO<String> apiResult=null;
if(e instanceof BusinessException) {
BusinessException businessException = (BusinessException) e;
apiResult = ApiResultDTO.failed(businessException.getErrorCode(), businessException.getOriMsg());
}else {
apiResult = ApiResultDTO.failed(e.getMessage());
}
hres.setContentType("application/json;charset=UTF-8");
hres.setHeader("Cache-Control", "no-store");
hres.setHeader("Pragma", "no-cache");
hres.setDateHeader("Expires", 0);
hres.getWriter().write(new ObjectMapper().writeValueAsString(apiResult));
hres.getWriter().flush();
hres.getWriter().close();
}
}
private String getCookieByCookieName(String cookieName,HttpServletRequest hreq) {
Cookie[] cookies = hreq.getCookies();
if(cookies!=null){
for(Cookie cookie:cookies){
if(cookieName.equalsIgnoreCase(cookie.getName())){
return cookie.getValue();
}
}
}
return null;
}
}
对内API过滤器
java
public class _APIFilter implements Filter {
private Logger logger=LogManager.getLogger(this.getClass());
private ICachedTokenService tokenService;
public _APIFilter(ICachedTokenService tokenService) {
super();
this.tokenService = tokenService;
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest hreq = (HttpServletRequest) req;
HttpServletResponse hres = (HttpServletResponse) resp;
try{
Utils.passOverByIp(hreq.getRemoteAddr());
// logger.error("--------------------------_APIFilter["+hreq.getRequestURL()+"]开始--------------------------");
AccessTokenUser user = null;
String access_token=hreq.getParameter("access_token");
if(StringUtils.isBlank(access_token)){
access_token= hreq.getHeader("access_token");
}
//根据访问令牌获取用户
if(StringUtils.isNotBlank(access_token)){
try {
String token=access_token;
user = ThreadLocalCache.fetchAPIData(
"AccessTokenUser."+token+",true",
() -> {
ApiResultDTO<AccessTokenUser> tokenUserResp= tokenService.getAccessTokenUserByAccessToken(token, true);
if(tokenUserResp.isSuccessed()) {
return tokenUserResp;
}else {
String errorMsg = "访问令牌服务发生错误,错误码:"+tokenUserResp.getStatus()+",错误信息:"+tokenUserResp.getStatusMsg();
logger.error(errorMsg);
throw new RuntimeException(errorMsg);
}
}
);
} catch (Exception e) {
e.printStackTrace();
user=null;
}
if(user==null) logger.error("--------------------------_APIFilter["+hreq.getRequestURL()+"]access_token["+access_token+"]获取用户结果"+user+"--------------------------");
}
//根据内部令牌获取用户
if(user==null) {
String inner_token=hreq.getParameter("inner_token");
if(StringUtils.isBlank(inner_token)){
inner_token = hreq.getHeader("inner_token");
}
if(StringUtils.isBlank(inner_token)) throw new RuntimeException("未找到访问令牌");
String token=inner_token;
user=ThreadLocalCache.fetchAPIData(null,()->{
return tokenService.verifyTokenAndGetUser(token);
},"验证访问令牌错误,");
if(user==null) logger.error("--------------------------_APIFilter["+hreq.getRequestURL()+"]inner_token["+inner_token+"]获取用户结果"+user+"--------------------------");
}
LoginInitUtils.afterLogin(false,hreq,hres,user);
chain.doFilter(req, resp);
return;
} catch (Exception e) {
e.printStackTrace();
ApiResultDTO<String> apiResult=ApiResultDTO.failed(e.getMessage());
hres.setContentType("application/json;charset=UTF-8");
hres.setHeader("Cache-Control", "no-store");
hres.setHeader("Pragma", "no-cache");
hres.setDateHeader("Expires", 0);
hres.getWriter().write(new ObjectMapper().writeValueAsString(apiResult));
hres.getWriter().flush();
hres.getWriter().close();
}
}
}
java
public ApiResultDTO<AccessTokenUser> verifyTokenAndGetUser(String inner_token) {
Map<String,String> claims=JWTUtils.verifyTokenAndGetClaims(inner_token);
AccessTokenUser user=null;
if(claims.get("user")!=null) {
user=JsonConverter.jsonStrToObject(claims.get("user"), AccessTokenUser.class);
}
return ApiResultDTO.success(user);
}
java
//将inner_token 转化为用户信息
public static Map<String,String> verifyTokenAndGetClaims(String jwt_token) {
JWT jwt=JWT.create();
//jwt.setHeader(JWTHeader.TYPE, "JWT");//默认值
jwt.setHeader(JWTHeader.ALGORITHM, "HS256");
jwt.setSigner(JWTSignerUtil.createSigner("HS256", SECRET));
try {
jwt.parse(jwt_token);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("校验jwt_token令牌失败");
}
if(!jwt.verify()) {
throw new RuntimeException("无效的jwt_token");
}
Map<String,String> result = new HashMap<String,String>();
JSONObject jo=jwt.getPayloads();
if(jo==null||jo.size()==0) {
return result;
}
for (Map.Entry<String,Object> d: jo.entrySet()) {
if(d.getValue()==null) {
continue;
}
result.put(d.getKey(), d.getValue().toString());
}
return result;
}
配置拦截器
java
@Configuration
public class FiltersConfiguration {
@Autowired
private ICachedTokenService tokenService;
//前后端交互处理-拦截所有api访问请求
@Bean
public FilterRegistrationBean<APIFilter> apiFilterRegistrationBean() {
FilterRegistrationBean<APIFilter> registrationBean = new FilterRegistrationBean<APIFilter>();
registrationBean.setFilter(new APIFilter(tokenService));
registrationBean.addUrlPatterns("/api/*");
registrationBean.setOrder(5);
return registrationBean;
}
//前后端交互处理-拦截所有_api访问请求
@Bean
public FilterRegistrationBean<_APIFilter> _apiFilterRegistrationBean() {
FilterRegistrationBean<_APIFilter> registrationBean = new FilterRegistrationBean<_APIFilter>();
registrationBean.setFilter(new _APIFilter(tokenService));
registrationBean.addUrlPatterns("/_api/*");
registrationBean.setOrder(6);
return registrationBean;
}
}
清除缓存
基础数据定义的一些资源变更,需要同步清除其他项目本地缓存
配置清除缓存过滤器
java
//处理基础数据变更后通知各系统清空本系统的基础数据的缓存
@Bean
public FilterRegistrationBean<FireSysDataChangedEventFilter> fireSysDataChangedEventFilterRegistrationBean() {
FilterRegistrationBean<FireSysDataChangedEventFilter> registrationBean = new FilterRegistrationBean<FireSysDataChangedEventFilter>();
registrationBean.setFilter(new FireSysDataChangedEventFilter(fireSysDataChangedEventService));
registrationBean.addUrlPatterns("/api/*");
registrationBean.setOrder(5);
return registrationBean;
}
定义过滤器
java
public class FireSysDataChangedEventFilter implements Filter {
private Logger logger = LoggerFactory.getLogger(this.getClass());
private IFireSysDataChangedEventService fireSysDataChangedEventService;
public FireSysDataChangedEventFilter(IFireSysDataChangedEventService fireSysDataChangedEventService) {
super();
this.fireSysDataChangedEventService = fireSysDataChangedEventService;
}
@Override
public void init(FilterConfig arg0) throws ServletException {
}
@Override
public void destroy() {
}
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(req, resp);
fireSysDataChangedEventIfNecessary(req);
}finally {
SysDataChanged.reset();
}
}
private void fireSysDataChangedEventIfNecessary(ServletRequest req) {
try {
if(SysDataChanged.isChanged()) {
HttpServletRequest hreq = (HttpServletRequest)req;
String uri = hreq.getRequestURI();
logger.info("完成请求{}后,基础数据有变更,需要向其他系统发出清除缓存的消息",uri);
fireSysDataChangedEventService.sysDataChanged();
}
}catch(Exception e) {
e.printStackTrace();
logger.error("fireSysDataChangedEventIfNecessary错误{}",e.getMessage());
}
}
}
定义清除缓存服务
java
public interface IFireSysDataChangedEventService {
/**
* 基础数据发生变更
*/
void sysDataChanged();
}
java
@Service
@ConfigurationProperties(prefix="docin-sys.config",ignoreUnknownFields=true)
public class FireSysDataChangedEventService implements IFireSysDataChangedEventService {
private Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private ISysDataVersionService sysDataVersionService;
@Autowired
private RestTemplate restTemplate;
@Autowired
private AsyncTaskComponent asyncTaskComponent;
//这里的url是再配置文件配置的
//清理缓存urls
private Map<String, List<String>> clearcacheurls = new HashMap<>();
public void setClearcacheurls(Map<String, List<String>> clearcacheurls) {
this.clearcacheurls = clearcacheurls;
}
@SuppressWarnings("unchecked")
@Override
public void sysDataChanged() {
long lastTimestamp = System.currentTimeMillis();
//更新系统版本时间戳
sysDataVersionService.updateLastTimestampOfSysData(lastTimestamp);
logger.info("成功更新系统版本时间戳为{}",lastTimestamp);
if(MapUtils.isEmpty(clearcacheurls)) return;
for (Map.Entry<String,List<String>> e: clearcacheurls.entrySet()) {
asyncTaskComponent.runTaskInThreadPool((d)->{
String name=(String)d.get("name");
List<String> urls=(List<String>)(d.get("urls"));
for (String url : urls) {
try {
restTemplate.getForObject(url, String.class);
logger.info("向服务["+name+"].["+url+"]发送清理缓存请求成功");
} catch (Exception e2) {
logger.error("向服务["+name+"].["+url+"]发送清理缓存请求失败,",e2);
}
}
return null;
}, Utils.buildMap("name",e.getKey(),"urls",e.getValue()));
}
}
}
properties
docin-sys.config.clearcacheurls.docin-xfxt[0]=http://localhost:8088/docin-xfxt/syscache/clear
docin-sys.config.clearcacheurls.docin-xfxt[1]=http://localhost:8088/docin-xfxt/syscache/clear
docin-sys.config.clearcacheurls.docin-sys[0]=http://localhost:8088/docin-sys/syscache/clear
docin-sys.config.clearcacheurls.docin-sys[1]=http://localhost:8088/docin-sys/syscache/clear
docin-sys.config.clearcacheurls.docin-portal[0]=http://localhost:8088/docin-portal/syscache/clear
docin-sys.config.clearcacheurls.docin-stat[0]=http://localhost:8088/docin-portal/syscache/clear
docin-sys.config.clearcacheurls.hlw-xfxt[0]=http://localhost:8089/hlw-xfxt/syscache/clear
docin-sys.config.clearcacheurls.hlw-sys[0]=http://localhost:8089/hlw-sys/syscache/clear
docin-sys.config.clearcacheurls.docin-xf-exchange[0]=http://localhost:8080/docin-xf-exchange/syscache/clear
具体清除缓存请求接口
java
@Api(value="公共基础请求-基础数据缓存处理",tags="公共基础请求")
@RestController
@RequestMapping(value="/syscache")
public class SysDataCacheController {
@Autowired
private CronClearSysDataCacheJob cronClearSysDataCacheJob;
@ApiOperation(value="清理基础数据缓存", httpMethod="GET")
@RequestMapping(value="/clear", method=RequestMethod.GET)
public ApiResultDTO<RequestMsgTempVO> clearSyscache(HttpServletRequest request){
return RestAPITemplate.restapi(()->{
cronClearSysDataCacheJob.refreshCache();
return null;
});
}
}
清除本地缓存
java
@Autowired
private ISysDataVersionService sysDataVersionService;
//本地所获得基础数据最后更新时间戳
private volatile long lastSysTimestamp = -1L;
private volatile boolean refreshing=false;
@Async("taskExecutor")//使用线程池 启动
public void refreshCache() {
logger.info("定时刷新令牌任务启动");
if(this.refreshing) {
logger.error("之前刷新任务还在堵塞中....");
return;
}
try {
this.refreshing = true;
long lastSysTimestampOfServer = sysDataVersionService.getLastTimestampOfSysData();
if(this.lastSysTimestamp!=lastSysTimestampOfServer) {
this.lastSysTimestamp = lastSysTimestampOfServer;
SysDataCacheAspect.clearCache();
logger.warn("本地基础数据缓存已经清理,清理后本地基础数据版本为{}",lastSysTimestamp);
Thread.sleep(5000);//防止攻击
}else {
logger.info("本地基础数据缓存的版本与基础数据服务一致,无需清理,本地基础数据版本为{}",lastSysTimestamp);
}
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
this.refreshing=false;
}
}
缓存逻辑
java
@Aspect //定义一个切面
@Configuration
public class SysDataCacheAspect {
private static Logger logger = LogManager.getLogger(SysDataCacheAspect.class);
private static final Map<String,Boolean> cacheFileNames = new ConcurrentHashMap<String, Boolean>();
private static LoadingCache<String,Object> cache = null;
static {
// CacheLoader 初始化
CacheLoader<String, Object> cacheLoader = new CacheLoader<String, Object>() {
@Override
// load方法的作用是在通过get方法从LoadingCache获取不到值时去加载该值并放入缓存。
public Object load(String key) throws Exception {
return null;
}
};
cache = CacheBuilder.newBuilder()
// 设置容量大小
.maximumSize(80000)
//默认一天后过期
.expireAfterWrite(10, TimeUnit.DAYS)
.removalListener(new RemovalListener<String, Object>() {
@Override
public void onRemoval(RemovalNotification<String, Object> notification) {
if(notification.getValue()!=null && notification.getValue() instanceof CacheFile) {
CacheFile cacheFile = (CacheFile)notification.getValue();
removeCacheFile(cacheFile.fileName);
}
}
})
// 加载器配置
.build(cacheLoader);
}
private String normalizedArgsStr(Object[] args){
if(args==null || args.length==0) {
return "";
}
Object[] normalizedArgs = new Object[args.length];
if(args!=null) {
for(int i=0;i<args.length;i++) {
Object arg = args[i];
if(arg instanceof AccessTokenUser) {
AccessTokenUser user = (AccessTokenUser)arg;
normalizedArgs[i]= user.getUserId();
}else {
normalizedArgs[i]=arg;
}
}
}
return JsonConverter.toJsonStr(normalizedArgs);
}
@Around("execution(* (@com.xysd.bizbase.annotation.SysDataCache *).*(..)) || execution(@com.xysd.bizbase.annotation.SysDataCache * *(..))")
public Object process(ProceedingJoinPoint point) throws Throwable {
String className = point.getSignature().getDeclaringTypeName();
String methodName = point.getSignature().getName();
Object[] args = point.getArgs();
String key = className+"_$_"+methodName+"_$_"+(normalizedArgsStr(args));
Object cached = cache.getIfPresent(key);
if(methodName.endsWith("_dontCache")){
return point.proceed(args);
}
if(cached!=null) {
if(cached instanceof CacheFile) {
CacheFile cachedFile = (CacheFile)cached;
Object cachedData = readCachedData(cachedFile);
if(cachedData==null) {
//读取缓存失败
return point.proceed(args);
}else {
return cachedData;
}
}else {
return cached;
}
}else {
cached = point.proceed(args);
if(cached instanceof ApiResultDTO){
if(((ApiResultDTO<?>) cached).getData() == null) return cached;
}
if(cached!=null) {
try {
CacheFile cachedFile = cacheToDiskIfNecessary(cached);
if(cachedFile!=null) {
cache.put(key, cachedFile);
}else {
cache.put(key, cached);
}
}catch(Exception e) {
logger.error("缓存失败,失败信息{}",e.getMessage());
e.printStackTrace();
}
}
return cached;
}
}
private Object readCachedData(CacheFile cachedFile) {
String fileName = cachedFile.getFileName();
String absolutePath = getAbsoluteCacheFilePath(fileName);
File f = new File(absolutePath);
InputStream in = null;
ObjectInputStream oin = null;
try {
in = new FileInputStream(f);
oin = new ObjectInputStream(in);
Object cachedData = oin.readObject();
return cachedData;
}catch(Exception e) {
logger.error("读取缓存序列化文件失败,失败信息:{}",e.getMessage());
e.printStackTrace();
return null;
}
finally {
Utils.clean(in,oin);
}
}
/**
* 当value序列化后占用字节大于50K时写入磁盘进行缓存
* @param value
* @return
*/
private CacheFile cacheToDiskIfNecessary(Object value) {
int cachThreadshold = 50*1024;
ByteArrayOutputStream bos = null ;
ObjectOutputStream oos = null;
try {
bos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(bos);
oos.writeObject(value);
oos.flush();
byte[] byteArray = bos.toByteArray();
if(byteArray!=null && byteArray.length>cachThreadshold) {
return buildCacheFile(byteArray);
}else {
return null;
}
}catch(Exception e) {
throw new RuntimeException(e);
}finally {
Utils.clean(bos,oos);
}
}
private CacheFile buildCacheFile(byte[] byteArray) {
String fileName = "syscachefile_"+Utils.getUUID("");
String absolutePath = getAbsoluteCacheFilePath(fileName);
File f = new File(absolutePath);
OutputStream out = null;
try {
if(!f.getParentFile().exists()) {
f.getParentFile().mkdirs();
}
out = new FileOutputStream(f);
out.write(byteArray);
out.flush();
cacheFileNames.put(fileName, true);
return new CacheFile(fileName);
}catch(Exception e) {
throw new RuntimeException(e);
}finally {
Utils.clean(out);
}
}
private static String getAbsoluteCacheFilePath(String fileName) {
String sysCacheBaseDir = Utils.getTmpDirRoot()+"/sysDataCache";
return sysCacheBaseDir+"/"+fileName;
}
public static void removeCacheFile(String fileName) {
if(StringUtils.isNoneBlank(fileName)) {
cacheFileNames.remove(fileName);
String absolutePath = getAbsoluteCacheFilePath(fileName);
File f = new File(absolutePath);
try {
if(f.exists() && f.isFile()) {
f.delete();
}
}catch(Exception e) {
//删除失败不做任何处理
e.printStackTrace();
}
}
}
/**
* 清空缓存
*/
public static final void clearCache() {
for(String fileName:cacheFileNames.keySet()) {
removeCacheFile(fileName);
}
cacheFileNames.clear();
cache.invalidateAll();
}
public static class CacheFile implements Serializable {
private static final long serialVersionUID = -6926387004863371705L;
private String fileName;
public CacheFile(String fileName) {
super();
this.fileName = fileName;
}
public String getFileName() {
return fileName;
}
}
}
拦截器
Hibernate的拦截器 监听实体变化
java
public class SysEntityUpdateListener extends EmptyInterceptor {
/**
*
*/
private static final long serialVersionUID = -7428554904158765594L;
@Override
public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
super.onDelete(entity, id, state, propertyNames, types);
SysDataChanged.changed();
}
@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types) {
SysDataChanged.changed();
return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
}
@Override
public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
SysDataChanged.changed();
return super.onSave(entity, id, state, propertyNames, types);
}
@Override
public void postFlush(Iterator entities) {
super.postFlush(entities);
}
@Override
public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types) {
return super.findDirty(entity, id, currentState, previousState, propertyNames, types);
}
@Override
public void onCollectionRemove(Object collection, Serializable key) throws CallbackException {
SysDataChanged.changed();
super.onCollectionRemove(collection, key);
}
@Override
public void onCollectionUpdate(Object collection, Serializable key) throws CallbackException {
SysDataChanged.changed();
super.onCollectionUpdate(collection, key);
}
}
记录是否要清理缓存
java
public class SysDataChanged {
private static final ThreadLocal<Boolean> store = new ThreadLocal<Boolean>();
/**
* 当前线程已经更改过基础数据
*/
public static final void changed() {
store.set(true);
}
/**
* 重置
*/
public static final void reset() {
store.set(false);
}
/**
* 当前线程是否更改过基础数据
* @return
*/
public static final boolean isChanged() {
return Boolean.TRUE.equals(store.get());
}
}
通过SysEntityUpdateListener监听实体变化,更改SysDataChanged.store.set(true)。当用户请求后端接口时,通过FireSysDataChangedEventFilter过滤器判断SysDataChanged.store.get()==true,从而触发restTemplate.getForObject(url, String.class)请求,更新各个项目本地的基础数据缓存SysDataCacheAspect.clearCache()。