Shiro权限控制应用

       最近学习了解了一下Shiro,发现他在权限控制方面有很好的处理,现将相关的学习心得记录下来,作为自己的知识储备,也为有需要的小伙伴提供解决方案。

      Shiro 是一款简单易用的Java安全框架,可以帮助我们完成:认证、授权、加密、会话管理,你可以快速集成到任何应用程序——从最小的移动应用程序到最大的web应用程序。

       图一在很多地方都可以见到,他可以让我们从程序的角度来了解shiro怎样帮我们完成权限控制。

        Subject, 当前登录主体。

        SecurityManager,是Shiro的核心,一系列的认证、授权、加密、会话管理等操作,都是由他来统一调度。

        Realm,相当于安全数据源,Shiro需要从Realm中获取安全数据,如用户、角色、权限。如果用户需要访问某个接口,首先需要Realm告知登录用户是否合法(如密码校验等),其次需要Realm告知用户是否有权限访问接口。Realm需要开发者自己编写处理逻辑。

      


                                                                图一

      图二在很多地方也可以见到,这个可以反映Shiro的整体架构,各个组件的说明,可以查看开涛老师Shiro教程博客,上面写得很详细,大家可以查看他的博客,http://jinnianshilongnian.iteye.com/blog/2018936

                   

                                                                            图二

      了解了Shiro的整体架构后,我接下来说明一下自己遇到的三个问题及解决方案。

一、前后端分离

       项目中采用前后端分离的处理方法,并且前端是ajax跨域请求,无法带上cookie,虽然针对这点网上有解决办法,可是从安全角度考虑,也不想把sessionId保存在cookie中。查看Shiro默认的web会话管理器,DefaultWebSessionManager,可以发现Shiro默认是通过Cookie来获取sessionId,如果无法从cookie中获取,就会去查看url后是否带有JSESSIONID或jsessionid参数,来获取sessionId。我们都不满足,这样的话,原生的方法就无法获取sessionId,所以需要自己稍微改造一下获取sessionId的方法,如下所示,自行定义一个MyWebSessionManager,继承DefaultWebSessionManager类,更改获取sessionId的方法,最后需要将自定义的MyWebSessionManager注入到securityManager中。

public class MyWebSessionManager extends DefaultWebSessionManager{
.................
private Serializable getReferencedSessionId(ServletRequest request, ServletResponse response) {

    String id = WebUtils.toHttp(request).getHeader("token");
    if(Strings.isNullOrEmpty(id)){
        id = this.getSessionIdCookieValue(request, response);
    }
    if (id != null) {
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie");
    } else {
        id = this.getUriPathSegmentParamValue(request, "JSESSIONID");
        if (id == null) {
            String name = this.getSessionIdName();
            id = request.getParameter(name);
            if (id == null) {
                id = request.getParameter(name.toLowerCase());
            }
        }
        if (id != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "url");
        }
    }
    if (id != null) {
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
        request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
    }
    return id;
}
....................

}

二、会话集群管理

 后台部署在多台机器上,就涉及到session共享的问题。通过查看DefaultWebSessionManage源码,发现其父类DefaultSessionManager中含有SessionDAO属性,通过他可以对session进行管理,默认的是MemorySessionDAO,那么我们可以自己实现SessionDAO,注入到sessionManager中。如下所示,redisManager是普通的redis管理类,定义了自己的session储存类,最后需要将MyShiroSessionDAO注入到sessionManager中。

public class MyShiroSessionDAO extends EnterpriseCacheSessionDAO {

    static final String SESSION_KEY = "session:";

    //创建session
    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = super.doCreate(session);
        final String key = SESSION_KEY + session.getId().toString();
        setShiroSession(key,session);
        return sessionId;
    }

    //获取session
    @Override
    protected Session doReadSession(Serializable sessionId) {
        Session session = super.doReadSession(sessionId);
        if(session == null){
            final String key = SESSION_KEY + sessionId;
            session = getShiroSession(key);
        }
        return session;
    }

    //更新session
    @Override
    protected void doUpdate(Session session) {
        super.doUpdate(session);
        final String key = SESSION_KEY + session.getId();
        setShiroSession(key, session);
    }

    //删除session
    @Override
    protected void doDelete(Session session) {
        super.doDelete(session);
        final String key = SESSION_KEY + session.getId();
        byte[] keyBytes = key.getBytes();
        redisManager.deletBytes(keyBytes);
    }

    //获取session
    private Session getShiroSession(String key) {
        byte[] keyBytes = key.getBytes();
        Session session = null;
        byte[] sessionBytes = redisManager.getBytes(keyBytes);
        if(sessionBytes != null){
            session = (Session)SerializeUtil.unserialize(sessionBytes);
        }
        return session;
    }

    //保存session
    private void setShiroSession(String key, Session session){
        Integer expire = 1800;
        byte[] keyBytes = key.getBytes();
        byte[] sessionBytes = SerializeUtil.serialize(session);
        redisManager.setBytes(keyBytes, sessionBytes, expire);
    }

}
三、去掉登录时url中的JSESSIONID

   shiro配置文件中有一个loginUrl的配置,表示登录跳转的url,shiro默认的会在url后面加上“;JSESSIONID=”的后缀,看网上很多人说shiro1.4版本已经修改了这个bug,只要在sessionManager中的sessionIdUrlRewritingEnabled设置为false就可以了,只是我的好像没起作用,所以跟踪了源码后,发现到最后拼接跳转url的时候,会先判断ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED值,如果为false就不会在登录url后拼接JSESSIONID,于是可以覆写sessionManager中的onStart方法,如下所示,

 protected void onStart(Session session, SessionContext context) {

    super.onStart(session, context);
    if(!WebUtils.isHttp(context)) {
        log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response pair. No session ID cookie will be set.");
    } else {
        ServletRequest request = WebUtils.getRequest(context);
        //控制重定向到loginUrl时不带JSESSIONID
        request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED,isSessionIdUrlRewritingEnabled());
    }
}
下面这个方法是把shiro的源码粘贴上去,isEncodeable方法是判断url是否加上JSESSIONID后缀的其中一步。
protected boolean isEncodeable(String location) {
    if(Boolean.FALSE.equals(this.request.getAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED))) {
        return false;
    } else if(location == null) {
        return false;
    } else if(location.startsWith("#")) {
        return false;
    } else {
        HttpServletRequest hreq = this.request;
        HttpSession session = hreq.getSession(false);
        return session == null?false:(hreq.isRequestedSessionIdFromCookie()?false:this.doIsEncodeable(hreq, session, location));
    }
}
四、控制同一个账户只有一个用户在线

       这个就不细说了,主要结合redis,把登录的用户名和session保存在redis中,在realm的doGetAuthorizationInfo方法中进行判断


                
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值