美文网首页
前后端分离-自定义权限控制

前后端分离-自定义权限控制

作者: KICHUN | 来源:发表于2018-04-04 15:15 被阅读0次

前记:一般来说程序员都有记录学习历程、分享技术博客的习惯,刚入行学习时老师也是如此教导的,既可以拓展及整理思路以保持良好的思维习惯,又可同时分享技术,为他人提供些许帮助。今天恰好时间充足,特地开始我的第一篇文章。

公司后台要进行前后端分离,采用的模式为:前后端分离为独立项目,使用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

相关文章

网友评论

      本文标题:前后端分离-自定义权限控制

      本文链接:https://www.haomeiwen.com/subject/pmbkhftx.html