feat: 优化,解耦

This commit is contained in:
wangyu 2024-10-21 11:44:49 +08:00
parent 8cdc212d78
commit a9323e4f9b
18 changed files with 5 additions and 1645 deletions

View File

@ -45,6 +45,11 @@
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>dev.flyfish.boot</groupId>
<artifactId>spring-webflux-cas-client-starter</artifactId>
<version>0.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>

View File

@ -1,99 +0,0 @@
package dev.flyfish.boot.cas.config;
import dev.flyfish.boot.cas.config.resolver.CASUserArgumentResolver;
import dev.flyfish.boot.cas.config.session.WebSessionDecorator;
import dev.flyfish.boot.cas.config.session.WebSessionListener;
import dev.flyfish.boot.cas.filter.CASFilter;
import dev.flyfish.boot.cas.filter.CASParameter;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.WebFluxConfigurer;
import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.InMemoryWebSessionStore;
import org.springframework.web.server.session.WebSessionManager;
import org.springframework.web.server.session.WebSessionStore;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.List;
/**
* cas核心配置
*
* @author wangyu
*/
@Configuration
public class CASConfig implements WebFluxConfigurer {
@Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
configurer.addCustomResolver(new CASUserArgumentResolver());
}
@Bean
@ConfigurationProperties("cas.filter")
public CASParameter casParameter() {
return new CASParameter();
}
@Bean
public CASFilter casFilter(CASParameter casParameter) {
return new CASFilter(casParameter);
}
@Bean
public WebSessionStore webSessionStore(WebSessionManager webSessionManager, ServerProperties serverProperties,
ObjectProvider<WebSessionListener> listeners) {
if (webSessionManager instanceof DefaultWebSessionManager defaultWebSessionManager) {
Duration timeout = serverProperties.getReactive().getSession().getTimeout();
int maxSessions = serverProperties.getReactive().getSession().getMaxSessions();
ListenableWebSessionStore sessionStore = new ListenableWebSessionStore(timeout, listeners);
sessionStore.setMaxSessions(maxSessions);
defaultWebSessionManager.setSessionStore(sessionStore);
return sessionStore;
}
throw new IllegalStateException("Cannot find web session manager");
}
/**
* 处理session销毁保证正确退出
*
* @param casFilter 过滤器
* @return 结果
*/
@Bean
public WebSessionListener singleSignOutSessionListener(CASFilter casFilter) {
return new WebSessionListener() {
@Override
public Mono<Void> onSessionInvalidated(WebSession session) {
return casFilter.getSessionMappingStorage().removeBySessionById(session.getId());
}
};
}
static final class ListenableWebSessionStore extends InMemoryWebSessionStore {
private final Duration timeout;
private final List<WebSessionListener> listeners;
private ListenableWebSessionStore(Duration timeout, ObjectProvider<WebSessionListener> listeners) {
this.timeout = timeout;
this.listeners = listeners.stream().toList();
}
public Mono<WebSession> createWebSession() {
return super.createWebSession()
.map(session -> (WebSession) new WebSessionDecorator(session, listeners))
.doOnSuccess(this::setMaxIdleTime);
}
private void setMaxIdleTime(WebSession session) {
session.setMaxIdleTime(this.timeout);
}
}
}

View File

@ -1,9 +0,0 @@
package dev.flyfish.boot.cas.config.annotation;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface CASUser {
}

View File

@ -1,27 +0,0 @@
package dev.flyfish.boot.cas.config.resolver;
import dev.flyfish.boot.cas.config.annotation.CASUser;
import dev.flyfish.boot.cas.filter.CASLoginFilter;
import org.springframework.core.MethodParameter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolverSupport;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
public class CASUserArgumentResolver extends HandlerMethodArgumentResolverSupport {
public CASUserArgumentResolver() {
super(ReactiveAdapterRegistry.getSharedInstance());
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return checkAnnotatedParamNoReactiveWrapper(parameter, CASUser.class, (anno, type) -> true);
}
@Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext, ServerWebExchange exchange) {
return exchange.getSession().mapNotNull(session -> session.getAttribute(CASLoginFilter.CONST_CAS_USERNAME));
}
}

View File

