因为有需要要实现内外网双 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)踩坑经验 重定向次数多