前记:一般来说程序员都有记录学习历程、分享技术博客的习惯,刚入行学习时老师也是如此教导的,既可以拓展及整理思路以保持良好的思维习惯,又可同时分享技术,为他人提供些许帮助。今天恰好时间充足,特地开始我的第一篇文章。
公司后台要进行前后端分离,采用的模式为:前后端分离为独立项目,使用NGINX对访问URL进行切割,静态文件直接由NGINX返回,json数据由后台返回。
由于前后分离,原项目中使用的springSecurity权鉴框架被放弃,需要自己实现授权及认证管理。
主要分为如下三个步骤
1. 使用token代替默认sessionId,实现会话维持。创建tokenManager,该管理器实现token的生成,检查,删除及刷新。
注意:此处使用ConcurrenHashMap实现token保存,需自己实现过期删除避免内存泄漏
@Component
public class DefaultTokenManager implements TokenManager {
private static final Logger log = LoggerFactory.getLogger(DefaultTokenManager.class);
//用于存放token的map,一期简单使用map实现,通用的做法是将token保存在redis中
private static Map<String, Long> tokenMap = new ConcurrentHashMap<String, Long>();
//token超时时间
@Value("${token_time_out}")
private long tokenTimeOut;
/**
* @see TokenManager#createToken()
*/
public synchronized String createToken() {
String token = UUID.randomUUID().toString().replaceAll("-", "");
while (tokenMap.containsKey(token)) {
token = UUID.randomUUID().toString().replaceAll("-", "");
}
tokenMap.put(token, System.currentTimeMillis() + tokenTimeOut + 5000);
return token;
}
/**
* @see TokenManager#checkToken(String)
*/
public void checkToken(String token) {
if (StringUtils.isEmpty(token) || !tokenMap.containsKey(token))
throw new TokenInvalidException();
Long tokenValue = tokenMap.get(token);
if (null == tokenValue || System.currentTimeMillis() > tokenValue) {
//清除token信息
tokenMap.remove(token);
//清除token所对应的用户信息
CspUserHolder.removeToken(token);
throw new TokenTimeoutException();
}
}
/**
* @see TokenManager#deleteToken(String)
*/
@Override
public void deleteToken(String token) {
tokenMap.remove(token);
}
/**
* @see TokenManager#refreshToken(String)
*/
@Override
public void refreshToken(String token) {
tokenMap.put(token, System.currentTimeMillis() + tokenTimeOut);
}
}
2. 自定义切面,拦截数据请求,进行token及权限检查
@Component
@Aspect
public class SecurityAspect {
private static final Logger log = LoggerFactory.getLogger(SecurityAspect.class);
@Autowired
private TokenManager tokenManager;
@Autowired
private HttpServletRequest servletRequest;
@Autowired
private CspPermissionHolder cspPermissionHolder;
@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public Object execute(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
Method method = methodSignature.getMethod();
log.info("方法:" + method.getName() + "被调用开始,是需要否授权:" + !method.isAnnotationPresent(IgnoreSecurity.class));
if (method.isAnnotationPresent(IgnoreSecurity.class)) {
Object result = pjp.proceed();
log.info("方法:" + method.getName() + "被调用完成");
return result;
}
//检查token
String token = CookieUtil.getCookieValue(servletRequest, Constant.DEFAULT_TOKEN_NAME);
try {
tokenManager.checkToken(token);
} catch (Exception e) {
if (e instanceof TokenInvalidException) {
log.error("token[" + token + "]" + "无效,授权失败");
return new Response().error("token无效", Response.CODE_TOKEN_INVALID);
}
if (e instanceof TokenTimeoutException) {
log.error("token[" + token + "]" + "过期,授权失败");
return new Response().error("token过期", Response.CODE_TOKEN_TIMEOUT);
}
}
tokenManager.refreshToken(token);
//检查系统权限
if (!cspPermissionHolder.hasPermission(servletRequest)) {
return new Response().error("请求失败,权限不足", Response.CODE_PERMISSION_DENY);
}
//调用环境设置及真正方法执行
CspAuthContext.set(CspUserHolder.fetchUserByToken(token));
Object result = pjp.proceed();
CspAuthContext.clear();
return result;
}
}
3.定义用户域,保存用户权限
public class CspUserHolder {
//key为token,值为用户信息,注意失效清除,避免内存泄漏
private static Map<String, CspSecurityDto> authContext = new ConcurrentHashMap<>();
//新增token用户配置
public static void registerToken(String token, CspSecurityDto cspUserDto) {
authContext.put(token, cspUserDto);
}
//获取token用户配置
public static CspSecurityDto fetchUserByToken(String token) {
return authContext.get(token);
}
//移除token用户配置
public static void removeToken(String token) {
authContext.remove(token);
}
}
4. 实现登录逻辑,创建token及缓存用户权限,将token返回给客户端
/**
* 登陆
*/
@RequestMapping("/login")
@IgnoreSecurity
public Response login(HttpServletRequest request, HttpServletResponse response, String username, String password) throws IOException {
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
return new Response().error("用户名和密码不能为空");
}
CspSecurityDto user = queryUserByName(username);
if (user == null) {
return new Response().error("用户不存在");
}
if (user.getPassword().equals(password)) {
String token = tokenManager.createToken();
CspUserHolder.registerToken(token, user);
CookieUtil.setCookie(request, response, Constant.DEFAULT_TOKEN_NAME, token);
} else {
return new Response().error("密码错误");
}
return new Response().success("登陆成功");
}
以上代码为不完整代码,另有userContext、permissionHolder进行用户权限的查询缓存在此不再赘述。此文仅提供此实现思路。
除自己实现权限控制,也可登录使用token,权限拦截使用shiro、springSecurity来做的方式,具体请参考度娘
转发注明出处,by kichun
网友评论