@ -1,194 +0,0 @@
package dev.flyfish.boot.cas.config.session;
import lombok.RequiredArgsConstructor;
import org.springframework.lang.Nullable;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Map;
@RequiredArgsConstructor
public class WebSessionDecorator implements WebSession {
private final WebSession decorated;
private final List<WebSessionListener> listeners;
/**
* Return a unique session identifier.
*/
@Override
public String getId() {
return decorated.getId();
}
/**
* Return a map that holds session attributes.
*/
@Override
public Map<String, Object> getAttributes() {
return decorated.getAttributes();
}
/**
* Return the session attribute value if present.
*
* @param name the attribute name
* @return the attribute value
*/
@Nullable
@Override
public <T> T getAttribute(String name) {
return decorated.getAttribute(name);
}
/**
* Return the session attribute value or if not present raise an
* {@link IllegalArgumentException}.
*
* @param name the attribute name
* @return the attribute value
*/
@Override
public <T> T getRequiredAttribute(String name) {
return decorated.getRequiredAttribute(name);
}
/**
* Return the session attribute value, or a default, fallback value.
*
* @param name the attribute name
* @param defaultValue a default value to return instead
* @return the attribute value
*/
@Override
public <T> T getAttributeOrDefault(String name, T defaultValue) {
return decorated.getAttributeOrDefault(name, defaultValue);
}
/**
* Force the creation of a session causing the session id to be sent when
* {@link #save()} is called.
*/
@Override
public void start() {
decorated.start();
}
/**
* Whether a session with the client has been started explicitly via
* {@link #start()} or implicitly by adding session attributes.
* If "false" then the session id is not sent to the client and the
* {@link #save()} method is essentially a no-op.
*/
@Override
public boolean isStarted() {
return decorated.isStarted();
}
/**
* Generate a new id for the session and update the underlying session
* storage to reflect the new id. After a successful call {@link #getId()}
* reflects the new session id.
*
* @return completion notification (success or error)
*/
@Override
public Mono<Void> changeSessionId() {
return decorated.changeSessionId();
}
/**
* Invalidate the current session and clear session storage.
*
* @return completion notification (success or error)
*/
@Override
public Mono<Void> invalidate() {
// 后续处理
Mono<Void> consumer = Mono.defer(() -> listeners.stream()
.map(listener -> listener.onSessionInvalidated(this.decorated))
.reduce(Mono::then)
.orElse(Mono.empty()));
return decorated.invalidate().then(consumer);
}
/**
* Save the session through the {@code WebSessionStore} as follows:
* <ul>
* <li>If the session is new (i.e. created but never persisted), it must have
* been started explicitly via {@link #start()} or implicitly by adding
* attributes, or otherwise this method should have no effect.
* <li>If the session was retrieved through the {@code WebSessionStore},
* the implementation for this method must check whether the session was
* {@link #invalidate() invalidated} and if so return an error.
* </ul>
* <p>Note that this method is not intended for direct use by applications.
* Instead it is automatically invoked just before the response is
* committed.
*
* @return {@code Mono} to indicate completion with success or error
*/
@Override
public Mono<Void> save() {
return decorated.save();
}
/**
* Return {@code true} if the session expired after {@link #getMaxIdleTime()
* maxIdleTime} elapsed.
* <p>Typically expiration checks should be automatically made when a session
* is accessed, a new {@code WebSession} instance created if necessary, at
* the start of request processing so that applications don't have to worry
* about expired session by default.
*/
@Override
public boolean isExpired() {
return decorated.isExpired();
}
/**
* Return the time when the session was created.
*/
@Override
public Instant getCreationTime() {
return decorated.getCreationTime();
}
/**
* Return the last time of session access as a result of user activity such
* as an HTTP request. Together with {@link #getMaxIdleTime()
* maxIdleTimeInSeconds} this helps to determine when a session is
* {@link #isExpired() expired}.
*/
@Override
public Instant getLastAccessTime() {
return decorated.getLastAccessTime();
}
/**
* Configure the max amount of time that may elapse after the
* {@link #getLastAccessTime() lastAccessTime} before a session is considered
* expired. A negative value indicates the session should not expire.
*
* @param maxIdleTime
*/
@Override
public void setMaxIdleTime(Duration maxIdleTime) {
decorated.setMaxIdleTime(maxIdleTime);
}
/**
* Return the maximum time after the {@link #getLastAccessTime()
* lastAccessTime} before a session expires. A negative time indicates the
* session doesn't expire.
*/
@Override
public Duration getMaxIdleTime() {
return decorated.getMaxIdleTime();
}
}

View File

@ -1,21 +0,0 @@
package dev.flyfish.boot.cas.config.session;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono;
/**
* web session监听器
*
* @author wangyu
* 基于装饰器增强实现可灵活处理
*/
public interface WebSessionListener {
default Mono<Void> onSessionCreated(WebSession session) {
return Mono.empty();
}
default Mono<Void> onSessionInvalidated(WebSession session) {
return Mono.empty();
}
}

View File

@ -1,131 +0,0 @@
package dev.flyfish.boot.cas.context;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.util.Map;
/**
* cas 上下文
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class CASContext {
@Getter
private String ticket;
@Setter
@Getter
private WebSession session;
private final ServerWebExchange exchange;
private final WebFilterChain chain;
private final Map<String, Mono<String>> parameters = new ConcurrentReferenceHashMap<>();
@Setter
@Getter
private String username;
public static Mono<CASContext> create(ServerWebExchange exchange, WebFilterChain chain) {
return new CASContext(exchange, chain).init();
}
private Mono<CASContext> init() {
Mono<String> ticketMono = getParameter("ticket")
.filter(StringUtils::hasText)
.doOnNext(ticket -> this.ticket = ticket);
// 此处必须保证session不为空
Mono<WebSession> sessionMono = exchange.getSession()
.doOnNext(session -> this.session = session);
return Mono.zipDelayError(ticketMono, sessionMono)
.onErrorContinue((e, v) -> e.printStackTrace())
.thenReturn(this);
}
public boolean isTokenRequest() {
return StringUtils.hasText(ticket);
}
public Mono<Void> filter() {
return chain.filter(exchange);
}
public Mono<Void> redirect(String url) {
ServerHttpResponse response = exchange.getResponse();
response.setRawStatusCode(HttpStatus.FOUND.value());
response.getHeaders().setLocation(URI.create(url));
return Mono.empty();
}
public ServerHttpRequest getRequest() {
return exchange.getRequest();
}
public ServerHttpResponse getResponse() {
return exchange.getResponse();
}
public String getPath() {
return exchange.getRequest().getPath().value();
}
public HttpMethod getMethod() {
return exchange.getRequest().getMethod();
}
public String getQuery(String key) {
ServerHttpRequest request = exchange.getRequest();
return request.getQueryParams().getFirst(key);
}
public Mono<String> getFormData(String key) {
return exchange.getFormData()
.mapNotNull(formData -> formData.getFirst(key));
}
public void setSessionAttribute(String key, Object value) {
session.getAttributes().put(key, value);
}
/**
* 获取参数
*
* @param key
* @return 异步结果
*/
private Mono<String> getParameter(String key) {
return parameters.computeIfAbsent(key, this::computeParameter);
}
private Mono<String> computeParameter(String key) {
return this.readParameter(key).cache();
}
private Mono<String> readParameter(String key) {
String query = getQuery(key);
if (StringUtils.hasText(query)) {
return Mono.just(query);
}
MediaType mediaType = exchange.getRequest().getHeaders().getContentType();
if (null != mediaType && mediaType.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED)) {
return exchange.getFormData().mapNotNull(formData -> formData.getFirst(key));
}
return Mono.empty();
}
}

