pull/11/head
yzyunzhang 2023-02-02 15:16:36 +00:00 committed by smallbun
parent 14729e9ff5
commit d720775faf
7 changed files with 263 additions and 33 deletions

View File

@ -163,7 +163,7 @@ public final class ProtocolConstants {
/* /*
* cas ticket * cas ticket
*/ */
public final static String CAS_VALIDATE_PATH = CAS_AUTHORIZE_BASE_PATH + "/validate"; public final static String CAS_VALIDATE_V1_PATH = CAS_AUTHORIZE_BASE_PATH + "/validate";
public final static String CAS_VALIDATE_V2_PATH = CAS_AUTHORIZE_BASE_PATH public final static String CAS_VALIDATE_V2_PATH = CAS_AUTHORIZE_BASE_PATH
+ "/serviceValidate"; + "/serviceValidate";

View File

@ -32,8 +32,9 @@ import org.springframework.security.web.util.matcher.RequestMatcher;
import cn.topiam.employee.application.ApplicationServiceLoader; import cn.topiam.employee.application.ApplicationServiceLoader;
import cn.topiam.employee.common.repository.app.AppCasConfigRepository; import cn.topiam.employee.common.repository.app.AppCasConfigRepository;
import cn.topiam.employee.protocol.cas.idp.auth.CentralAuthenticationService; import cn.topiam.employee.protocol.cas.idp.auth.CentralAuthenticationService;
import cn.topiam.employee.protocol.cas.idp.endpoint.Cas10IdpValidateEndpointFilter;
import cn.topiam.employee.protocol.cas.idp.endpoint.Cas30IdpValidateEndpointFilter;
import cn.topiam.employee.protocol.cas.idp.endpoint.CasIdpSingleSignOnEndpointFilter; import cn.topiam.employee.protocol.cas.idp.endpoint.CasIdpSingleSignOnEndpointFilter;
import cn.topiam.employee.protocol.cas.idp.endpoint.CasIdpValidateEndpointFilter;
import cn.topiam.employee.protocol.cas.idp.filter.CasAuthorizationServerContextFilter; import cn.topiam.employee.protocol.cas.idp.filter.CasAuthorizationServerContextFilter;
import cn.topiam.employee.protocol.cas.idp.util.CasUtils; import cn.topiam.employee.protocol.cas.idp.util.CasUtils;
import static cn.topiam.employee.protocol.cas.idp.util.CasUtils.*; import static cn.topiam.employee.protocol.cas.idp.util.CasUtils.*;
@ -59,22 +60,29 @@ public class CasIdpConfigurer<B extends HttpSecurityBuilder<B>>
http.addFilterAfter(new CasIdpSingleSignOnEndpointFilter(applicationServiceLoader, http.addFilterAfter(new CasIdpSingleSignOnEndpointFilter(applicationServiceLoader,
centralAuthenticationService), UsernamePasswordAuthenticationFilter.class); centralAuthenticationService), UsernamePasswordAuthenticationFilter.class);
//cas 验证过滤器 //cas 1.0 验证过滤器
http.addFilterBefore( http.addFilterBefore(
new CasIdpValidateEndpointFilter(applicationServiceLoader, sessionRegistry, new Cas10IdpValidateEndpointFilter(applicationServiceLoader, sessionRegistry,
centralAuthenticationService, documentBuilder), centralAuthenticationService, documentBuilder),
CasIdpSingleSignOnEndpointFilter.class); CasIdpSingleSignOnEndpointFilter.class);
//cas 3.0 & 2.0验证过滤器
http.addFilterBefore(
new Cas30IdpValidateEndpointFilter(applicationServiceLoader, sessionRegistry,
centralAuthenticationService, documentBuilder),
Cas10IdpValidateEndpointFilter.class);
//CAS 授权服务器应用上下文过滤器 //CAS 授权服务器应用上下文过滤器
http.addFilterBefore( http.addFilterBefore(
new CasAuthorizationServerContextFilter(getEndpointsMatcher(), appCasConfigRepository), new CasAuthorizationServerContextFilter(getEndpointsMatcher(), appCasConfigRepository),
CasIdpValidateEndpointFilter.class); Cas30IdpValidateEndpointFilter.class);
} }
public RequestMatcher getEndpointsMatcher() { public RequestMatcher getEndpointsMatcher() {
List<RequestMatcher> requestMatchers = new ArrayList<>(); List<RequestMatcher> requestMatchers = new ArrayList<>();
requestMatchers.add(CasIdpSingleSignOnEndpointFilter.getRequestMatcher()); requestMatchers.add(CasIdpSingleSignOnEndpointFilter.getRequestMatcher());
requestMatchers.add(CasIdpValidateEndpointFilter.getRequestMatcher()); requestMatchers.add(Cas30IdpValidateEndpointFilter.getRequestMatcher());
requestMatchers.add(Cas10IdpValidateEndpointFilter.getRequestMatcher());
return new OrRequestMatcher(requestMatchers); return new OrRequestMatcher(requestMatchers);
} }
} }

