package com.gkhy.safePlatform.config.security;
|
|
import com.alibaba.fastjson.JSONArray;
|
import com.alibaba.fastjson.JSONObject;
|
import com.gkhy.safePlatform.account.rpc.apimodel.AccountAuthService;
|
import com.gkhy.safePlatform.account.rpc.apimodel.AccountAuthService;
|
import com.gkhy.safePlatform.commons.co.ContextCacheAuthority;
|
import com.gkhy.safePlatform.commons.co.ContextCacheUser;
|
import com.gkhy.safePlatform.commons.enums.RedisKeyEnum;
|
import com.gkhy.safePlatform.commons.enums.ResultCodes;
|
import com.gkhy.safePlatform.commons.exception.BusinessException;
|
import com.gkhy.safePlatform.commons.utils.RPCUtils;
|
import com.gkhy.safePlatform.commons.utils.RequestContextHolder;
|
import com.gkhy.safePlatform.commons.utils.StringUtils;
|
import com.gkhy.safePlatform.commons.vo.ResultVO;
|
import com.gkhy.safePlatform.config.redis.RedisUtils;
|
import org.apache.dubbo.config.annotation.DubboReference;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.core.GrantedAuthority;
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.stereotype.Component;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
|
import javax.servlet.FilterChain;
|
import javax.servlet.ServletException;
|
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletResponse;
|
import java.io.IOException;
|
import java.io.PrintWriter;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.List;
|
|
/**
|
* @Description: token登录过滤器
|
*/
|
@Component
|
public class TokenAuthenticationFilter extends OncePerRequestFilter {
|
|
@Autowired
|
private TokenConfig tokenConfig;
|
@DubboReference(check = false)
|
private AccountAuthService userAccountService;
|
@Autowired
|
private RedisUtils redisUtils;
|
|
|
|
@Override
|
protected void doFilterInternal(HttpServletRequest req, HttpServletResponse resp, FilterChain chain) throws IOException, ServletException {
|
|
try {
|
//获取当前认证成功用户权限信息
|
UsernamePasswordAuthenticationToken authRequest = getAuthentication(req, resp);
|
if (authRequest != null) {
|
SecurityContextHolder.getContext().setAuthentication(authRequest);
|
}
|
// 执行下一个 filter 过滤器链
|
chain.doFilter(req, resp);
|
} catch (BusinessException e) {
|
// 返回异常
|
this.writeJSON(req, resp, new ResultVO<>(e.getCode(),e.getMessage()));
|
}
|
|
}
|
|
|
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest req,HttpServletResponse resp) {
|
// header获取token
|
String authToken = req.getHeader(tokenConfig.getHeader());
|
if(authToken != null) {
|
// 登录成功时,会将权限数据存入redis
|
// 这里是验证获取权限信息
|
// 1.从redis中获取对应该用户的权限信息
|
String accessTokenKey = RedisKeyEnum.authKey(RedisKeyEnum.AUTH_TOKEN, authToken);
|
Object o = redisUtils.get(accessTokenKey);
|
// 2.token是否存在
|
if (o == null) {
|
// 是否存在 uid未登录
|
throw new BusinessException(ResultCodes.CLIENT_CREDENTIALS_TOKEN_INVALID);
|
}else{
|
// todo 可以不转换,建议rpc传入string
|
String uid = o.toString();
|
Long userId = Long.valueOf(uid);
|
String accessUserKey = RedisKeyEnum.authKey(RedisKeyEnum.AUTH_USER, userId);
|
// 这里不做用户信息的token判断 放入登录
|
Long expireSecondsLeft = redisUtils.getExpireTime(accessTokenKey);
|
// 60m 内请求则续期 时长为原本的有效时间
|
if (expireSecondsLeft != null && 0L < expireSecondsLeft && expireSecondsLeft < 60 * 60) {
|
// 重置token:uid
|
redisUtils.resetKeyExpireTime(accessTokenKey, tokenConfig.getExpiration());
|
// 重置uid:userInfo
|
redisUtils.resetKeyExpireTime(accessUserKey, tokenConfig.getExpiration());
|
}
|
// 获取用户信息
|
Object oo = redisUtils.get(accessUserKey);
|
// 初始化
|
ContextCacheUser contextCacheUser = null;
|
if (oo == null) {
|
// 业务逻辑上是不会空的
|
// 实际操作可能会手动清空
|
ResultVO<ContextCacheUser> rpcResultVo = userAccountService.getCacheUserDetailByUid(userId);
|
// 调用rpc返回的数据 没有token 所以得至少续上这次token
|
contextCacheUser = this.getRpcResult(rpcResultVo);
|
// 因为手动清空等原因,可能会丢失其他token数据,就不去一一搜索这个uid的token了
|
contextCacheUser.setAccessToken(Collections.singletonList(authToken));
|
}else{
|
// 正常的实际场景必定会走这里
|
// 推荐用jackson
|
contextCacheUser = JSONObject.parseObject(oo.toString(), ContextCacheUser.class);
|
}
|
// threadLocal存入用户信息
|
RequestContextHolder.contextUserLocal.set(contextCacheUser);
|
// security对象中存入登陆者信息
|
return new UsernamePasswordAuthenticationToken(contextCacheUser, authToken, contextCacheUser.getAuthorities());
|
|
}
|
}
|
return null;
|
}
|
|
/**
|
* 获取rpc 返回的用户数据
|
*
|
* @param rpcResultVo rpc返回数据
|
* @return 用户准备缓存的数据
|
*/
|
private ContextCacheUser getRpcResult(ResultVO<ContextCacheUser> rpcResultVo) {
|
if (!rpcResultVo.getCode().equals(ResultCodes.OK.getCode())) {
|
throw new BusinessException(rpcResultVo.getCode(), rpcResultVo.getMsg());
|
}
|
if (rpcResultVo.getData() == null) {
|
throw new BusinessException(ResultCodes.RPC_DATA_NULL);
|
}
|
if (rpcResultVo.getData() instanceof ContextCacheUser) {
|
return (ContextCacheUser) rpcResultVo.getData();
|
} else {
|
throw new BusinessException(ResultCodes.RPC_DATA_TYPE_NOT_MATCH);
|
}
|
|
}
|
|
|
protected void writeJSON(HttpServletRequest req,
|
HttpServletResponse resp,
|
ResultVO resultVO) throws IOException {
|
// 设置编码格式
|
resp.setContentType("text/json;charset=utf-8");
|
// 处理跨域问题
|
resp.setHeader("Access-Control-Allow-Origin", "*");
|
resp.setHeader("Access-Control-Allow-Methods", "POST, GET");
|
|
//输出JSON
|
PrintWriter out = resp.getWriter();
|
out.write(JSONObject.toJSONString(resultVO));
|
out.flush();
|
out.close();
|
}
|
}
|