客户端依赖包
<dependency>
<groupId>net.unicon.casgroupId>
<artifactId>cas-client-autoconfig-supportartifactId>
<version>2.3.0-GAversion>
dependency>
浏览器向客户端发送请求 http://localhost:8989/test1/index
客户端: AbstractTicketValidationFilter
过滤器拦截
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (this.preFilter(servletRequest, servletResponse, filterChain)) {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
String ticket = this.retrieveTicketFromRequest(request);
//校验请求中是否有ticket
if (CommonUtils.isNotBlank(ticket)) {
this.logger.debug("Attempting to validate ticket: {}", ticket);
try {
//校验ticket是否正确
Assertion assertion = this.ticketValidator.validate(ticket, this.constructServiceUrl(request, response));
this.logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());
//校验成功设置 _const_cas_assertion_属性
request.setAttribute("_const_cas_assertion_", assertion);
if (this.useSession) {
request.getSession().setAttribute("_const_cas_assertion_", assertion);
}
this.onSuccessfulValidation(request, response, assertion);
if (this.redirectAfterValidation) {
//重定向请求
this.logger.debug("Redirecting after successful ticket validation.");
response.sendRedirect(this.constructServiceUrl(request, response));
return;
}
} catch (TicketValidationException var8) {
//校验失败抛出异常
this.logger.debug(var8.getMessage(), var8);
this.onFailedValidation(request, response);
if (this.exceptionOnValidationFailure) {
throw new ServletException(var8);
}
response.sendError(403, var8.getMessage());
return;
}
}
//没有ticket直接放行
filterChain.doFilter(request, response);
}
}
客户端:重定向之后的请求是没有 ticket的,所以经过上述的过滤器会直接放行,放行后来到下一个过滤器 AuthenticationFilter
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
//判断请求需不需要拦截
if (this.isRequestUrlExcluded(request)) {
this.logger.debug("Request is ignored.");
filterChain.doFilter(request, response);
} else {
HttpSession session = request.getSession(false);
Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;
//获取请求中的 _const_cas_assertion_值
if (assertion != null) {
//如果以上过滤器检验通过此时是有值得,直接放行请求
filterChain.doFilter(request, response);
} else {
String serviceUrl = this.constructServiceUrl(request, response);
String ticket = this.retrieveTicketFromRequest(request);
boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);
//无 _const_cas_assertion_值,再次判断有无 ticket
if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {
//也没有ticket重定向到配置文件配置的服务器
this.logger.debug("no ticket and no assertion found");
String modifiedServiceUrl;
if (this.gateway) {
this.logger.debug("setting gateway attribute in session");
modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
} else {
modifiedServiceUrl = serviceUrl;
}
this.logger.debug("Constructed service url: {}", modifiedServiceUrl);
//获取重定向请求
String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);
this.logger.debug("redirecting to \"{}\"", urlToRedirectTo);
//重定向发送
this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
} else {
filterChain.doFilter(request, response);
}
}
}
}
服务器端:接收到重定向请求后被拦截器 SingleSignOnlnterceptor
拦截
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
logger.trace("Single Sign On Interceptor");
AuthorizationUtils.authenticateWithCookie(request,authTokenService,sessionManager);
//判断请求是否有current_authentication值
if(AuthorizationUtils.isNotAuthenticated()) {
//没有则重定向 `/sign/static/index.html/#/passport/login?redirect_uri=http://localhost:9527/sign/authz/cas/login?service=http%3A%2F%2Flocalhost%3A8989%2Ftest1%2Findex`
String loginUrl = applicationConfig.getFrontendUri() + "/index.html/#/passport/login?redirect_uri=%s";
String redirect_uri = UrlUtils.buildFullRequestUrl(request);
String base64RequestUrl = Base64Utils.base64UrlEncode(redirect_uri.getBytes());
logger.debug("No Authentication ... Redirect to /passport/login , redirect_uri {} , base64 {}",
redirect_uri ,base64RequestUrl);
response.sendRedirect(String.format(loginUrl,base64RequestUrl));
return false;
}
//...
}
注意:
applicationConfig.getFrontendUri()
获取的是配置文件的maxkey.server.frontend.uri
,记得配置好
重定向登录界面后输入账号密码登录,登录成功成功后返回jwt信息
前端处理响应信息,设置token和ticket等信息
auth(authJwt: any) {
let user: User = {
name: `${authJwt.displayName}(${authJwt.username})`,
displayName: authJwt.displayName,
username: authJwt.username,
userId: authJwt.id,
avatar: './assets/img/avatar.svg',
email: authJwt.email,
passwordSetType: authJwt.passwordSetType
};
//token
this.cookieService.set(CONSTS.CONGRESS, authJwt.token, { path: '/' });
//ticket
this.cookieService.set(CONSTS.ONLINE_TICKET, authJwt.ticket, { domain: this.getSubHostName(), path: '/' });
if (authJwt.remeberMe) {
localStorage.setItem(CONSTS.REMEMBER, authJwt.remeberMe);
}
this.settingsService.setUser(user);
this.tokenService.set(authJwt);
this.tokenService.get()?.expired;
}
如果地址后面有拼接 redirect_uri
,则会重定向到拼接的地址,如上述的 http://localhost:9527/sign/authz/cas/login?service=http%3A%2F%2Flocalhost%3A8989%2Ftest1%2Findex
navigate(authJwt: any) {
// 重新获取 StartupService 内容,我们始终认为应用信息一般都会受当前用户授权范围而影响
this.startupService.load().subscribe(() => {
let url = this.tokenService.referrer!.url || '/';
if (url.includes('/passport')) {
url = '/';
}
if (localStorage.getItem(CONSTS.REDIRECT_URI) != null) {
this.redirect_uri = `${localStorage.getItem(CONSTS.REDIRECT_URI)}`;
localStorage.removeItem(CONSTS.REDIRECT_URI);
}
if (this.redirect_uri != '') {
console.log(`redirect_uri ${this.redirect_uri}`);
//重定向
location.href = this.redirect_uri;
}
this.router.navigateByUrl(url);
});
}
服务端:服务器接受重定向后的地址请求,并由 SingleSignOnInterceptor
再次拦截,相比未登录的,此时的请求携带了一些登录后设置的jwt信息,就可以设置 current_authentication
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response, Object handler)
throws Exception {
logger.trace("Single Sign On Interceptor");
//根据请求携带的信息设置 current_authentication
AuthorizationUtils.authenticateWithCookie(request,authTokenService,sessionManager);
if(AuthorizationUtils.isNotAuthenticated()) {
//...
}
//判断请求是否有 current_authentication值
if(AuthorizationUtils.isAuthenticated()){
logger.debug("preHandle {}",request.getRequestURI());
Apps app = (Apps)WebContext.getAttribute(WebConstants.AUTHORIZE_SIGN_ON_APP);
if(app == null) {
String requestURI = request.getRequestURI();
if(requestURI.contains("/authz/cas/login")) {//for CAS service
//获取`service`后面的值,即 http://localhost:8989/test1/index,并根据此service查询配置的应用信息
app = casDetailsService.getAppDetails(
request.getParameter(CasConstants.PARAMETER.SERVICE), true);
}
//...
}
if(app == null) {
logger.debug("preHandle app is not exist . ");
return true;
}
SignPrincipal principal = AuthorizationUtils.getPrincipal();
if(principal != null && app !=null) {
//判断是否有权限访问应用,有则放行
if(principal.getGrantedAuthorityApps().contains(new SimpleGrantedAuthority(app.getId()))) {
logger.trace("preHandle have authority access {}" , app);
return true;
}
}
logger.debug("preHandle not have authority access {}" , app);
response.sendRedirect(request.getContextPath()+"/authz/refused");
return false;
}
return true;
}
放行之后,再经过一系列操作跳转 http://localhost:8989/test1/index界面
成功登录后再次请求 http://localhost:8989/test1/index地址
此时请求中含有ticket,而客户端校验ticket没问题后会设置 _const_cas_assertion_
属性
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if (this.preFilter(servletRequest, servletResponse, filterChain)) {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
String ticket = this.retrieveTicketFromRequest(request);
if (CommonUtils.isNotBlank(ticket)) {
this.logger.debug("Attempting to validate ticket: {}", ticket);
try {
Assertion assertion = this.ticketValidator.validate(ticket, this.constructServiceUrl(request, response));
this.logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());
//在服务器 session中设置 _const_cas_assertion_属性
request.setAttribute("_const_cas_assertion_", assertion);
if (this.useSession) {
request.getSession().setAttribute("_const_cas_assertion_", assertion);
}
this.onSuccessfulValidation(request, response, assertion);
if (this.redirectAfterValidation) {
this.logger.debug("Redirecting after successful ticket validation.");
response.sendRedirect(this.constructServiceUrl(request, response));
return;
}
} catch (TicketValidationException var8) {
this.logger.debug(var8.getMessage(), var8);
this.onFailedValidation(request, response);
if (this.exceptionOnValidationFailure) {
throw new ServletException(var8);
}
response.sendError(403, var8.getMessage());
return;
}
}
filterChain.doFilter(request, response);
}
}
后续请求直接判断请求中是否有 const_cas_assertion
属性,有就说明登录过了,请求放行
public final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest)servletRequest;
HttpServletResponse response = (HttpServletResponse)servletResponse;
if (this.isRequestUrlExcluded(request)) {
this.logger.debug("Request is ignored.");
filterChain.doFilter(request, response);
} else {
HttpSession session = request.getSession(false);
Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;
if (assertion != null) {
//不为空请求放行
filterChain.doFilter(request, response);
}
//...
}
}
此时登录别的应用地址,由于服务器session中已经设置 _const_cas_assertion_属性值,所以也可以直接校验通过