View File

@ -0,0 +1,105 @@
/*
* eiam-protocol-cas - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.protocol.cas.idp.endpoint;
import java.io.IOException;
import java.util.HashMap;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import org.springframework.boot.web.servlet.filter.OrderedFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import cn.topiam.employee.application.ApplicationServiceLoader;
import cn.topiam.employee.common.constants.ProtocolConstants;
import cn.topiam.employee.core.security.userdetails.UserDetails;
import cn.topiam.employee.protocol.cas.idp.auth.CentralAuthenticationService;
import cn.topiam.employee.protocol.cas.idp.tickets.ServiceTicket;
import cn.topiam.employee.protocol.cas.idp.xml.Response10GeneratorImpl;
import cn.topiam.employee.protocol.cas.idp.xml.ResponseGenerator;
import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.SERVICE;
import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.TICKET;
/**
* @author TopIAM
* Created by support@topiam.cn on 2023/2/2 19:27
*/
public class Cas10IdpValidateEndpointFilter extends OncePerRequestFilter implements OrderedFilter {
private final ApplicationServiceLoader applicationServiceLoader;
private final SessionRegistry sessionRegistry;
private final CentralAuthenticationService centralAuthenticationService;
private final DocumentBuilder documentBuilder;
private static final RequestMatcher CAS10_VALIDATE_MATCHER = new AntPathRequestMatcher(
ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_V1_PATH, HttpMethod.GET.name());
public Cas10IdpValidateEndpointFilter(ApplicationServiceLoader applicationServiceLoader,
SessionRegistry sessionRegistry,
CentralAuthenticationService centralAuthenticationService,
DocumentBuilder documentBuilder) {
this.applicationServiceLoader = applicationServiceLoader;
this.sessionRegistry = sessionRegistry;
this.centralAuthenticationService = centralAuthenticationService;
this.documentBuilder = documentBuilder;
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
if (CAS10_VALIDATE_MATCHER.matches(request)) {
ResponseGenerator generator = new Response10GeneratorImpl(response);
String ticketId = request.getParameter(TICKET);
String service = request.getParameter(SERVICE);
ServiceTicket serviceTicket = centralAuthenticationService
.validateServiceTicket(ticketId, service);
if (serviceTicket == null) {
generator.genFailedMessage(ticketId);
} else {
UserDetails userDetails = serviceTicket.getTicketGrantingTicket().getUserDetails();
// TODO: 2023/1/2 根据配置返回额外的属性配置
generator.genSucceedMessage(userDetails.getUsername(), new HashMap<>());
}
generator.sendMessage();
}
filterChain.doFilter(request, response);
}
public static RequestMatcher getRequestMatcher() {
return CAS10_VALIDATE_MATCHER;
}
}

View File