View File

@ -1,13 +0,0 @@
package dev.flyfish.boot.cas.context;
/**
* 上下文初始化逻辑
*
* @author wangyu
*/
public interface CASContextInit {
String getTranslatorUser(String username);
void initContext(CASContext casContext, String username);
}

View File

@ -1,111 +0,0 @@
package dev.flyfish.boot.cas.context;
import dev.flyfish.boot.cas.exception.CASAuthenticationException;
import dev.flyfish.boot.cas.validator.ProxyTicketValidator;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@Setter
@ToString
public class CASReceipt implements Serializable {
private static final Log log = LogFactory.getLog(CASReceipt.class);
@Getter
private String casValidateUrl;
@Getter
private String pgtIou;
@Getter
private boolean primaryAuthentication = false;
@Getter
private String proxyCallbackUrl;
private List<?> proxyList = new ArrayList<>();
@Getter
private String userName;
public static CASReceipt getReceipt(ProxyTicketValidator ptv) throws CASAuthenticationException {
if (log.isTraceEnabled()) {
log.trace("entering getReceipt(ProxyTicketValidator=[" + ptv + "])");
}
if (!ptv.isAuthenticationSuccessful()) {
try {
ptv.validate();
} catch (Exception e) {
CASAuthenticationException casException = new CASAuthenticationException("Unable to validate ProxyTicketValidator [" + ptv + "]", e);
log.error(casException);
throw casException;
}
}
if (!ptv.isAuthenticationSuccessful()) {
log.error("validation of [" + ptv + "] was not successful.");
throw new CASAuthenticationException("Unable to validate ProxyTicketValidator [" + ptv + "]");
} else {
CASReceipt receipt = new CASReceipt();
receipt.casValidateUrl = ptv.getCasValidateUrl();
receipt.pgtIou = ptv.getPgtIou();
receipt.userName = ptv.getUser();
receipt.proxyCallbackUrl = ptv.getProxyCallbackUrl();
receipt.proxyList = ptv.getProxyList();
receipt.primaryAuthentication = ptv.isRenew();
if (!receipt.validate()) {
throw new CASAuthenticationException("Validation of [" + ptv + "] did not result in an internally consistent CASReceipt.");
} else {
if (log.isTraceEnabled()) {
log.trace("returning from getReceipt() with return value [" + receipt + "]");
}
return receipt;
}
}
}
public CASReceipt() {
}
public List<?> getProxyList() {
return Collections.unmodifiableList(this.proxyList);
}
public boolean isProxied() {
return !this.proxyList.isEmpty();
}
public String getProxyingService() {
return this.proxyList.isEmpty() ? null : (String) this.proxyList.getFirst();
}
private boolean validate() {
boolean valid = true;
if (this.userName == null) {
log.error("Receipt was invalid because userName was null. Receipt:[" + this + "]");
valid = false;
}
if (this.casValidateUrl == null) {
log.error("Receipt was invalid because casValidateUrl was null. Receipt:[" + this + "]");
valid = false;
}
if (this.proxyList == null) {
log.error("receipt was invalid because proxyList was null. Receipt:[" + this + "]");
valid = false;
}
if (this.primaryAuthentication && !this.proxyList.isEmpty()) {
log.error("If authentication was by primary credentials then it could not have been proxied. Yet, primaryAuthentication is true where proxyList is not empty. Receipt:[" + this + "]");
valid = false;
}
return valid;
}
}

View File

@ -1,65 +0,0 @@
package dev.flyfish.boot.cas.context;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
* webflux的session mapping存储
*
* @author wangyu
*/
public interface SessionMappingStorage {
Mono<WebSession> removeSessionByMappingId(String mappingId);
Mono<Void> removeBySessionById(String mappingId);
Mono<Void> addSessionById(String mappingId, WebSession session);
@Slf4j
class HashMapBackedSessionStorage implements SessionMappingStorage {
private final Map<String, WebSession> MANAGED_SESSIONS = new HashMap<>();
private final Map<String, String> ID_TO_SESSION_KEY_MAPPING = new HashMap<>();
@Override
public Mono<WebSession> removeSessionByMappingId(String mappingId) {
WebSession session = this.MANAGED_SESSIONS.get(mappingId);
if (session != null) {
return this.removeBySessionById(session.getId()).thenReturn(session);
}
return Mono.empty();
}
@Override
public Mono<Void> removeBySessionById(String sessionId) {
if (log.isDebugEnabled()) {
log.debug("Attempting to remove Session=[" + sessionId + "]");
}
String key = this.ID_TO_SESSION_KEY_MAPPING.get(sessionId);
if (log.isDebugEnabled()) {
if (key != null) {
log.debug("Found mapping for session. Session Removed.");
} else {
log.debug("No mapping for session found. Ignoring.");
}
}
this.MANAGED_SESSIONS.remove(key);
this.ID_TO_SESSION_KEY_MAPPING.remove(sessionId);
return Mono.empty();
}
@Override
public Mono<Void> addSessionById(String mappingId, WebSession session) {
this.ID_TO_SESSION_KEY_MAPPING.put(session.getId(), mappingId);
this.MANAGED_SESSIONS.put(mappingId, session);
return Mono.empty();
}
}
}

