禁止ajax访问shiro管理的登录页面

在使用shiro的时候,对于用户权限的管理,相信很多人都已经很熟悉了。
今天,我这里简单的记录一下我自己调试过程中遇到的问题。主要是登录的操作,禁止通过ajax的方式进行访问。

shiro中,登录过程拒绝ajax的访问操作,主要在FormAuthenticationFilter里面实现的。更具体的说,应该是自己重写AccessControlFilter方法中的onAccessDenied方法。

为什么要禁止ajax的登录?安全的考虑!

先看看自己应用中重写的RdFormAuthenticationFilter,它继承于FormAuthenticationFilter,至于FormAuthenticationFilter和AccessControlFilter的关系,自己看shiro的源码吧。

public class RdFormAuthenticationFilter extends FormAuthenticationFilter {

    private String passwordParam;

    @Autowired
    @Qualifier("mqmKefuService")
    private IMqmKefuService mksService;
    
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue)
            throws Exception {
        if (request.getAttribute(getFailureKeyAttribute()) != null) {
            return true;
        }        

        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        if (isLoginSubmission(request, response)) {
            return executeLogin(request, response);
        } else {
            if ("XMLHttpRequest".equalsIgnoreCase(httpServletRequest.getHeader("X-Requested-With")) 
                    || request.getParameter("ajax") != null) {
                HttpServletResponse res = WebUtils.toHttp(response);
                res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
            return true;
        }        
    }

    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                ServletRequest request, ServletResponse response) throws Exception {        
        HttpServletRequest httpServletRequest = (HttpServletRequest)request;
        HttpServletResponse httpServletResponse = (HttpServletResponse)response;
        
        String username = (String)SecurityUtils.getSubject().getPrincipal();
        if(username == null){
            return true;
        }
        
        MqmKefu user = mksService.selectByUsername(username);
        
        SecurityUtils.getSubject().getSession().setAttribute(Constants.CURRENT_USER, user);        
                
        if ("XMLHttpRequest".equalsIgnoreCase(httpServletRequest.getHeader("X-Requested-With")) 
                || request.getParameter("ajax") != null) {
            // 是ajax请求
            httpServletRequest.getRequestDispatcher("/login/timeout/success").forward(httpServletRequest, httpServletResponse);
        } else {
            //httpServletResponse.sendRedirect(httpServletRequest.getContextPath() + this.getSuccessUrl());
            String redirectUrl = httpServletRequest.getContextPath() + "/home";
            SavedRequest sr = WebUtils.getSavedRequest(request);
            //当用户地址的请求不是在地址栏输入,而是在页面上直接点击登录,那么SavedRequest返回值将会是空的。
            if(sr != null) {
                redirectUrl = sr.getRequestUrl();                
            }
            httpServletResponse.sendRedirect(redirectUrl);
        }
        
        return false;
    }

    public String getPasswordParam() {
        return passwordParam;
    }

    public void setPasswordParam(String passwordParam) {
        this.passwordParam = passwordParam;
    }

}

AAAA。这里,重点是下面的这个代码片段:

if ("XMLHttpRequest".equalsIgnoreCase(httpServletRequest.getHeader("X-Requested-With")) 
        || request.getParameter("ajax") != null) {
    HttpServletResponse res = WebUtils.toHttp(response);
    res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}

如何测试呢?很简单!
1. 将shiro的配置中,重写LogoutFilter,并将其redirectUrl的值配置为自己想要的内容,而不是原来默认的DEFAULT_REDIRECT_URL(值为/)。例如,将redirectUrl改为登录的url,这里是/usr/login.
2. 将前端页面的退出,一种用ajax的post或者get方式触发,一种用href直接配置url。
2.1 针对退出url (/usr/logout)以ajax的方式触发事件到后台,代码逻辑将会进入上述代码AAAA,且此时HTTP请求头部含有X-Requested-With。前端将收到401的错误,页面不跳转。
2.2 针对退出url以a标签的href方式,直接由浏览器以http调用后台服务的方式,则不会进入AAAA的代码逻辑。

针对上述的LogoutFilter的重写,可以看看配置文件:

    <bean id="sysUserFilter" class="com.roomdis.mqr.infra.shiro.SysUserFilter"/> 
    <bean id="sysLogoutFilter" class="com.roomdis.mqr.infra.shiro.RdLogoutFilter">
        <property name="redirectUrl" value="/user/login"/>
    </bean>
    
    <bean id="jCaptchaValidateFilter" class="com.roomdis.mqr.infra.shiro.JCaptchaValidateFilter">
        <property name="jcaptchaEbabled" value="true"/>
        <property name="jcaptchaParam" value="jcaptchaCode"/>
        <property name="failureKeyAttribute" value="shiroLoginFailure"/>
    </bean>
    
    <bean id="filterChainManager" class="com.roomdis.mqr.infra.shiro.RdDefaultFilterChainManager">
        <property name="loginUrl" value="/user/login" />        
        <property name="successUrl" value="/home" />         
        <property name="unauthorizedUrl" value="/unauth" />  
        <property name="customFilters">
            <util:map>                
                <entry key="authc" value-ref="authcFilter"/>
                <entry key="sysUser" value-ref="sysUserFilter"/>
                <entry key="jCaptchaValidate" value-ref="jCaptchaValidateFilter"/>
                <entry key="logout" value-ref="sysLogoutFilter" />
            </util:map>
        </property>
        <property name="defaultFilterChainDefinitions">  
            <value>
                / = anon
                /*.png = anon
                 /css/* = anon
                /js/** = anon
                /image/* = anon
                /index.html = anon
                /user/login = jCaptchaValidate,authc                        
                /jcaptcha* = anon
                /jcaptcha.jsp = anon
                /home = sysUser,user                                                 
                /logout = logout                
                <!-- /home = authc, perms[/home]  perms 表示需要该权限才能访问的页面 -->  
            </value>
        </property>
    </bean> 

上述配置的红色部分,是重写LogoutFilter的对应bean的信息。

蓝色部分,要特别注意,logout的url,权限部分处理,必须是和自己代码逻辑中的重定向部分一致使用。例如这里的/logout,对应代码逻辑中的地方(红色)如下:

    /**
     * logout 即常说的登出操作
     * 
     * @param req
     * @param rsp
     * @param model
     * @return
     */
    @GET
    @Path("/user/logout")    
    @Produces(MediaType.APPLICATION_JSON)
    public void logout(@Context HttpServletRequest req, @Context HttpServletResponse rsp){
        Map<String, Object> infoMap = new HashMap<String, Object>();
        String basePath = basePath(req);
        String usernamep = req.getParameter("username");
        infoMap.put("basePath", basePath);
        SecurityUtils.getSubject().getSession().removeAttribute(Constants.CURRENT_USER);        
        try {
            rsp.sendRedirect(req.getContextPath() + "/logout");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

前端的代码:

<p class="checkmove">
     <span class="fl sp_title">
        <#if username??>
            <label id="username">${username}</label>
            <a class=logout-btn href="${basePath}/user/logout">退出</a>         #能够正常运行,即不会触发401的错误的用法
            <!--                          
            <button class=button id="leave">退出</button>                       #这样子用,配合后台的ajax的方式触发,就会出现401的错误
             -->
        </#if>                    
        <select>
            <option value="0">上线</option>
            <option value="1">离开</option>
            <option value="2">隐身</option>
            <option value="3">下线</option>
        </select>
    </span>
</p>

下面上一个图,看看我的项目雏形!

原文地址:https://www.cnblogs.com/shihuc/p/7553669.html