@ -42,8 +42,9 @@ import cn.topiam.employee.common.constants.ProtocolConstants;
import cn.topiam.employee.core.security.userdetails.UserDetails; import cn.topiam.employee.core.security.userdetails.UserDetails;
import cn.topiam.employee.protocol.cas.idp.auth.CentralAuthenticationService; import cn.topiam.employee.protocol.cas.idp.auth.CentralAuthenticationService;
import cn.topiam.employee.protocol.cas.idp.tickets.ServiceTicket; import cn.topiam.employee.protocol.cas.idp.tickets.ServiceTicket;
import cn.topiam.employee.protocol.cas.idp.xml.Response20GeneratorImpl;
import cn.topiam.employee.protocol.cas.idp.xml.Response30GeneratorImpl;
import cn.topiam.employee.protocol.cas.idp.xml.ResponseGenerator; import cn.topiam.employee.protocol.cas.idp.xml.ResponseGenerator;
import cn.topiam.employee.protocol.cas.idp.xml.ResponseGeneratorImpl;
import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.SERVICE; import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.SERVICE;
import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.TICKET; import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.TICKET;
@ -51,7 +52,7 @@ import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.TIC
* @author TopIAM * @author TopIAM
* Created by support@topiam.cn on 2023/1/2 13:38 * Created by support@topiam.cn on 2023/1/2 13:38
*/ */
public class CasIdpValidateEndpointFilter extends OncePerRequestFilter implements OrderedFilter { public class Cas30IdpValidateEndpointFilter extends OncePerRequestFilter implements OrderedFilter {
private final ApplicationServiceLoader applicationServiceLoader; private final ApplicationServiceLoader applicationServiceLoader;
@ -61,20 +62,22 @@ public class CasIdpValidateEndpointFilter extends OncePerRequestFilter implement
private static final OrRequestMatcher orRequestMatcher; private static final OrRequestMatcher orRequestMatcher;
private static final RequestMatcher CAS20_VALIDATE_MATCHER = new AntPathRequestMatcher(
ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_V2_PATH, HttpMethod.GET.name());
private static final RequestMatcher CAS30_VALIDATE_MATCHER = new AntPathRequestMatcher(
ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_V3_PATH, HttpMethod.GET.name());
static { static {
List<RequestMatcher> requestMatchers = new ArrayList<>(); List<RequestMatcher> requestMatchers = new ArrayList<>();
requestMatchers.add(new AntPathRequestMatcher( requestMatchers.add(CAS20_VALIDATE_MATCHER);
ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_PATH, HttpMethod.GET.name())); requestMatchers.add(CAS30_VALIDATE_MATCHER);
requestMatchers.add(new AntPathRequestMatcher(
ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_V2_PATH, HttpMethod.GET.name()));
requestMatchers.add(new AntPathRequestMatcher(
ProtocolConstants.CasEndpointConstants.CAS_VALIDATE_V3_PATH, HttpMethod.GET.name()));
orRequestMatcher = new OrRequestMatcher(requestMatchers); orRequestMatcher = new OrRequestMatcher(requestMatchers);
} }
private final DocumentBuilder documentBuilder; private final DocumentBuilder documentBuilder;
public CasIdpValidateEndpointFilter(ApplicationServiceLoader applicationServiceLoader, public Cas30IdpValidateEndpointFilter(ApplicationServiceLoader applicationServiceLoader,
SessionRegistry sessionRegistry, SessionRegistry sessionRegistry,
CentralAuthenticationService centralAuthenticationService, CentralAuthenticationService centralAuthenticationService,
DocumentBuilder documentBuilder) { DocumentBuilder documentBuilder) {
@ -91,10 +94,13 @@ public class CasIdpValidateEndpointFilter extends OncePerRequestFilter implement
} }
@Override @Override
//Cas30Validate MUST perform the same validation tasks as Cas20Validate and additionally return user attributes in the CAS response.
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException { FilterChain filterChain) throws ServletException, IOException {
if (orRequestMatcher.matches(request)) { if (orRequestMatcher.matches(request)) {
ResponseGenerator generator = new ResponseGeneratorImpl(documentBuilder, response); ResponseGenerator generator = CAS20_VALIDATE_MATCHER.matches(request)
? new Response20GeneratorImpl(documentBuilder, response)
: new Response30GeneratorImpl(documentBuilder, response);
String ticketId = request.getParameter(TICKET); String ticketId = request.getParameter(TICKET);
String service = request.getParameter(SERVICE); String service = request.getParameter(SERVICE);
ServiceTicket serviceTicket = centralAuthenticationService ServiceTicket serviceTicket = centralAuthenticationService
@ -103,13 +109,12 @@ public class CasIdpValidateEndpointFilter extends OncePerRequestFilter implement
generator.genFailedMessage(ticketId); generator.genFailedMessage(ticketId);
} else { } else {
UserDetails userDetails = serviceTicket.getTicketGrantingTicket().getUserDetails(); UserDetails userDetails = serviceTicket.getTicketGrantingTicket().getUserDetails();
// TODO: 2023/1/2 根据配置返回额外的属性配置 // TODO: 2023/1/2 Cas30需要根据配置返回额外的属性配置
generator.genSucceedMessage(userDetails.getUsername(), new HashMap<>()); generator.genSucceedMessage(userDetails.getUsername(), new HashMap<>());
} }
generator.sendMessage(); generator.sendMessage();
} }
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} }
public static RequestMatcher getRequestMatcher() { public static RequestMatcher getRequestMatcher() {

View File

@ -0,0 +1,62 @@
/*
* eiam-protocol-cas - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.protocol.cas.idp.xml;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author TopIAM
* Created by support@topiam.cn on 2023/2/2 19:38
*/
public class Response10GeneratorImpl implements ResponseGenerator {
private final static Logger logger = LoggerFactory
.getLogger(Response20GeneratorImpl.class);
private final HttpServletResponse response;
private String message;
public Response10GeneratorImpl(HttpServletResponse response) {
this.response = response;
}
@Override
public void genFailedMessage(String serviceTicketId) {
this.message = "no\n";
}
@Override
public void genSucceedMessage(String casUser, Map<String, Object> attributes) {
this.message = String.format("yes\n%s\n", casUser);
}
@Override
public void sendMessage() throws IOException {
response.setContentType("text/xml;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println(message);
out.flush();
}
}

View File

@ -44,15 +44,16 @@ import static cn.topiam.employee.protocol.cas.idp.constant.ProtocolConstants.*;
* @author TopIAM * @author TopIAM
* Created by support@topiam.cn on 2023/1/2 20:23 * Created by support@topiam.cn on 2023/1/2 20:23
*/ */
public class ResponseGeneratorImpl implements ResponseGenerator { public class Response20GeneratorImpl implements ResponseGenerator {
private final static Logger logger = LoggerFactory.getLogger(ResponseGeneratorImpl.class); private final static Logger logger = LoggerFactory
.getLogger(Response20GeneratorImpl.class);
private final HttpServletResponse response; private final HttpServletResponse response;
private final DocumentBuilder documentBuilder; private final DocumentBuilder documentBuilder;
private String message; private String message;
public ResponseGeneratorImpl(DocumentBuilder documentBuilder, HttpServletResponse response) { public Response20GeneratorImpl(DocumentBuilder documentBuilder, HttpServletResponse response) {
this.response = response; this.response = response;
this.documentBuilder = documentBuilder; this.documentBuilder = documentBuilder;
} }
@ -73,6 +74,19 @@ public class ResponseGeneratorImpl implements ResponseGenerator {
@Override @Override
public void genSucceedMessage(String casUser, Map<String, Object> attributes) { public void genSucceedMessage(String casUser, Map<String, Object> attributes) {
buildDocument(casUser, attributes, false);
}
@Override
public void sendMessage() throws IOException {
response.setContentType("text/xml;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println(message);
out.flush();
}
protected void buildDocument(String casUser, Map<String, Object> attributes,
boolean appendAttributes) {
Document document = documentBuilder.newDocument(); Document document = documentBuilder.newDocument();
OutputFormat outputFormat = OutputFormat.createCompactFormat(); OutputFormat outputFormat = OutputFormat.createCompactFormat();
outputFormat.setExpandEmptyElements(true); outputFormat.setExpandEmptyElements(true);
@ -83,7 +97,7 @@ public class ResponseGeneratorImpl implements ResponseGenerator {
Element userElement = document.createElement(CAS_USER); Element userElement = document.createElement(CAS_USER);
userElement.setTextContent(casUser); userElement.setTextContent(casUser);
successElement.appendChild(userElement); successElement.appendChild(userElement);
if (attributes.size() > 0) { if (appendAttributes && attributes.size() > 0) {
Element attributeElement = document.createElement(CAS_ATTRIBUTES); Element attributeElement = document.createElement(CAS_ATTRIBUTES);
for (Map.Entry<String, Object> entry : attributes.entrySet()) { for (Map.Entry<String, Object> entry : attributes.entrySet()) {
Object value = entry.getValue(); Object value = entry.getValue();
@ -105,14 +119,6 @@ public class ResponseGeneratorImpl implements ResponseGenerator {
this.message = parseDocumentToString(serviceResponse); this.message = parseDocumentToString(serviceResponse);
} }
@Override
public void sendMessage() throws IOException {
response.setContentType("text/xml;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println(message);
out.flush();
}
private String parseDocumentToString(Element node) { private String parseDocumentToString(Element node) {
try { try {
ByteArrayOutputStream bos = new ByteArrayOutputStream(); ByteArrayOutputStream bos = new ByteArrayOutputStream();

View File

@ -0,0 +1,44 @@
/*
* eiam-protocol-cas - Employee Identity and Access Management Program
* Copyright © 2020-2023 TopIAM (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.protocol.cas.idp.xml;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author TopIAM
* Created by support@topiam.cn on 2023/2/2 19:56
*/
public class Response30GeneratorImpl extends Response20GeneratorImpl {
private final static Logger logger = LoggerFactory.getLogger(Response20GeneratorImpl.class);
public Response30GeneratorImpl(DocumentBuilder documentBuilder, HttpServletResponse response) {
super(documentBuilder, response);
}
@Override
public void genSucceedMessage(String casUser, Map<String, Object> attributes) {
buildDocument(casUser, attributes, true);
}
}