Shiro认证跳转问题排查(论练习的重要性)

文章讲述了在使用Shiro进行认证和权限校验时,遇到登录成功后跳转异常的问题。经过排查,问题出在使用Ajax进行登录请求,导致无法重定向。解决方案是改用表单提交方式进行登录,避免了重定向问题。作者强调了对技术细节理解和实践的重要性。

背景        

        前不久接触到一个新的项目,翻看了下项目框架和代码,发现很多地方用到了shiro,它不仅可以用来获取登录信息,还可以做权限校验。shiro的使用也极其方便,使用公共方法SecurityUtils.getSubject()便可获取Subject,通过它可以进行认证和授权相关的一系列操作,使用@RequiresPermissions注解就可以对请求进行权限校验。但对它的了解也仅仅是项目所展现的一知半解,对它还需要一个系统的了解,它的配置、使用以及底层。于是在网上查看了shiro教学的课程,并做了一个demo项目来实践。

        但仅仅在认证成功之后的跳转上就出现了问题,并为此耗费了一周多的时间排查。。。特此记录下。

配置

pom.xml

 shiro配置

package com.bruce.shiro.config;

import com.bruce.shiro.filter.ShiroFilter;
import com.bruce.shiro.security.CustomRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    @Bean
    public CustomRealm customRealm() {
        return new CustomRealm();
    }

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(customRealm());
        return securityManager;
    }

    @Bean
    public ShiroFilter formAuthenticationFilter() {
        return new ShiroFilter();
    }

    /**
     * 配置ShiroFilter过滤器
     * @return ShiroFilter过滤器bean
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        //配置安全管理器
        factoryBean.setSecurityManager(defaultWebSecurityManager());
        Map<String, String> filterChainMap = new LinkedHashMap<>();
        //配置系统公共资源
        filterChainMap.put("/static/**", "anon");
        //配置系统受限资源
        filterChainMap.put("/user/**", "authc");
        //配置认证请求
        factoryBean.setLoginUrl("/user/login");
        factoryBean.setSuccessUrl("/");
        factoryBean.setFilterChainDefinitionMap(filterChainMap);

        return factoryBean;
    }
}

请求

@GetMapping("login")
public String login(UserEntity user, Model model) {
    //设置当前登录对象
    Subject subject = SecurityUtils.getSubject();
    if (subject.isAuthenticated()) {
    return "index";
    } else if (StringUtils.isAnyBlank(user.getLoginName(), user.getPassword())) {
        return "login";
    }
    try {
        subject.login(new UsernamePasswordToken(user.getLoginName(), user.getPassword()));
    } catch (UnknownAccountException ae) {
        log.error("登录失败:{}", ae.getMessage());
        model.addAttribute("msg", "用户名错误!");
        return "login";
    } catch (IncorrectCredentialsException pe) {
        model.addAttribute("msg", "密码错误!");
        return "login";
    }
    return "index";
}

最终效果

直接访问首页和登录的请求跳转没有问题,但登录认证成功后理论上应该跳转localhost:8080/,但总是跳转localhost:8080/index.html,由于index.html在templates目录,导致请求404,通过断点检查,是有走return "index"的,理论上thymeleaf解析后应该能找到/templates/index.html。

排查过程

第一次排查

        以为是shiro认证问题,在网上也的确找到很多关于shiro认证成功跳转出错的文章,大部分的解决方案是继承FormAuthenticationFilter,重写issueSuccessRedirect,如图:

@Override
protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
    WebUtils.issueRedirect(request, response, getSuccessUrl());
}

修改后依然没有效果,断点检查发现根本没有进issueSuccessRedirect方法。根据该方法查找使用位置发现isLoginSubmission判断为false。

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
    if (isLoginRequest(request, response)) {
        //此处判断为false
        if (isLoginSubmission(request, response)) {
            if (log.isTraceEnabled()) {
                log.trace("Login submission detected.  Attempting to execute login.");
            }
            return executeLogin(request, response);
        } else {
            ...
        }
        ...
    }
}

默认只有请求方式为POST时返回true

protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
    return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
}

于是又重写了isLoginSubmission方法,加上了GET请求方式。之后又发现请求报认证失败,原来createToken中默认获取的是username和password的值进行认证,而我是将用户名存在loginName中,于是我又重写了createToken方法,让其获取loginName的属性值。

protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {
    //默认从request获取username属性值
    String username = getUsername(request);
    //默认从request获取password属性值
    String password = getPassword(request);
    return createToken(username, password, request, response);
}

最后认证成功,但依然跳转localhost:8080/index.html,请求404异常

第二次排查

        我又想是不是thymeleaf配置问题,然后我又在网上找到很多关于使用thymeleaf无法找到templates目录下页面的文章。有说是配置了指定模板目录导致无法找到的,但我使用的是默认配置,页面也是放置在templates下。有说是thymeleaf依赖版本问题,与springboot版本不兼容导致,但我并没有配置版本,而是使用springboot的spring-boot-dependencies版本依赖管理,理论上不可能出现不兼容。本着死马当活马医的想法,我还是将我的springboot版本改为博主的版本,2.5.6改为2.4.1,看看会不会发生奇迹。然而奇迹并没有眷顾我。

第三次排查

        没办法,我移除了shiro及thymeleaf依赖,决定先以较原始的方式让项目正常运行,然后再不断增加依赖,看看是哪个环节出了问题。问题来了,最终还是无法跳转。这让我改变了之前的看法,也许是前端请求的问题。于是我对比了跳转成功和不成功的请求,发现有那么一处不同,那就是登录请求是用的ajax方式调用的,我搜索之下才想起来,ajax请求是局部刷新,默认是不支持重定向的。我晕死,这个之前是学过的,我却没有想到,甚至特地用了ajax做登录请求,导致我在一个shiro认证上耗费一周多。。。

解决方案

        将ajax方式的登录请求换成表单提交,shiro认证成功跳转,也不需要重写FormAuthenticationFilter,登录请求也可修改为如下:

@GetMapping("login")
public String login() {
    return "login";
}

 因为shiro认证登录在AuthenticationFilter的executeLogin就已进行,若认证成功则不会走登录请求,若认证失败才会走登录请求。但需要注意,若你的请求方式是GET或请求参数不是username、password、isRememberMe等默认参数,则必须重写FormAuthenticationFilter。可参考前文“第一次排查”所述。

总结

        在正常工作中,由于多是使用现成的组件,以及架构大佬搭好的框架,所以我忽略了很多技术上的细枝末节,导致我会遇到一些看似奇奇怪怪的问题,其实问题奇奇怪怪,多半是自身技术不到位导致的。还是得多做练习,多自己搭项目,这样不仅会对已掌握的技术更加熟练,还会补全一些之前不知道的技术。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值