View File

@ -1,11 +0,0 @@
package dev.flyfish.boot.cas.exception;
public class CASAuthenticationException extends Exception {
public CASAuthenticationException(String string) {
super(string);
}
public CASAuthenticationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,420 +0,0 @@
package dev.flyfish.boot.cas.filter;
import dev.flyfish.boot.cas.context.CASContext;
import dev.flyfish.boot.cas.context.CASContextInit;
import dev.flyfish.boot.cas.context.CASReceipt;
import dev.flyfish.boot.cas.context.SessionMappingStorage;
import dev.flyfish.boot.cas.exception.CASAuthenticationException;
import dev.flyfish.boot.cas.validator.ProxyTicketValidator;
import dev.flyfish.boot.cas.validator.XmlUtils;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* cas filter的webflux实现
*
* @author wangyu
* 实现相关核心逻辑完成鉴权信息抽取
*/
@Slf4j
public class CASFilter implements WebFilter {
public static final String LOGIN_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.loginUrl";
public static final String VALIDATE_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.validateUrl";
public static final String SERVICE_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.serviceUrl";
public static final String SERVERNAME_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.serverName";
public static final String RENEW_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.renew";
public static final String AUTHORIZED_PROXY_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.authorizedProxy";
public static final String PROXY_CALLBACK_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.proxyCallbackUrl";
public static final String WRAP_REQUESTS_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.wrapRequest";
public static final String GATEWAY_INIT_PARAM = "edu.yale.its.tp.cas.client.filter.gateway";
public static final String CAS_FILTER_USER = "edu.yale.its.tp.cas.client.filter.user";
public static final String CAS_FILTER_RECEIPT = "edu.yale.its.tp.cas.client.filter.receipt";
static final String CAS_FILTER_GATEWAYED = "edu.yale.its.tp.cas.client.filter.didGateway";
static final String CAS_FILTER_INITCONTEXTCLASS = "edu.yale.its.tp.cas.client.filter.initContextClass";
static final String CAS_FILTER_USERLOGINMARK = "edu.yale.its.tp.cas.client.filter.userLoginMark";
static final String CAS_FILTER_EXCLUSION = "edu.yale.its.tp.cas.client.filter.filterExclusion";
private final CASParameter parameter;
private final CASContextInit initializer;
@Getter
private final SessionMappingStorage sessionMappingStorage = new SessionMappingStorage.HashMapBackedSessionStorage();
public CASFilter(CASParameter parameter) {
this.parameter = parameter.checked();
this.initializer = createInitializer();
}
private CASContextInit createInitializer() {
if (null != parameter.casInitContextClass) {
try {
// 未正确配置类型抛弃
Class<? extends CASContextInit> cls = parameter.casInitContextClass.asSubclass(CASContextInit.class);
// 实例化对象并返回
return cls.getConstructor().newInstance();
} catch (ClassCastException e) {
log.warn("cas context init class not implements CASContextInit", e);
} catch (IllegalArgumentException | IllegalAccessException | InstantiationException
| SecurityException | NoSuchMethodException e) {
log.error("error when initialize the context init class", e);
} catch (InvocationTargetException e) {
log.error("error when create the cas context initializer's instance!", e);
}
}
return null;
}
private boolean isReceiptAcceptable(CASReceipt receipt) {
if (receipt == null) {
throw new IllegalArgumentException("Cannot evaluate a null receipt.");
} else if (parameter.casRenew && !receipt.isPrimaryAuthentication()) {
return false;
} else {
return !receipt.isProxied() || parameter.authorizedProxies.contains(receipt.getProxyingService());
}
}
private CASReceipt getAuthenticatedUser(CASContext context) throws CASAuthenticationException {
log.trace("entering getAuthenticatedUser()");
ProxyTicketValidator pv = new ProxyTicketValidator();
pv.setCasValidateUrl(parameter.casValidate);
pv.setServiceTicket(context.getTicket());
pv.setService(this.getService(context));
pv.setRenew(parameter.casRenew);
if (parameter.casProxyCallbackUrl != null) {
pv.setProxyCallbackUrl(parameter.casProxyCallbackUrl);
}
log.debug("about to validate ProxyTicketValidator: [{}]", pv);
return CASReceipt.getReceipt(pv);
}
private String getService(CASContext context) {
log.trace("entering getService()");
if (parameter.casServerName == null && parameter.casServiceUrl == null) {
throw new IllegalArgumentException("need one of the following configuration parameters: edu.yale.its.tp.cas.client.filter.serviceUrl or edu.yale.its.tp.cas.client.filter.serverName");
}
String serviceString;
if (parameter.casServiceUrl != null) {
serviceString = URLEncoder.encode(parameter.casServiceUrl, StandardCharsets.UTF_8);
} else {
serviceString = computeService(context, parameter.getFullServerUrl());
}
if (log.isTraceEnabled()) {
log.trace("returning from getService() with service [{}]", serviceString);
}
return serviceString;
}
/**
* 计算服务地址主要是替换url中server的部分并去除ticket
*
* @param context 上下文
* @param server 服务
* @return 结果
*/
public String computeService(CASContext context, String server) {
if (log.isTraceEnabled()) {
log.trace("entering getService({}, {})", context, server);
}
if (server == null) {
log.error("getService() argument \"server\" was illegally null.");
throw new IllegalArgumentException("name of server is required");
}
URI uri = context.getRequest().getURI();
StringBuilder sb = new StringBuilder(server).append(uri.getPath());
if (uri.getQuery() != null) {
String query = uri.getQuery();
int ticketLoc = query.indexOf("ticket=");
if (ticketLoc == -1) {
sb.append("?").append(query);
} else if (ticketLoc > 0) {
ticketLoc = query.indexOf("&ticket=");
if (ticketLoc == -1) {
sb.append("?").append(query);
} else if (ticketLoc > 0) {
sb.append("?").append(query, 0, ticketLoc);
}
}
}
String encodedService = URLEncoder.encode(sb.toString(), StandardCharsets.UTF_8);
if (log.isTraceEnabled()) {
log.trace("returning from getService() with encoded service [{}]", encodedService);
}
return encodedService;
}
/**
* 核心跳转cas服务器鉴权
*
* @param context 上下文
* @return 结果
*/
private Mono<Void> redirectToCAS(CASContext context) {
ServerHttpRequest request = context.getRequest();
String sessionId = context.getSession().getId();
log.trace("entering redirectToCAS()");
StringBuilder casLoginString = new StringBuilder()
.append(parameter.casLogin)
.append("?service=").append(this.getService(context))
.append(parameter.casRenew ? "&renew=true" : "")
.append(parameter.casGateway ? "&gateway=true" : "");
if (StringUtils.hasText(sessionId)) {
String appId = parameter.casServerName + request.getPath().contextPath().value();
casLoginString.append("&appId=").append(URLEncoder.encode(appId, StandardCharsets.UTF_8))
.append("&sessionId=").append(sessionId);
}
List<HttpCookie> cookies = request.getCookies().get("JSESSIONID");
if (!CollectionUtils.isEmpty(cookies)) {
cookies.stream()
.filter(Objects::nonNull)
.map(HttpCookie::getValue)
.filter(cookie -> !cookie.equals("null") && !cookie.equals(sessionId))
.peek(cookie -> log.debug("Session is timeout. The timeout session is {}", cookie))
.findFirst()
.ifPresent(cookie -> casLoginString.append("&timeOut=").append(cookie));
}
log.debug("Redirecting browser to [{})", casLoginString);
log.trace("returning from redirectToCAS()");
return context.redirect(casLoginString.toString());
}
private Mono<Void> redirectToInitFailure(CASContext context, String cause) {
log.trace("entering redirectToInitFailure()");
String casLoginString = parameter.casLogin + "?action=initFailure";
if (cause != null && cause.equals("Illegal user")) {
casLoginString += "&userIllegal=true";
}
String locale = context.getQuery("locale");
if (locale != null) {
casLoginString += "&locale=" + locale;
}
log.debug("Redirecting browser to [{})", casLoginString);
log.trace("returning from redirectToInitFailure()");
return context.redirect(casLoginString);
}
private boolean isExclusion(String url) {
if (parameter.exclusions == null) {
return false;
} else {
return parameter.exclusions.contains(url);
}
}
private Mono<Void> translate(CASContext context) {
// 是代理回调地址通过
if (parameter.casProxyCallbackUrl != null && parameter.casProxyCallbackUrl.endsWith(context.getPath())
&& context.getQuery("pgtId") != null && context.getQuery("pgtIou") != null) {
log.trace("passing through what we hope is CAS's request for proxy ticket receptor.");
return context.filter();
}
// 请求包装增强请求并完成自定义功能
if (parameter.wrapRequest) {
log.trace("Wrapping request with CASFilterRequestWrapper.");
// todo 暂时啥也不干看看有无问题
// request = new CASFilterRequestWrapper((HttpServletRequest) request);
}
WebSession session = context.getSession();
Map<String, Object> sessionAttributes = session.getAttributes();
// 使用了用户标记快速跳过
if (parameter.userLoginMark != null && session.getAttribute(parameter.userLoginMark) != null) {
return context.filter();
}
// 获取receipt若存在则通过
CASReceipt receipt = session.getAttribute(CAS_FILTER_RECEIPT);
if (receipt != null && this.isReceiptAcceptable(receipt)) {
log.trace("CAS_FILTER_RECEIPT attribute was present and acceptable - passing request through filter..");
return context.filter();
}
// 命中排除地址跳过请求
if (this.isExclusion(context.getPath())) {
return context.filter();
}
// 判断票据
String ticket = context.getTicket();
// 存在票据时验证票据
if (StringUtils.hasText(ticket)) {
try {
receipt = this.getAuthenticatedUser(context);
} catch (CASAuthenticationException e) {
return this.redirectToCAS(context);
}
if (!this.isReceiptAcceptable(receipt)) {
throw new IllegalStateException("Authentication was technically successful but rejected as a matter of policy. [" + receipt + "]");
}
// 记录receipt
String pt = context.getQuery("pt");
if (StringUtils.hasText(pt)) {
context.setSessionAttribute(pt, receipt);
}
// 获取到用户名
String userName = receipt.getUserName();
// 尝试初始化
if (null != initializer) {
try {
String translated = initializer.getTranslatorUser(userName);
log.debug("translated username: {} to {}", userName, translated);
initializer.initContext(context, translated);
} catch (Exception e) {
String cause = e.getCause().getMessage();
context.setSessionAttribute("initFailure", cause);
return this.redirectToInitFailure(context, cause);
}
}
sessionAttributes.put(CAS_FILTER_USER, userName);
sessionAttributes.put(CAS_FILTER_RECEIPT, receipt);
sessionAttributes.remove(CAS_FILTER_GATEWAYED);
if (log.isTraceEnabled()) {
log.trace("validated ticket to get authenticated receipt [{}], now passing request along filter chain.", receipt);
log.trace("returning from doFilter()");
}
return context.filter();
}
// 不存在票据跳转验证
log.trace("CAS ticket was not present on request.");
boolean didGateway = Boolean.parseBoolean(session.getAttribute(CAS_FILTER_GATEWAYED));
if (parameter.casLogin == null) {
log.error("casLogin was not set, so filter cannot redirect request for authentication.");
throw new IllegalArgumentException("When CASFilter protects pages that do not receive a 'ticket' parameter, it needs a edu.yale.its.tp.cas.client.filter.loginUrl filter parameter");
}
if (!didGateway) {
log.trace("Did not previously gateway. Setting session attribute to true.");
sessionAttributes.put(CAS_FILTER_GATEWAYED, "true");
return this.redirectToCAS(context);
}
log.trace("Previously gatewayed.");
if (!parameter.casGateway && session.getAttribute(CAS_FILTER_USER) == null) {
if (session.getAttribute("initFailure") != null) {
String cause = session.getAttribute("initFailure");
return this.redirectToInitFailure(context, cause);
}
sessionAttributes.put(CAS_FILTER_GATEWAYED, "true");
return this.redirectToCAS(context);
}
log.trace("casGateway was true and CAS_FILTER_USER set: passing request along filter chain.");
return context.filter();
}
/**
* 二阶段处理预处理特殊情况提前中断请求
*
* @param context 上下文工具
* @return 结果
*/
private Mono<Void> handle(CASContext context) {
// 优先处理token请求
if (context.isTokenRequest()) {
String sessionId = context.getSession().getId();
log.debug("Storing session identifier for {}", sessionId);
// 包括ticket尝试重新替换session
return sessionMappingStorage.removeBySessionById(sessionId)
.onErrorContinue((e, v) -> log.debug("error when remove session"))
.then(Mono.defer(() -> sessionMappingStorage.addSessionById(context.getTicket(), context.getSession())
.then(translate(context))));
}
// post请求需要特殊处理
if (context.getMethod() == HttpMethod.POST) {
// 通过form表单获取注销请求处理注销逻辑
return context.getFormData("logoutRequest")
.doOnNext(payload -> log.trace("Logout request=[{}]", payload))
.defaultIfEmpty("")
.flatMap(payload -> {
if (StringUtils.hasText(payload)) {
String token = XmlUtils.getTextForElement(payload, "SessionIndex");
if (StringUtils.hasText(token)) {
// 满足条件时断路
return sessionMappingStorage.removeSessionByMappingId(token)
.doOnNext(session -> log.debug("Invalidating session [{}] for ST [{}]", session.getId(), token))
.flatMap(WebSession::invalidate)
.doOnError(IllegalStateException.class, e -> log.debug(e.getMessage(), e))
.onErrorComplete();
}
}
// 继续执行
return translate(context);
});
}
return translate(context);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// 拦截器需要基于session判定故提前使用
return CASContext.create(exchange, chain)
.flatMap(context -> {
WebSession session = context.getSession();
if (log.isTraceEnabled()) {
log.trace("entering doFilter()");
}
// 执行跳过策略
String pt = context.getQuery("pt");
if (StringUtils.hasText(pt)) {
if (session.getAttribute(pt) != null) {
return context.filter();
}
}
return handle(context);
});
}
}

View File

@ -1,23 +0,0 @@
package dev.flyfish.boot.cas.filter;
import dev.flyfish.boot.cas.context.CASContext;
import dev.flyfish.boot.cas.context.CASContextInit;
/**
* 登录过滤器旨在缓存用户名
*
* @author wangyu
*/
public class CASLoginFilter implements CASContextInit {
public static String CONST_CAS_USERNAME = "const_cas_username";
@Override
public String getTranslatorUser(String username) {
return username;
}
@Override
public void initContext(CASContext casContext, String username) {
casContext.setSessionAttribute(CONST_CAS_USERNAME, username);
}
}

View File

@ -1,88 +0,0 @@
package dev.flyfish.boot.cas.filter;
import com.fasterxml.jackson.annotation.JsonAlias;
import lombok.Data;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
@Data
public class CASParameter {
// 排除的过滤地址
@JsonAlias(CASFilter.CAS_FILTER_EXCLUSION)
Set<String> exclusions;
@JsonAlias(CASFilter.LOGIN_INIT_PARAM)
String casLogin;
@JsonAlias(CASFilter.VALIDATE_INIT_PARAM)
String casValidate;
@JsonAlias(CASFilter.SERVICE_INIT_PARAM)
String casServiceUrl;
@JsonAlias(CASFilter.SERVERNAME_INIT_PARAM)
String casServerName;
String casServerProtocol = "http";
@JsonAlias(CASFilter.PROXY_CALLBACK_INIT_PARAM)
String casProxyCallbackUrl;
@JsonAlias(CASFilter.CAS_FILTER_INITCONTEXTCLASS)
Class<?> casInitContextClass;
@JsonAlias(CASFilter.RENEW_INIT_PARAM)
boolean casRenew;
@JsonAlias(CASFilter.WRAP_REQUESTS_INIT_PARAM)
boolean wrapRequest;
@JsonAlias(CASFilter.GATEWAY_INIT_PARAM)
boolean casGateway = false;
@JsonAlias(CASFilter.CAS_FILTER_USERLOGINMARK)
String userLoginMark = null;
// 已授权的代理地址列表
@JsonAlias(CASFilter.AUTHORIZED_PROXY_INIT_PARAM)
List<String> authorizedProxies = new ArrayList<>();
public void setAuthorizedProxies(String casAuthorizedProxy) {
if (casAuthorizedProxy != null) {
StringTokenizer casProxies = new StringTokenizer(casAuthorizedProxy);
while (casProxies.hasMoreTokens()) {
String anAuthorizedProxy = casProxies.nextToken();
this.authorizedProxies.add(anAuthorizedProxy);
}
}
}
public String getFullServerUrl() {
if (StringUtils.hasText(casServerName)) {
return casServerProtocol + "://" + casServerName;
}
return null;
}
/**
* 检查配置参数是否有误
*/
public CASParameter checked() {
if (this.casGateway && this.casRenew) {
throw new IllegalArgumentException("gateway and renew cannot both be true in filter configuration");
} else if (this.casServerName != null && this.casServiceUrl != null) {
throw new IllegalArgumentException("serverName and serviceUrl cannot both be set: choose one.");
} else if (this.casServerName == null && this.casServiceUrl == null) {
throw new IllegalArgumentException("one of serverName or serviceUrl must be set.");
} else if (this.casValidate == null) {
throw new IllegalArgumentException("validateUrl parameter must be set.");
}
return this;
}
}

View File

@ -1,86 +0,0 @@
package dev.flyfish.boot.cas.validator;
import lombok.ToString;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.List;
@ToString
public class ProxyTicketValidator extends ServiceTicketValidator {
protected List proxyList;
public ProxyTicketValidator() {
}
public static void main(String[] args) throws Exception {
System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
ProxyTicketValidator pv = new ProxyTicketValidator();
pv.setCasValidateUrl("https://portal.yale.edu/cas/proxyValidate");
pv.setService(args[0]);
pv.setServiceTicket(args[1]);
pv.validate();
System.out.println(pv.getResponse());
System.out.println();
if (pv.isAuthenticationSuccessful()) {
System.out.println("user: " + pv.getUser());
System.out.println("proxies:\n " + pv.getProxyList());
} else {
System.out.println("error code: " + pv.getErrorCode());
System.out.println("error message: " + pv.getErrorMessage());
}
}
public List getProxyList() {
return this.proxyList;
}
protected DefaultHandler newHandler() {
return new ProxyHandler();
}
protected void clear() {
super.clear();
this.proxyList = null;
}
protected class ProxyHandler extends ServiceTicketValidator.Handler {
protected static final String PROXIES = "cas:proxies";
protected static final String PROXY = "cas:proxy";
protected List proxyList = new ArrayList();
protected boolean proxyFragment = false;
protected ProxyHandler() {
super();
}
public void startElement(String ns, String ln, String qn, Attributes a) {
super.startElement(ns, ln, qn, a);
if (this.authenticationSuccess && qn.equals("cas:proxies")) {
this.proxyFragment = true;
}
}
public void endElement(String ns, String ln, String qn) throws SAXException {
super.endElement(ns, ln, qn);
if (qn.equals("cas:proxies")) {
this.proxyFragment = false;
} else if (this.proxyFragment && qn.equals("cas:proxy")) {
this.proxyList.add(this.currentText.toString().trim());
}
}
public void endDocument() throws SAXException {
super.endDocument();
if (this.authenticationSuccess) {
ProxyTicketValidator.this.proxyList = this.proxyList;
}
}
}
}

View File

@ -1,44 +0,0 @@
package dev.flyfish.boot.cas.validator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
public class SecureURL {
private static final Log log = LogFactory.getLog(SecureURL.class);
public SecureURL() {
}
public static void main(String[] args) throws IOException {
System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
System.out.println(retrieve(args[0]));
}
public static String retrieve(String url) throws IOException {
if (log.isTraceEnabled()) {
log.trace("entering retrieve(" + url + ")");
}
URL u = URI.create(url).toURL();
URLConnection uc = u.openConnection();
uc.setRequestProperty("Connection", "close");
InputStream in = uc.getInputStream();
ByteArrayOutputStream output = new ByteArrayOutputStream();
for (int chByte = in.read(); chByte != -1; chByte = in.read()) {
output.write(chByte);
}
return output.toString(StandardCharsets.UTF_8);
}
}

View File

@ -1,192 +0,0 @@
package dev.flyfish.boot.cas.validator;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.StringReader;
@ToString
public class ServiceTicketValidator {
@Getter
@Setter
private String casValidateUrl;
@Getter
@Setter
private String proxyCallbackUrl;
private String st;
@Setter
private String service;
@Getter
private String pgtIou;
@Getter
private String user;
@Getter
private String errorCode;
@Getter
private String errorMessage;
private String entireResponse;
private String ss;
@Setter
@Getter
private boolean renew = false;
private boolean attemptedAuthentication;
private boolean successfulAuthentication;
public ServiceTicketValidator() {
}
public static void main(String[] args) throws Exception {
System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
ServiceTicketValidator sv = new ServiceTicketValidator();
sv.setCasValidateUrl("https://portal1.wss.yale.edu/cas/serviceValidate");
sv.setProxyCallbackUrl("https://portal1.wss.yale.edu/casProxy/receptor");
sv.setService(args[0]);
sv.setServiceTicket(args[1]);
sv.validate();
System.out.println(sv.getResponse());
System.out.println();
if (sv.isAuthenticationSuccessful()) {
System.out.println("user: " + sv.getUser());
System.out.println("pgtIou: " + sv.getPgtIou());
} else {
System.out.println("error code: " + sv.getErrorCode());
System.out.println("error message: " + sv.getErrorMessage());
}
}
public void setServiceTicket(String x) {
this.st = x;
}
public boolean isAuthenticationSuccessful() {
return this.successfulAuthentication;
}
public String getResponse() {
return this.entireResponse;
}
public void validate() throws IOException, SAXException, ParserConfigurationException {
if (this.casValidateUrl != null && this.st != null) {
this.clear();
this.attemptedAuthentication = true;
StringBuilder sb = new StringBuilder();
sb.append(this.casValidateUrl);
if (this.casValidateUrl.indexOf(63) == -1) {
sb.append('?');
} else {
sb.append('&');
}
sb.append("service=").append(this.service).append("&ticket=").append(this.st);
if (this.proxyCallbackUrl != null) {
sb.append("&pgtUrl=").append(this.proxyCallbackUrl);
}
if (this.renew) {
sb.append("&renew=true");
}
String url = sb.toString();
this.ss = url;
String response = SecureURL.retrieve(url);
this.entireResponse = response;
if (response != null) {
XMLReader r = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
r.setFeature("http://xml.org/sax/features/namespaces", false);
r.setContentHandler(this.newHandler());
r.parse(new InputSource(new StringReader(response)));
}
} else {
throw new IllegalStateException("must set validation URL and ticket");
}
}
protected DefaultHandler newHandler() {
return new Handler();
}
protected void clear() {
this.user = this.pgtIou = this.errorMessage = null;
this.attemptedAuthentication = false;
this.successfulAuthentication = false;
}
protected class Handler extends DefaultHandler {
protected static final String AUTHENTICATION_SUCCESS = "cas:authenticationSuccess";
protected static final String AUTHENTICATION_FAILURE = "cas:authenticationFailure";
protected static final String PROXY_GRANTING_TICKET = "cas:proxyGrantingTicket";
protected static final String USER = "cas:user";
protected StringBuffer currentText = new StringBuffer();
protected boolean authenticationSuccess = false;
protected boolean authenticationFailure = false;
protected String netid;
protected String pgtIou;
protected String errorCode;
protected String errorMessage;
protected Handler() {
}
public void startElement(String ns, String ln, String qn, Attributes a) {
this.currentText = new StringBuffer();
if (qn.equals(AUTHENTICATION_SUCCESS)) {
this.authenticationSuccess = true;
} else if (qn.equals(AUTHENTICATION_FAILURE)) {
this.authenticationFailure = true;
this.errorCode = a.getValue("code");
if (this.errorCode != null) {
this.errorCode = this.errorCode.trim();
}
}
}
public void characters(char[] ch, int start, int length) {
this.currentText.append(ch, start, length);
}
public void endElement(String ns, String ln, String qn) throws SAXException {
if (this.authenticationSuccess) {
if (qn.equals(USER)) {
ServiceTicketValidator.this.user = this.currentText.toString().trim();
}
if (qn.equals(PROXY_GRANTING_TICKET)) {
this.pgtIou = this.currentText.toString().trim();
}
} else if (this.authenticationFailure && qn.equals(AUTHENTICATION_FAILURE)) {
this.errorMessage = this.currentText.toString().trim();
}
}
public void endDocument() throws SAXException {
if (this.authenticationSuccess) {
ServiceTicketValidator.this.user = ServiceTicketValidator.this.user;
ServiceTicketValidator.this.pgtIou = this.pgtIou;
ServiceTicketValidator.this.successfulAuthentication = true;
} else {
if (!this.authenticationFailure) {
throw new SAXException("no indication of success or failure from CAS");
}
ServiceTicketValidator.this.errorMessage = this.errorMessage;
ServiceTicketValidator.this.errorCode = this.errorCode;
ServiceTicketValidator.this.successfulAuthentication = false;
}
}
}
}

View File

@ -1,111 +0,0 @@
package dev.flyfish.boot.cas.validator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
public final class XmlUtils {
private static final Log LOG = LogFactory.getLog(XmlUtils.class);
public static XMLReader getXmlReader() {
try {
return XMLReaderFactory.createXMLReader();
} catch (SAXException var1) {
SAXException e = var1;
throw new RuntimeException("Unable to create XMLReader", e);
}
}
public static List getTextForElements(String xmlAsString, final String element) {
final List elements = new ArrayList(2);
XMLReader reader = getXmlReader();
DefaultHandler handler = new DefaultHandler() {
private boolean foundElement = false;
private StringBuffer buffer = new StringBuffer();
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (localName.equals(element)) {
this.foundElement = true;
}
}
public void endElement(String uri, String localName, String qName) throws SAXException {
if (localName.equals(element)) {
this.foundElement = false;
elements.add(this.buffer.toString());
this.buffer = new StringBuffer();
}
}
public void characters(char[] ch, int start, int length) throws SAXException {
if (this.foundElement) {
this.buffer.append(ch, start, length);
}
}
};
reader.setContentHandler(handler);
reader.setErrorHandler(handler);
try {
reader.parse(new InputSource(new StringReader(xmlAsString)));
return elements;
} catch (Exception var6) {
Exception e = var6;
LOG.error(e, e);
return null;
}
}
public static String getTextForElement(String xmlAsString, final String element) {
XMLReader reader = getXmlReader();
final StringBuffer buffer = new StringBuffer();
DefaultHandler handler = new DefaultHandler() {
private boolean foundElement = false;
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
if (localName.equals(element)) {
this.foundElement = true;
}
}
public void endElement(String uri, String localName, String qName) throws SAXException {
if (localName.equals(element)) {
this.foundElement = false;
}
}
public void characters(char[] ch, int start, int length) throws SAXException {
if (this.foundElement) {
buffer.append(ch, start, length);
}
}
};
reader.setContentHandler(handler);
reader.setErrorHandler(handler);
try {
reader.parse(new InputSource(new StringReader(xmlAsString)));
} catch (Exception var6) {
Exception e = var6;
LOG.error(e, e);
return null;
}
return buffer.toString();
}
}