关于java:CAS和Shiro内外网双IP动态访问

3次阅读

共计 6936 个字符,预计需要花费 18 分钟才能阅读完成。

因为有需要要实现内外网双 IP 拜访同一个利用,然而以后已部署的利用应用的 cas+shiro 的跳转 url 在 spring 的配置 xml 中写死的,所以须要实现判断起源 HOST 动静单点登录和跳转

继承 FormAuthenticationFilter 动静扭转各个 url

package com.bajins.common;

import java.io.IOException;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.bajins.common.shiro.cas.CasUserRealm;

/**
 * @Title: ImsAuthenticationFilter.java
 * @Package com.bajins.common
 * @Description: shiro 动静扭转 loginUrl
 * @author: https://www.bajins.com
 * @date: 2021 年 4 月 15 日 下午 3:07:18
 * @version V1.0
 * @Copyright: 2021 bajins.com Inc. All rights reserved.
 */
public class ImsAuthenticationFilter extends FormAuthenticationFilter {private static transient final Logger log = LoggerFactory.getLogger(ImsAuthenticationFilter.class);

    private static final String FLAG = "/login?service=";

    private String clientUrl;
    private String serverUrl;

    /**
     * @return the clientUrl
     */
    public String getClientUrl() {return clientUrl;}

    /**
     * @param clientUrl the clientUrl to set
     */
    public void setClientUrl(String clientUrl) {this.clientUrl = clientUrl;}

    /**
     * @return the serverUrl
     */
    public String getServerUrl() {return serverUrl;}

    /**
     * @param serverUrl the serverUrl to set
     */
    public void setServerUrl(String serverUrl) {this.serverUrl = serverUrl;}

    @Override
    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {HttpServletRequest httpServletRequest = (HttpServletRequest) request;

        String contextPath = httpServletRequest.getContextPath();

        // url - uri = domain
        int len = httpServletRequest.getRequestURL().length() - httpServletRequest.getRequestURI().length();
        String domain = httpServletRequest.getRequestURL().substring(0, len);

        /*String reg = "^(192\\.168|172\\.(1[6-9]|2\\d|3[0,1]))(\\.(2[0-4]\\d|25[0-5]|[0,1]?\\d?\\d)){2}$"
                + "|^10(\\.([2][0-4]\\d|25[0-5]|[0,1]?\\d?\\d)){3}$";
        //String reg = "(10|172|192|127)\\.([0-1][0-9]{0,2}|[2][0-5]{0,2}|[3-9][0-9]{0,1})\\.([0-1][0-9]{0,2}"
        //        + "|[2][0-5]{0,2}|[3-9][0-9]{0,1})\\.([0-1][0-9]{0,2}|[2][0-5]{0,2}|[3-9][0-9]{0,1})";
        Pattern p = Pattern.compile(reg);
        Matcher matcher = p.matcher(ipAddress);
        boolean isIntranet = matcher.find();
        if (isIntranet || httpServletRequest.getRemoteHost().equals("172.16.0.91")) { // 如果是内网
            WebUtils.issueRedirect(request, response, domain + "/cas" + loginUrl);
        } else { }*/

        // 获取 servletContext 容器
        ServletContext sc = httpServletRequest.getSession().getServletContext();
        // 获取 web 环境下 spring 容器
        ApplicationContext ac = WebApplicationContextUtils.getWebApplicationContext(sc);

        CasUserRealm casUserRealm = (CasUserRealm) ac.getBean("casUserRealm");
        CasFilter casFilter = (CasFilter) ac.getBean("casFilter");
        LogoutFilter logoutFilter = (LogoutFilter) ac.getBean("logoutFilter");

        ShiroFilterFactoryBean shiroFilter = (ShiroFilterFactoryBean) ac.getBean("&shiroFilter");

        // 依据客户端 url 中的 host 动静替换 url
        String client = domain + contextPath;
        String clientLoginUrl = client + "/login";
        casUserRealm.setCasServerUrlPrefix(domain + getServerUrl());
        casUserRealm.setCasService(clientLoginUrl);
        casFilter.setFailureUrl(client + "/index");
        casFilter.setSuccessUrl(client + "/");
        // casFilter.setLoginUrl(loginUrl);
        logoutFilter.setRedirectUrl(domain + getServerUrl() + FLAG.replace("login", "logout") + clientLoginUrl);
        shiroFilter.setLoginUrl(domain + getServerUrl() + FLAG + clientLoginUrl);
        log.info("login 跳转地址:{}", this.getLoginUrl());

        WebUtils.issueRedirect(httpServletRequest, response, this.getLoginUrl()); // 302 跳转
    }

