概述
场景一、网站部分文章需要Cas认证(登录平台)通过后才能访问
1、通过拦截器,把文章访问路径进行拦截,判断当前文章是否需要cas认证
2、如果需要认证,那么调用CAS服务端进行认证,成功后回调地址
3、根据回调地址,返回文章内容
场景二、通过登录平台Cas认证(登录平台)后,不需要再登录就能访问业务系统
1、登录平台系统
2、通过平台,点击业务系统(一般平台上看到业务系统图片链接)
3、进入cas服务端认证中心
4、认证通过后,回调地址(调用业务系统)并返回用户username等信息
5、业务系统根据返回用户,校验信息并返回前端需要的内容(如:菜单、cookie、token等等)
说明:把自己登录页面换成平台登录登录页面,不是真正意义上单点登录,通过哪个登录入口都可以。
cas原理
来自百度百科一张图片,其实也是官网一张图片,其实百度百科有解释,这里我用自己的理解再复述一遍。
1、用户访问CAS Client请求资源
比如我们配置的路径:cas.server-login-url=http://XXX:8989/cas/login
2、客户端程序做了重定向,重定向到CAS Server
我们在网址上看到路径:http://XXX/cas?service=http://localhost:8888/xxx/
http://localhost:8888是业务系统网址(一般客户端配置在业务系统上,在类上使用@EnableCasClient标识为cas客户端)
3、CAS Server会对请求做认证,验证是否有TGC(Ticket Granted Cookie,有TGC说明已经登录过,不需要再登录,没有就返回登录页面
4、认证通过后会生成一个Service Ticket返回Cas Client,客户端进行Ticket缓存,一般放在Cookie里,我们称之为TGC(Ticket Granted Cookie)
5、然后Cas Client就带着Ticket再次访问Cas Server,CAS Server进行Ticket验证
6、CAS Server对Ticket进行验证,回调业务系统地址并返回用户信息
回调地址:前面第二步service后面的地址--》http://localhost:8888/xxx
cas客户端配置
包引进代码-》:
<dependency>
<groupId>net.unicon.cas</groupId>
<artifactId>cas-client-autoconfig-support</artifactId>
<version>2.3.0-GA</version>
</dependency>
一、网站部分文章需要Cas认证通过后才能访问
配置代码-》
// 1、配置文件的内容
cas:
server-url-prefix: http://cas.XXX:8989/cas
server-login-url: http://cas.XXX:8989/cas/login
client-host-url: http://localhost:8888
validation-type: cas
authentication-url-patterns:
/cas_authentication
// 备注:authentication-url-patterns 是正则表达式,在重定向路径有这个(/cas_authentication),cas会拦截并进行认证
// 2、配置cas客户端以及服务地址与客户端访问地址
@Configuration @EnableCasClient
public class CasAuthConfig extends CasClientConfigurerAdapter {
@Value("${cas.server-url-prefix}") private String casServerUrlPrefix;
@Value("${cas.client-host-url}") private String casClientHostUrl;
@Override
public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
// 设定匹配的路径
// authenticationFilter.addUrlPatterns(API_CAS_AUTH);
Map<String, String> initParameters = new HashMap<>();
initParameters.put(ConfigurationKeys.CAS_SERVER_LOGIN_URL.getName(), this.casServerUrlPrefix);
initParameters.put(ConfigurationKeys.SERVER_NAME.getName(), this.casClientHostUrl);
authenticationFilter.setInitParameters(initParameters);
}
}
// 3、拦截器,重定向到cas服务端认证后,会判断是否登录,如果没有登录,则先跳转到cas登录框(看1配置server-login-url)
@Component
public class ArchiveInterceptor extends HandlerInterceptorAdapter {
private static final Logger logger = LoggerUtils.getPlatformLogger();
@Autowired
private ArchivesService archivesService;
@Value("${cas.client-host-url}") private String casClientHostUrl;
@Value("${cas.authentication-url-patterns}") private String casAuth;
private static final String INTERCEPTOR_URL = "/article/";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getRequestURI().contains(INTERCEPTOR_URL)) {
logger.info("拦截路径 uri = {}", request.getRequestURI());
String id = StringUtils.substringAfter(request.getRequestURI(), INTERCEPTOR_URL);
if (StringUtils.isEmpty(id)) {
throw new TemplateNotFoundException(
ExceptionEnum.HTTP_BAD_REQUEST.getCode(),
ExceptionEnum.HTTP_BAD_REQUEST.getMessage(),
"转入的路径不正确");
}
Archives archives = archivesService.selectByPrimaryKey(id);
//判断下文章是否需要登录
if (!Objects.isNull(archives.getIsCheckLogin()) && archives.getIsCheckLogin() == 1) {
String pathUrl = casClientHostUrl + casAuth + "?redirectUrl=" + request.getRequestURI();
logger.info("重定网址:{}", pathUrl);
response.sendRedirect(pathUrl);
}
}
return Boolean.TRUE;
}
}
// 控制器(文章路径)
@RequestMapping("/article/{id}")
public void article(@PathVariable String id) throws CmsException {
//返回文章内容
}
@RequestMapping("/cas_authentication")
public void redirect(@RequestParam(value = "redirectUrl") String redirectUrl) throws CmsException {
if (Strings.isEmpty(redirectUrl)) return;
//返回文章内容
}
备注:为什么cas认证成功后访问文章路径不一样?
这里主要是避免拦截器死循环,重定向都同一个路径,会不断判断并进入死循环。
那为什么不对返回的用户(或token)进行判断,有用户了不就证明登录成功了吗,不用重定向了?
因为文章访问原本就没有用户,所有无法判断。
二、通过登录平台Cas认证后,不需要再登录就能访问业务系统
供配置代码-》
// 1、配置cas
cas.server-url-prefix=http://XXX/cas
cas.server-login-url=http://XXX/cas/login
cas.client-host-url=http://localdev
cas.validation-url-patterns=/cas_authentication
// 2、cas客户端配置
@Slf4j
@Configuration @EnableCasClient @Controller
public class CasControlFacade extends CasClientConfigurerAdapter {
private static final int COOKIE_MAX_AGE = 30 * 60 * 24;
@Value("${cas.server-url-prefix}") @Getter private String casServerUrlPrefix;
@Value("${cas.client-host-url}") @Getter private String casClientHostUrl;
@Resource private SessionUserApp userApp;
/**
* 受cas保护的路径, 回跳时会先使用filter进行验证Ticket,进入控制器后已经是合法用户
*/
public String casAuth(Model model, HttpServletRequest request, HttpServletResponse response) {
model.addAttribute("hasError", false);
Principal principal = request.getUserPrincipal();
if (Objects.isNull(principal) || !(principal instanceof AttributePrincipal)) {
model.addAttribute("error", buildErrorMsg("Invalid signin request"));
model.addAttribute("hasError", true);
return "index";
}
AttributePrincipal loginResult= (AttributePrincipal) principal;
log.debug("cas联合登录成功, username = {}, userPrincipal = {}", loginResult.username, loginResult.principal);
CheckUserExistOutput isUserExist = userApp.isUserExist(new CheckUserExistInput(loginResult.username));
if (!isUserExist.getIsExist()) {
log.warn("cas回传参数异常, 本地用户不存在, username = {}, userPrincipal = {}", loginResult.username, loginResult.principal);
model.addAttribute("error", buildErrorMsg("用户[" + loginResult.username + "]不存在"));
model.addAttribute("hasError", true);
return "index";
}
// 直接使用uid快捷登录, 前面的检测保证到这里肯定成功
SigninOutput result = userApp.signin(DirectSigninInput.of(loginResult.username));
response.addCookie(generateCookie(result.getToken()));
response.addCookie(generateLoginFlagCookie());
model.addAttribute("payload", result);
return "index";
}
@Override
public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
// 设定匹配的路径
authenticationFilter.addUrlPatterns(API_CAS_AUTH);
Map<String, String> initParameters = Maps.newHashMap();
log.debug("[CAS] initial CAS with loginUrl = {}", this.casServerUrlPrefix);
log.debug("[CAS] initial CAS with serverName = {}", this.casClientHostUrl);
initParameters.put(ConfigurationKeys.CAS_SERVER_LOGIN_URL.getName(), this.casServerUrlPrefix);
initParameters.put(ConfigurationKeys.SERVER_NAME.getName(), this.casClientHostUrl);
authenticationFilter.setInitParameters(initParameters);
}
}