前言:
通过对CAS架构的学习,模仿实现了基于Cookie和过滤器的单点登录。并且利用Spring Boot中的自配置,来移除客户端重复配置。
- 代码地址:https://gitee.com/marvelcode/marvelcode-sso
- Lombok:通过对应的注解,在编译源码的时候生成对应的方法,保证源代码的整洁。常用的有@Getter、@Setter、@Data等。
流程图:
- 先大致讲下流程,用户请求service-1,过滤器拦截请求后,从cookie中获取TGT(一个证明用户已登录的票据),如果没有取到,就重定向sso-server的登录页,并传递原本的请求(方便登录成功后重定向回原url);
- 用户名密码验证通过后,就生成ST(一个与服务绑定的票据,说明用户有权访问该服务)拼接在原url后重定向,并同时生成cookie用于存放TGT(这里用到了共享cookie的技巧);
- 这时会再次被过滤器拦截,校验ST的合法性,同样是请求sso-server,在验证通过后放行。
- 如果用户访问service-2,过滤器检测到TGT的存在,就会去sso-server验证TGT,并在验证通过后生成对应服务的ST,同样的重定向...
客户端:
- 抽象过滤器:
package com.menghao.sso.client.filter;
import com.menghao.sso.client.util.CommonUtils;
import lombok.Setter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* <p>客户端过滤器抽象类.<br>
*
* @author menghao.
* @version 2017/11/15.
*/
public abstract class AbstractCasFilter implements Filter {
protected final Log log = LogFactory.getLog(this.getClass());
/*
请求服务主机名
*/
@Setter
private String clientHost;
/*
cas服务端地址
*/
@Setter
protected String serverHost;
private static final String HTTP = "http://";
// ...省略若干构建url方法
protected String makeOriginalRequest(HttpServletRequest request, HttpServletResponse response) {
StringBuilder builder = new StringBuilder();
builder.append(request.isSecure() ? "https://" : "http://");
builder.append(clientHost);
builder.append(request.getRequestURI());
// 如果存在查询参数,将参数抽取拼接
if (StringUtils.hasLength(request.getQueryString())) {
int index = request.getQueryString().indexOf(CommonUtils.ST_ID + "=");
// 默认规则ticket放在查询参数最后
if (index == -1) {
builder.append("?").append(request.getQueryString());
} else if (index == 0) {
// do nothing
} else {
index = request.getQueryString().indexOf("&" + CommonUtils.ST_ID + "=");
if (index == -1) {
builder.append("?").append(request.getQueryString());
} else {
builder.append("?").append(request.getQueryString().substring(0, index));
}
}
}
final String returnValue = response.encodeURL(builder.toString());
if (log.isDebugEnabled()) {
log.debug("serviceUrl make: " + returnValue);
}
return returnValue;
}
protected abstract void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
/*
此步对request和response做了统一转型
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
doFilterInternal((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
@Override
public void destroy() {
}
}
抽象的主要目的,是为了对ServletRequest 和 ServletResponse的统一转型。其中代码省略了很多构造url的方法:比如登录、验证、注销等等。其中makeOriginalRequest是获取原本的url请求(过滤掉ServiceTicket后的原本url请求),该方法构造的url会传递给服务端,方便登录成功的重定向。
- 过滤器一:
package com.menghao.sso.client.filter;
import com.menghao.sso.client.util.CommonUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.WebUtils;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* <p>对TGT和ST有无校验.<br>
*
* @author menghao.
* @version 2017/11/15.
*/
public class AuthenticationFilter extends AbstractCasFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
Cookie cookie = WebUtils.getCookie(request, CommonUtils.TGT_ID);
// 无TGT,说明未登录
if (null == cookie || cookie.getValue() == null) {
String originalRequest = makeOriginalRequest(request, response);
// 没有ticket则重定向登录
String loginUrl = makeLoginRequest(originalRequest);
response.sendRedirect(loginUrl);
return;
}
// 有TGT,无ST,说明已登录但登录其他系统
String serviceTicket = request.getParameter(CommonUtils.ST_ID);
if (!StringUtils.hasText(serviceTicket)) {
String originalRequest = makeOriginalRequest(request, response);
String validateRequest = makeValidateTGTRequest(originalRequest, cookie.getValue());
response.sendRedirect(validateRequest);
return;
}
// 具备TGT和ST
filterChain.doFilter(request, response);
}
}
- 过滤器二:
package com.menghao.sso.client.filter;
import com.menghao.sso.client.model.ValidateBean;
import com.menghao.sso.client.util.CommonUtils;
import com.menghao.sso.client.validation.TicketValidator;
import com.menghao.sso.client.validation.ValidationException;
import lombok.Setter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* <p>对ST合法性校验.<br>
*
* @author menghao.
* @version 2017/11/15.
*/
public class CheckTicketFilter extends AbstractCasFilter {
@Setter
private TicketValidator ticketValidator;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
// 创建服务验证请求
String serviceTicket = request.getParameter(CommonUtils.ST_ID);
String originalRequest = makeOriginalRequest(request, response);
String validateRequest = makeValidateSTRequest(originalRequest);
// 发送验证请求
try {
ValidateBean validateBean = ValidateBean.builder().url(validateRequest).serviceTicket(serviceTicket).build();
Boolean success = ticketValidator.validate(validateBean);
if (success) {
filterChain.doFilter(request, response);
return;
}
} catch (ValidationException e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
log.warn(e, e);
throw new ServletException(e);
}
}
}
package com.menghao.sso.client.validation;
import com.menghao.sso.client.model.ValidateBean;
import lombok.Setter;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
/**
* <p>抽象校验类.<br>
*
* @author menghao.
* @version 2017/11/16.
*/
public abstract class AbstractTicketValidator implements TicketValidator {
@Setter
protected RestTemplate restTemplate;
@Setter
protected String casServerUrl;
/**
* 模版模式
*
* @param validateBean 验证包装类
* @return Boolean 是否验证通过
* @throws ValidationException
*/
@Override
public Boolean validate(ValidateBean validateBean) throws ValidationException {
try {
ResponseEntity<Boolean> responseEntity =
restTemplate.getForEntity(validateBean.getUrl(), Boolean.class, validateBean.getServiceTicket());
return parseResponse(responseEntity);
} catch (RestClientException e) {
throw new ValidationException(e);
}
}
protected abstract Boolean parseResponse(ResponseEntity<Boolean> responseEntity);
}
介绍完客户端的主要验证方案,来看看如何将客户端以插件的形式“配置”到各个需要验证的模块上。
- 自配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
package com.menghao.sso.client.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* <p>客户端配置属性.<br>
*
* @author menghao.
* @version 2017/11/6.
*/
@Data
@ConfigurationProperties(prefix = "menghao.sso")
public class SsoClientProperties {
/*
客户端<host>:<port>
*/
private String clientHost;
/*
服务端<host>:<port>
*/
private String serverHost;
/*
限制登录url,逗号分割
*/
private String restrictUrls;
}
package com.menghao.sso.client.config;
import com.menghao.sso.client.filter.AuthenticationFilter;
import com.menghao.sso.client.filter.CheckTicketFilter;
import com.menghao.sso.client.filter.WrapInfoFilter;
import com.menghao.sso.client.validation.PersonTicketValidator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
import java.util.List;
/**
* <p>客户端自动配置类.<br>
*
* @author menghao.
* @version 2017/11/16.
*/
@Configuration
@EnableConfigurationProperties(SsoClientProperties.class)
@ConditionalOnWebApplication
@ConditionalOnProperty(prefix = "menghao.sso", value = "enabled", matchIfMissing = false)
public class SsoClientAutoConfiguration {
private SsoClientProperties ssoClientProperties;
private List<String> urls;
public SsoClientAutoConfiguration(SsoClientProperties ssoClientProperties) {
this.ssoClientProperties = ssoClientProperties;
String strictUrls = ssoClientProperties.getRestrictUrls();
Assert.hasText(ssoClientProperties.getClientHost(), "服务主机地址必须指定");
Assert.hasText(strictUrls, "拦截地址必须指定");
// 初始化时,会将配置需要拦截的url分割成列表
urls = Arrays.asList(strictUrls.split(","));
}
@Bean
@ConditionalOnMissingBean(AuthenticationFilter.class)
public FilterRegistrationBean registerAuthenticationFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
AuthenticationFilter authenticationFilter = new AuthenticationFilter();
authenticationFilter.setServerHost(ssoClientProperties.getServerHost());
authenticationFilter.setClientHost(ssoClientProperties.getClientHost());
filterRegistrationBean.setFilter(authenticationFilter);
// 配置过滤器需要拦截的url列表
filterRegistrationBean.setUrlPatterns(urls);
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}
// ...省略其他过滤器配置
}
SsoClientProperties封装了一些可配置的信息。
- @ConfigurationProperties:将application.properties文件中设置的以“menghao.sso”+属性名的属性加载至该Bean,方便自配置时使用。
SsoClientAutoConfiguration则是真正自配置的实现。
- @Configuration:将该类映射为XML配置中的<beans>标签;
- @EnableConfigurationProperties:开启对@ConfigurationProperties标记的Bean支持;
- @ConditionalOnXxx:在满足指定条件下才使得该配置生效。其中多个注解同时标识时是条件与的关系。例如@ConditionalOnProperty(prefix = "menghao.sso", value = "enabled", matchIfMissing = false),就是在“menghao.sso.enabled”属性未设置的情况下配置不生效,即客户端默认不开启;
- @Bean:通过标识方法,将方法返回的Bean交由Spring管理,等同于XML配置的<bean>标签。
最后一步,在路径/resources/META-INF路径下,创建spring.factories文件,并添加:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.menghao.sso.client.config.SsoClientAutoConfiguration
这样,将客户端打成Jar包,并在需要的模块引入依赖,就可以在Spring Boot启动时,自动加载该配置类(前提是满足@ConditionOnXxx的条件)
如果有额外的属性需要指定,可以通过additional-spring-configuration-metadata.json文件中声明,该文件与spring.factories在同一级目录,格式如下:
{
"properties": [
{
"name": "menghao.sso.enabled",
"type": "java.lang.Boolean",
"description": "自定义单点登录客户端.",
"defaultValue": false
}
]
}
- 配置属性:
只需要在需要的模块引入即可,指定服务端,客户端地址,拦截的url请求正则。
# true开启,false关闭
menghao.sso.enabled = true
menghao.sso.cas-server-host = localhost:1000
menghao.sso.cas-client-host = localhost:1001
# 配置拦截的url,多个用逗号分割
menghao.sso.restrict-urls = /*
服务端:
其中使用到了两种票据,TicketGrantingTicket (TGT)和 ServiceTicket(ST)。在登录成功时,往Cookie中放入TGT,在url上拼接ST;在校验时,如果有ST,直接校验,如果没有则获取TGT,校验通过后授予ST,一切校验失败的行为都会抛出ValidateFailException异常(自定义),并交由异常统一处理返回登录界面。
- 验证与授权:
来看下主要的两个业务实现类:
package com.menghao.sso.server.service;
import com.menghao.sso.server.exception.ValidateFailException;
import com.menghao.sso.server.model.Service;
import com.menghao.sso.server.model.credentials.Credentials;
import com.menghao.sso.server.model.credentials.UsernamePasswordCredentials;
import com.menghao.sso.server.model.ticket.ServiceTicket;
import com.menghao.sso.server.model.ticket.TicketGrantingTicket;
import com.menghao.sso.server.registry.TicketRegistry;
import com.menghao.sso.server.repository.UCredentialsRepository;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
/**
* <p>认证Service实现.<br>
*
* @author menghao.
* @version 2017/11/17.
*/
@org.springframework.stereotype.Service
public class AuthenticationServiceImpl implements AuthenticationService {
private final Log log = LogFactory.getLog(this.getClass());
@Autowired
private UCredentialsRepository uCredentialsRepository;
@Autowired
private TicketRegistry ticketRegistry;
@Override
public void validateCredentials(Credentials credentials) throws ValidateFailException {
if (credentials instanceof UsernamePasswordCredentials) {
UsernamePasswordCredentials usernamePasswordCredentials =
uCredentialsRepository.queryByProperty((UsernamePasswordCredentials) credentials);
// 验证通过
if (usernamePasswordCredentials == null) {
throw new ValidateFailException("用户名与密码不匹配");
}
} else {
throw new ValidateFailException("暂不支持的认证方式");
}
}
@Override
public void validateGrantingTicket(String ticketGrantingTicketId) throws ValidateFailException {
if (ticketGrantingTicketId == null) {
throw new ValidateFailException("未检测到票据信息,请登录");
}
final TicketGrantingTicket ticketGrantingTicket = (TicketGrantingTicket) this.ticketRegistry.getTicket(ticketGrantingTicketId);
if (ticketGrantingTicket == null) {
log.debug("TicketGrantingTicket [" + ticketGrantingTicket + "] does not exist.");
throw new ValidateFailException("票据信息校验未通过,请登录");
}
if (ticketGrantingTicket.isExpired()) {
log.debug("ServiceTicket [" + ticketGrantingTicket + "] has expired.");
this.ticketRegistry.deleteTicket(ticketGrantingTicketId);
throw new ValidateFailException("身份验证已过期,请重新登录");
}
ticketGrantingTicket.updateLastTimeUsed();
}
@Override
public void validateServiceTicket(String serviceTicketId, Service service) throws ValidateFailException {
if (serviceTicketId == null || service == null) {
throw new ValidateFailException("未检测到票据信息,请登录");
}
final ServiceTicket serviceTicket = (ServiceTicket) this.ticketRegistry.getTicket(serviceTicketId);
if (serviceTicket == null) {
log.debug("ServiceTicket [" + serviceTicketId + "] does not exist.");
throw new ValidateFailException("票据信息校验未通过,请登录");
}
if (serviceTicket.isExpired()) {
log.debug("ServiceTicket [" + serviceTicketId + "] has expired.");
this.ticketRegistry.deleteTicket(serviceTicketId);
throw new ValidateFailException("身份验证已过期,请重新登录");
}
serviceTicket.incrementCountOfUses();
serviceTicket.updateLastTimeUsed();
if (serviceTicket.isExpired()) {
log.debug("ServiceTicket [" + serviceTicketId + "] has expired.");
this.ticketRegistry.deleteTicket(serviceTicketId);
throw new ValidateFailException("身份验证已过期,请重新登录");
}
if (!service.equals(serviceTicket.getService())) {
log.debug("ServiceTicket [" + serviceTicketId + "] does not match supplied service.");
throw new ValidateFailException("票据信息与服务不匹配,请登录");
}
}
}
package com.menghao.sso.server.service;
import com.menghao.sso.server.exception.InvalidTicketException;
import com.menghao.sso.server.exception.ValidateFailException;
import com.menghao.sso.server.model.Principal;
import com.menghao.sso.server.model.Service;
import com.menghao.sso.server.model.SimplePrincipal;
import com.menghao.sso.server.model.credentials.Credentials;
import com.menghao.sso.server.model.credentials.UsernamePasswordCredentials;
import com.menghao.sso.server.model.ticket.ServiceTicket;
import com.menghao.sso.server.model.ticket.TicketGrantingTicket;
import com.menghao.sso.server.model.ticket.TicketGrantingTicketImpl;
import com.menghao.sso.server.model.validation.Authentication;
import com.menghao.sso.server.registry.ExpirationPolicy;
import com.menghao.sso.server.registry.TicketRegistry;
import com.menghao.sso.server.util.TicketIdGenerator;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import java.util.HashMap;
/**
* <p>授权Service实现.<br>
*
* @author menghao.
* @version 2017/11/17.
*/
@org.springframework.stereotype.Service
public class AuthorizationServiceImpl implements AuthorizationService {
private final Log log = LogFactory.getLog(this.getClass());
@Autowired
private TicketRegistry ticketRegistry;
@Autowired
private ExpirationPolicy expirationPolicy;
@Override
public String createTicketGrantingTicket(Credentials credentials) throws ValidateFailException {
if (credentials instanceof UsernamePasswordCredentials) {
String username = ((UsernamePasswordCredentials) credentials).getUsername();
if (!StringUtils.hasText(username)) {
throw new ValidateFailException("无法获取用户名");
}
String tgtId = TicketIdGenerator.newTGTId();
Principal principal = new SimplePrincipal(username);
Authentication authentication = new Authentication(principal, new HashMap());
ticketRegistry.addTicket(new TicketGrantingTicketImpl(tgtId, authentication, this.expirationPolicy));
return tgtId;
}
return null;
}
@Override
public String createServiceTicket(String ticketGrantingTicketId, Service service) throws ValidateFailException {
if (!StringUtils.hasText(ticketGrantingTicketId)) {
throw new ValidateFailException("未检测到票据信息,请登录");
}
TicketGrantingTicket ticketGrantingTicket = (TicketGrantingTicket) ticketRegistry.getTicket(ticketGrantingTicketId);
if (ticketGrantingTicket == null) {
throw new ValidateFailException("票据信息校验未通过,请登录");
}
if (ticketGrantingTicket.isExpired()) {
throw new ValidateFailException("身份验证已过期,请重新登录");
}
final ServiceTicket serviceTicket = ticketGrantingTicket.grantServiceTicket(
TicketIdGenerator.newSTId(), service, this.expirationPolicy);
this.ticketRegistry.addTicket(serviceTicket);
log.info("Granted service ticket ["
+ serviceTicket.getId()
+ "] for service ["
+ service.getUrl()
+ "] for user ["
+ serviceTicket.getGrantingTicket().getAuthentication()
.getPrincipal().getUrl() + "]");
return serviceTicket.getId();
}
@Override
public void destroyTicketGrantingTicket(String ticketGrantingTicketId) throws InvalidTicketException {
if (!StringUtils.hasText(ticketGrantingTicketId)) {
throw new InvalidTicketException();
}
ticketRegistry.deleteTicket(ticketGrantingTicketId);
}
}
这两个类中几乎包含了所有的服务端处理逻辑,Controller层就是借助这两个类的方法,拼装后实现的。其中注入的 TicketRegistry(票据注册)和 ExpirationPolicy(票据过期)代码讲解在下面。
- 异常统一处理:
package com.menghao.sso.server.exception;
import lombok.Getter;
/**
* <p>校验失败异常.<br>
*
* @author menghao.
* @version 2017/11/20.
*/
public class ValidateFailException extends Exception {
public ValidateFailException(String msg) {
super();
this.msg = msg;
}
public ValidateFailException(String service, String msg) {
this(msg);
this.service = service;
}
@Getter
private String msg;
@Getter
private String service;
}
package com.menghao.sso.server.controller.advice;
import com.menghao.sso.server.exception.ValidateFailException;
import com.menghao.sso.server.util.CommonUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.ModelAndView;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
/**
* <p>异常统一处理类.<br>
*
* @author menghao.
* @version 2017/11/20.
*/
@ControllerAdvice
public class ExceptionController {
@ExceptionHandler(ValidateFailException.class)
public ModelAndView validateException(ValidateFailException e) throws UnsupportedEncodingException {
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("msg", URLEncoder.encode(e.getMsg(), "UTF-8"));
modelAndView.addObject(CommonUtils.SERVICE, e.getService());
modelAndView.setViewName("redirect:" + CommonUtils.LOGIN_URL);
return modelAndView;
}
}
除了验证通过的其他任何情况,如过期,不存在等等,都会抛出 ValidateFailException 异常,通过异常统一处理,重定向至登录页,并将提示信息封装到 request 域中。
- 票据注册:
为了方便横向扩展,将注册策略抽象为接口,目前只实现了一种:
package com.menghao.sso.server.registry;
import com.menghao.sso.server.model.ticket.Ticket;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* <p>默认的票据注册实现.<br>
*
* @author menghao.
* @version 2017/11/21.
*/
public final class DefaultTicketRegistry implements TicketRegistry {
private final Log log = LogFactory.getLog(getClass());
private final Map<String, Ticket> cache = new HashMap<String, Ticket>();
public synchronized void addTicket(final Ticket ticket) {
if (ticket == null) {
throw new IllegalArgumentException("ticket cannot be null");
}
log.debug("Added ticket [" + ticket.getId() + "] to registry.");
this.cache.put(ticket.getId(), ticket);
}
public synchronized Ticket getTicket(final String ticketId) {
log.debug("Attempting to retrieve ticket [" + ticketId + "]");
final Ticket ticket = this.cache.get(ticketId);
if (ticket != null) {
log.debug("Ticket [" + ticketId + "] found in registry.");
}
return ticket;
}
public synchronized boolean deleteTicket(final String ticketId) {
log.debug("Removing ticket [" + ticketId + "] from registry");
return (this.cache.remove(ticketId) != null);
}
public synchronized Collection getTickets() {
return Collections.unmodifiableCollection(this.cache.values());
}
}
- 票据过期:
为了方便横向扩展,将过期策略抽象为接口,目前只实现了两种:
package com.menghao.sso.server.registry;
import com.menghao.sso.server.model.ticket.Ticket;
/**
* 基于过期时间:最近一次使用的使用
*/
public final class TimeoutExpirationPolicy implements ExpirationPolicy {
private final long timeToKillInMilliSeconds;
public TimeoutExpirationPolicy(final long timeToKillInMilliSeconds) {
this.timeToKillInMilliSeconds = timeToKillInMilliSeconds;
}
public boolean isExpired(final Ticket ticket) {
return (ticket == null) || (System.currentTimeMillis() - ticket.getLastTimeUsed() >= this.timeToKillInMilliSeconds);
}
}
package com.menghao.sso.server.registry;
import com.menghao.sso.server.model.ticket.Ticket;
/**
* 永不过期
*/
public final class NeverExpirationPolicy implements ExpirationPolicy {
public boolean isExpired(final Ticket ticket) {
return false;
}
}
- 自配置:
package com.menghao.sso.server.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* <p>服务端配置属性Bean.<br>
*
* @author menghao.
* @version 2017/11/17.
*/
@Data
@ConfigurationProperties(prefix = "menghao.sso.server")
public class SsoServerProperties {
/*
缓存策略
*/
private String ticketCache = "default";
}
package com.menghao.sso.server.config;
import com.menghao.sso.server.registry.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* <p>服务端自配置类<br>
*
* @author menghao.
* @version 2017/11/17.
*/
@Configuration
public class SsoServerAutoConfiguration {
@Bean
@ConditionalOnProperty(prefix = "menghao.sso.server", name = "ticketCache", havingValue = "default")
public TicketRegistry defaultRegistry() {
return new DefaultTicketRegistry();
}
@Bean
public ExpirationPolicy expirationPolicy() {
return new TimeoutExpirationPolicy(1000 * 60 * 60);
}
}
默认注册策略,采用内存放置Map的形式,存储 ticketId-Ticket键值对。默认的过期策略,1小时的间隔使用时间。
总结:
目前只完成了单个请求的校验逻辑,如果是服务间调用,按照Cas原本架构中,是以代理票据实现的,目前还不能支持。该架构只是为了能够对Cas架构有更好的理解,而进行的拆分整理,单纯的实现了基本功能,对于并发等情况未做考虑。