    /*@Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {return false;}*/

    /*@Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {String loginUrl = this.getLoginUrl();
        Subject subject = getSubject(request, response);
        if (subject.getPrincipal() == null) {// 示意没有登录,重定向到登录页面
            saveRequest(request);
            WebUtils.issueRedirect(request, response, loginUrl);
        } else {if (StringUtils.hasText(loginUrl)) {WebUtils.issueRedirect(request, response, loginUrl);
            } else {WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
        }
        return true;
    }*/

    /**
     * 获取用户实在 IP 地址
     * <p>
     * 当咱们通过 request 获取客户端 IP 时,如果对本身服务器做了反向代理。通过 request.getRemoteAddr(); 可能获取到的是代理服务器的 IP,而无奈获取到用户申请 IP
     *
     * @param request
     * @return java.lang.String
     */
    public static String getIpAddress(HttpServletRequest request) {
        // X-Real-IP:Nginx 服务代理
        String ipAddresses = request.getHeader("X-Real-IP");

        if (!StringUtils.hasText(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
            // Proxy-Client-IP:Apache 服务代理
            ipAddresses = request.getHeader("Proxy-Client-IP");
        }
        if (!StringUtils.hasText(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
            // WL-Proxy-Client-IP:WebLogic 服务代理
            ipAddresses = request.getHeader("WL-Proxy-Client-IP");
        }
        if (!StringUtils.hasText(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
            // HTTP_CLIENT_IP:有些代理服务器
            ipAddresses = request.getHeader("HTTP_CLIENT_IP");
        }
        if (!StringUtils.hasText(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {ipAddresses = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (!StringUtils.hasText(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {
            // X-Forwarded-For:Squid 服务代理 和 Nginx 服务代理
            ipAddresses = request.getHeader("X-Forwarded-For");
        }
        // 有些网络通过多层代理,那么会获取到以逗号(,)宰割的多个 IP,第一个才是实在 IP
        int index = ipAddresses.indexOf(",");
        if (index != -1) {ipAddresses = ipAddresses.substring(0, index);
        }
        if (!StringUtils.hasText(ipAddresses) || "unknown".equalsIgnoreCase(ipAddresses)) {ipAddresses = request.getRemoteAddr();
        }
        return ipAddresses;
    }
}

批改 Spring 配置 xml

<bean id="imsAuthenticationFilter" class="com.bajins.common.ImsAuthenticationFilter">
    <property name="serverUrl" value="${cas.server}" />
    <property name="clientUrl" value="${cas.client}" />
</bean>
<!-- Shiro 的 Web 过滤器 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <property name="securityManager" ref="securityManager" />
    <!-- 原来写死的配置 -->
    <!-- <property name="loginUrl" value="${cas.server}/login?service=${cas.client}/login" /> -->
    <property name="loginUrl" value="/login?service=${cas.client}/login" />
    <property name="unauthorizedUrl" value="/unauthorized" />
    <property name="filters">
        <util:map>
            <!-- 这里把自定义的过滤器退出 -->
            <entry key="authc" value-ref="imsAuthenticationFilter" />
            <entry key="authl" value-ref="loginControlFilter" />
            <entry key="cas" value-ref="casFilter" />
            <entry key="logout" value-ref="logoutFilter" />
            <entry key="casLogout" value-ref="casLogoutFilter" />
        </util:map>
    </property>
    <!-- 指定拜访地址通过指定 Filter 过滤 -->
    <property name="filterChainDefinitions">
        <value>
            /common/** = anon
            /css/** = anon
            /js/** = anon
            /fileUpload/**=anon
            /api/** = anon
            /changeLocale=anon
            <!-- 留神:这里不能用自定义的过滤器,否则死循环重定向 -->
            /login = authl,casLogout,cas
            /logout = logout
            <!-- 应用自定义的过滤器 -->
            /** = authc,casLogout,user
        </value>
    </property>
</bean>

参考:

  • 解决 CAS 内外网双 IP 拜访的问题
  • 双网隔离环境下 CAS 单点登录的解决方案
  • CAS 内外网都能拜访配置阐明
  • DataViz CAS 单点登录集成 · dataviz
  • springboot shiro 多 realm 多 loginUrl 设置(动静扭转 loginUrl)踩坑经验 重定向次数多
正文完
 0