CAS单点登录-自定义主题、界面 (十一)
在集成了sso之后,cas的登录界面一般都不满足上线要求,所以必须来一套自定义登录界面,当然了甚至会提出登录界面里面新增部门选择呀、区域选择等等这些业务性的
当然了我们还有以下的使用场景:
- 不同接入端登录页不一
- 默认主题
本章目标
- 图标改变
- 背景颜色改变
- 部分字体改变
疑问与介绍
官方文档:https://apereo.github.io/cas/5.1.x/installation/User-Interface-Customization.html
主题?
先介绍下什么叫主题,主题就意味着风格不一,目的就是为了在不同的接入端(service)展示不同的页面,就例如淘宝登录、天猫登录,其中登录点还是一个sso,但淘宝登录卖的广告是淘宝的,而天猫登录卖的广告是天猫的
简略看完后,会有以下的规范:
- 静态资源(js,css)存放目录为
src/main/resources/static
- html资源存(thymeleaf)放目录为
src/main/resources/templates
- 主题配置文件存放在
src/main/resources
并且命名为[theme_name].properties - 主题页面html存放目录为
src/main/resources/templates/<theme-id>
可能看完上面的规范会有一些疑问
主题渲染是怎么样的?
官方文档明确说明,登录页渲染文件为casLoginView.html
,那意味我们在主题具体目录下新增改文件并且按照cas要求写那就可以了
最终目的还是获取到对应的配置文件,渲染对应主题的登录页
接入服务如何指定主题?
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^https://www.example.org",
"name" : "MyTheme",
"theme" : "[theme_name]",
"id" : 1000
}
theme
为key指定配置文件id
若主题配置文件为test_theme.application
则"theme":"test_theme"
如何修改默认主题?
application.properties
cas.theme.defaultThemeName=[theme_id]
实战
新建主题配置文件
在src/main下新建demo.properties
demo.css.file=/themes/demo/css/demo.css
新建样式文件
由于上面指定了样式文件位置,我们必须在,src\main\resources\static\themes\demo\css
下建立demo.css
为了简单起见,h1标签的全为蓝色
h1 {
color: blue;
}
新建登录也文件
明码规定文件名为casLoginView.html
,路径为src/main/resources/templates/demo
为了简单起见,以最简单的方式展示出来
注意要点:
from表单的内容需要遵循一定的标准th:object
等等
<!DOCTYPE html>
<!--
~ 版权所有.(c)2008-2017.卡尔科技工作室
-->
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<title th:text="${#themes.code('demo.pageTitle')}"></title>
<link rel="stylesheet" th:href="@{${#themes.code('demo.css.file')}}"/>
</head>
<body>
<h1 th:text="${#themes.code('demo.pageTitle')}"></h1>
<div>
<form method="post" th:object="${credential}">
<div th:if="${#fields.hasErrors('*')}">
<span th:each="err : ${#fields.errors('*')}" th:utext="${err}"/>
</div>
<h2 th:utext="#{screen.welcome.instructions}"></h2>
<section class="row">
<label for="username" th:utext="#{screen.welcome.label.netid}"/>
<div th:unless="${openIdLocalId}">
<input class="required"
id="username"
size="25"
tabindex="1"
type="text"
th:disabled="${guaEnabled}"
th:field="*{username}"
th:accesskey="#{screen.welcome.label.netid.accesskey}"
autocomplete="off"/>
</div>
</section>
<section class="row">
<label for="password" th:utext="#{screen.welcome.label.password}"/>
<div>
<input class="required"
type="password"
id="password"
size="25"
tabindex="2"
th:accesskey="#{screen.welcome.label.password.accesskey}"
th:field="*{password}"
autocomplete="off"/>
</div>
</section>
<section>
<input type="hidden" name="execution" th:value="${flowExecutionKey}"/>
<input type="hidden" name="_eventId" value="submit"/>
<input type="hidden" name="geolocation"/>
<input class="btn btn-submit btn-block"
name="submit"
accesskey="l"
th:value="#{screen.welcome.button.login}"
tabindex="6"
type="submit"/>
</section>
</form>
</div>
</body>
</html>
修改service主题
{
"@class": "org.apereo.cas.services.RegexRegisteredService",
"serviceId": "^http://(localhost|192.168).*",
"name": "Local Services",
"id": 10000002,
"description": "this is a localhost service",
"evaluationOrder": 10000000,
"theme":"demo"
}
重点为: "theme":"demo"
最终效果如下
bug和疑问
- cas版本
5.1.0~5.1.5
不支持默认主题目录(application.properties配置了cas.theme.defaultThemeName
不会加载主题目录下src/main/resources/templates/[theme_id]/casLoginView.html
文件,解决办法为覆盖方式) - 由于访问第一次的时候默认会对页面进行缓存,需要
spring.thymeleaf.cache=false
- 当访问cas携带service,主题目录生效
- 上述问题在cas
5.2.x
得到 解决
加载代码如下:
发现,如果没有service(39~52行代码)是不会到主题目录下加载主题页面
public class RegisteredServiceThemeBasedViewResolver extends ThymeleafViewResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(RegisteredServiceThemeBasedViewResolver.class);
private final ServicesManager servicesManager;
private final ArgumentExtractor argumentExtractor;
private final String prefix;
private final String suffix;
public RegisteredServiceThemeBasedViewResolver(final ServicesManager servicesManager,
final ArgumentExtractor argumentExtractor,
final String prefix,
final String suffix) {
this.servicesManager = servicesManager;
this.argumentExtractor = argumentExtractor;
this.prefix = prefix;
this.suffix = suffix;
}
@Override
protected View loadView(final String viewName, final Locale locale) throws Exception {
final View view = super.loadView(viewName, locale);
final RequestContext requestContext = RequestContextHolder.getRequestContext();
final WebApplicationService service;
final HttpServletResponse response;
final List<ArgumentExtractor> argumentExtractorList = Collections.singletonList(this.argumentExtractor);
if (requestContext != null) {
response = WebUtils.getHttpServletResponse(requestContext);
service = WebUtils.getService(argumentExtractorList, requestContext);
} else {
final HttpServletRequest request = WebUtils.getHttpServletRequestFromRequestAttributes();
service = WebUtils.getService(argumentExtractorList, request);
response = WebUtils.getHttpServletResponseFromRequestAttributes();
}
if (service == null) {
return view;
}
final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
if (registeredService != null) {
try {
RegisteredServiceAccessStrategyUtils.ensureServiceAccessIsAllowed(service, registeredService);
} catch (final Exception e) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
}
if (registeredService != null && StringUtils.hasText(registeredService.getTheme()) && view instanceof AbstractThymeleafView) {
LOGGER.debug("Attempting to locate views for service [{}] with theme [{}]",
registeredService.getServiceId(), registeredService.getTheme());
final AbstractThymeleafView thymeleafView = (AbstractThymeleafView) view;
final String viewUrl = registeredService.getTheme() + '/' + thymeleafView.getTemplateName();
final String viewLocationUrl = prefix.concat(viewUrl).concat(suffix);
LOGGER.debug("Attempting to locate view at [{}]", viewLocationUrl);
final TemplateLocation location = new TemplateLocation(viewLocationUrl);
if (location.exists(getApplicationContext())) {
LOGGER.debug("Found view [{}]", viewUrl);
thymeleafView.setTemplateName(viewUrl);
} else {
LOGGER.debug("View [{}] does not exist. Falling back to default view at [{}]", viewLocationUrl, thymeleafView.getTemplateName());
}
}
return view;
}
}
发现一些意外的事情可以考虑翻翻前面的博客进行学习哦
作者联系方式
如果技术的交流或者疑问可以联系或者提出issue。
QQ: 756884434 (请注明:SSO-CSDN)