SpringBoot+Shiro瞎折腾——不使用Shiro的Filter模式

本文深入探讨Shiro框架中Subject如何与线程绑定,解释其设计原理及在前后端分离项目中的应用。通过手写实现,理解Shiro-web中Filter的作用及SessionID的获取方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


       想要实现基于SpringBoot+Shiro+Vue的前后端分离技术,网上教程还是不少的,在实现成功后,多问了个问题,就有了这篇文章。问题如标题,如果不使用Shiro提供的Filter模式会怎么样:

    前后端分离,如果没用RESTful架构,使用JSON rpc等类似协议,
    其实是不需要Shiro基于URL路径去判断用户权限,只需要使用注解来
    标识函数权限即可,如:
    //统一的接口入口
    @RequestMapping(value = "/api",produces = "application/json;charset=UTF-8")
    @ResponseBody
    public String api(HttpServletRequest request) {
        //调用不同接口
    }
    //需要用户已登录
    @RequiresAuthentication
    public Result getUserInfo(JSONObject json){
        //do something
    }

       所以,我注释掉了Shiro的Filter注册部分,然后理所当然的认为直接就可用了,也理所当然的被打脸了。原因是并没有真正了解Shiro的设计模式与其在Shiro-web中所做的事。

一、总结:Subject其实是绑定线程的

       其实如果多想想,这样设计是符合Shiro定位的。Shiro本意就是不依赖任何容器与环境,可以在任意的Java环境中提供用户权限认证,如果Shiro是绑定进程的,独立App可能还没问题,因为只需要提供给一个用户使用,但服务器模式等需要为多用户提供用户鉴权的,就没办法实现了。
       基于这个前提,Shiro-web的Filter,利用Filter的机制,在所有请求真实处理前,根据SessionID获取Session(SessionID获取方式可自定义,默认使用Cookie,不需要干预;也可以修改为Header、Query等等),构造一个Subject绑定到线程中,后续的权限相关处理,就都是针对这个用户的了。在该线程中,任意位置都可以直接调用:Subject subject = SecurityUtils.getSubject();来获取用户的Subject。
       Request处理完后,还需要对线程进行处理,将相关数据与线程解绑。
       关键代码:

//创建Subject
//org.apache.shiro.web.servlet.AbstractShiroFilter#doFilterInternal
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
            throws ServletException, IOException {
    //....
    //封装一下Request与Response
    final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
    final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
    //生成Subject,生成时会获取SessionID,如果存在,根据Session生成Subject
    final Subject subject = createSubject(request, response);

    //用Subject提供的接口来绑定Subject到线程,并执行后续操作
    subject.execute(new Callable() {
        public Object call() throws Exception {
            updateSessionLastAccessTime(request, response);
            executeChain(request, response, chain);
            return null;
        }
    });
    //....
}

//其中,使用subject.execute来执行代码,保证线程与数据的绑定与解绑
//org.apache.shiro.subject.support.SubjectCallable#call
public V call() throws Exception {
    try {
        threadState.bind();
        return doCall(this.callable);
    } finally {
        threadState.restore();
    }
}

//获取SessionID
/**
getSessionId:273, DefaultWebSessionManager (org.apache.shiro.web.session.mgt)
...
resolveSession:446, DefaultSecurityManager (org.apache.shiro.mgt)
...
buildSubject:845, Subject$Builder (org.apache.shiro.subject)
*/
//org.apache.shiro.web.session.mgt.DefaultWebSessionManager#getSessionId(org.apache.shiro.session.mgt.SessionKey)
@Override
public Serializable getSessionId(SessionKey key) {
    Serializable id = super.getSessionId(key);
    if (id == null && WebUtils.isWeb(key)) {
        ServletRequest request = WebUtils.getRequest(key);
        ServletResponse response = WebUtils.getResponse(key);
        id = getSessionId(request, response);
    }
    return id;
}

       题外话:Shiro-web中的Session是直接利用了HttpSession,对其做了一层封装。Shiro默认也提供了一种Session——SimpleSession。

二、手写实现简单验证

       先约定一下接口:

  • 接口统一使用"/api",POST方式传入JSON数据,使用cmd来区分实际接口,如:{"cmd":"logout"}
  • SessionID前端使用X-TOKEN头传入

       通用配置就不列出来了,Shiro配置相当简化,只要提供一个Realm的配置即可:

1. Shiro配置

@Configuration
@Import({ShiroBeanConfiguration.class,
        ShiroConfiguration.class})
public class ShiroConfig {

    /**
     * 自定义安全域,用户验证、权限等数据在此提供
     * @return
     */
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }

    /**
     * 凭证匹配器,这里的hash算法用来将前台传入的数据做处理
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(1);//散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }
    /**
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

//绑定SecurityManager
@Resource
private SecurityManager securityManager;

@PostConstruct
private void initStaticSecurityManager() {
    SecurityUtils.setSecurityManager(securityManager);
}

2.使用

       用的时候也很简单

  • /api接口中,绑定Subject到进程,再调用后续服务
//首先解绑验证数据,保证Request开始的纯洁性
ThreadContext.unbindSubject();
if(StringUtils.isNotEmpty(request.getHeader("X-TOKEN"))){
    //指定SessionID创建Subject
    Subject subject = (new Subject.Builder()).sessionId(request.getHeader("X-TOKEN")).buildSubject();
    if(subject != null){
        //验证数据绑定线程数据
        ThreadContext.bind(subject);
    }
}
  • 用户登录
Subject subject = SecurityUtils.getSubject();
subject.login(new UsernamePasswordToken(username,password));
  • 权限控制,直接对相关函数做注解
//需要登录
@RequiresAuthentication
//需要权限
@RequiresPermissions("user:add")
//需要角色
@RequiresRoles("admin")

三、再总结

       这当然只是自己的瞎折腾,web项目还是建议直接使用Shiro-web来实现,相当简洁优雅。不过生命在于折腾,对于Shiro的设计模式、web验证等又多了许多新的认识。

web.xml配置 因为我们是与spring进行集成的,而spring的基本就是web项目的xml文件。所以我们在web.xml中配置shiros的过滤拦截。正常情况下,我们需要将shirofilter配置在所有的filter前面,当然和encodingFilter这个filter区分前后的。因为两者互相影响的。spring-shiro.xml 这里我们将来看看spring-shiro.xml的配置,这里我采取倒叙的方式讲解,我觉的倒叙更加的有助于我们理解代码。首先我们还记得在web.xml中配置的那个filter吧,名字shiroFilter,对spring-shiro.xml配置文件就是通过这个filter展开的。首先我们在web.xml配置的过滤器实际上是配置ShiroFilterFactoryBean,所以在这里需要将ShiroFilterFactoryBean定义为shiroFilter <!-- Shiro的核心安全接口,这个属性是必须的 --> <!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.html"页面 --> <!-- 登录成功后要跳转的连接 --> <!-- 用户访问未对其授权的资源时,所显示的连接 --> <!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp,然后用[玄玉]登录后访问/admin/listUser.jsp就看见浏览器会显示unauthor.jsp --> <!-- Shiro连接约束配置,即过滤链的定义 --> <!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.youkuaiyun.com/jadyer/article/details/12172839 --> <!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 --> <!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 --> <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter --> /statics/**=anon /login.html=anon /sys/schedule.html=perms[sys:schedule:save] /sys/login=anon /captcha.jpg=anon /**=authc
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值