1. Obtain CAS5.3 project resources
GitHub - apereo/cas-overlay-template at 5.3
cas5.3.x still runs based on jdk8, and the next version 6.0.x will be based on jdk9. With the upgrade of cas version, the jdk version is getting higher and higher. There are instructions on the basic operating conditions of each version on the official website and github , select the version according to the actual situation.
Two. Tomcat deployment
Use maven-package to make war package
2. Put the war package in the webapps directory of tomcat and start tomcat
3. Support HTTP protocol
Modify the tomcat/webapps/cas/WEB-INF/classes/services/HTTPSandIMAPS-10000001.json file and add http support. The modified code is as follows.
Modify the tomcat/webapps/cas/WEB-INF/classes/application.properties file
Add the following two lines
cas.tgc.secure=false
cas.serviceRegistry.initFromJson=true
Comment out the following three lines
#server.ssl.key-store=file:/etc/cas/thekeystore
#server.ssl.key-store-password=changeit
#server.ssl.key-password=changeit
Restart tomcat, visit the login page to log in
The cas default password is in the last line of the application.properties file
cas.authn.accept.users=casuser::Mellon
4. Front-end code
Modify Navbar.vue to override the logout() method
async logout() {
this.$confirm('确定注销并退出系统吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$store.dispatch('LogOut').then(() => {
if (!defaultSettings.casEnable) {
location.href = this.$router.options.base + "/index";
}
//location.href = '/index';
})
}).catch(() => {});
}
Modify the permission.js file, copy the code and paste it to the red box, modify the address appropriately, and pay attention to the import package.
if (!defaultSettings.casEnable) {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
}
// 开启cas
if (defaultSettings.casEnable) {
window.location.href = defaultSettings.casloginUrl // 否则全部重定向到登录页
}
Modify the settings.js file, add the following configuration, and modify the address appropriately. Before the service is the cas service address deployed earlier, and after the service is the backend service address of your own system
/**
* 开启cas
*/
casEnable: false,
/**
* 单点登录url
*/
casloginUrl:
'http://cpmp.fulongai.cn/cas/login?service=http://192.168.2.154:8080',
/**
* 单点登出url
*/
caslogoutUrl:
'http://cpmp.fulongai.cn/cas/logout?service=http://192.168.2.154:8080'
Modify the user.js file, replace the LogOut method, and pay attention to the import package.
LogOut({ commit, state }) {
return new Promise((resolve, reject) => {
logout(state.token).then(() => {
commit('SET_TOKEN', '')
commit('SET_ROLES', [])
commit('SET_PERMISSIONS', [])
commit('SET_PROJECT', {})
sessionStorage.removeItem('dmp-projectId')
removeToken()
resolve()
window.location.href = defaultSettings.caslogoutUrl
}).catch(error => {
reject(error)
})
})
}
Modify the request.js file and comment out the code in the red box
5. Backend code
Add the CasUserDetailsService.java class under framework.web.service
package com.cpmplatform.framework.web.service;
import com.cpmplatform.common.core.domain.entity.SysUser;
import com.cpmplatform.common.core.domain.model.LoginUser;
import com.cpmplatform.common.enums.UserStatus;
import com.cpmplatform.common.exception.base.BaseException;
import com.cpmplatform.common.utils.StringUtils;
import com.cpmplatform.system.service.ISysUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.cas.authentication.CasAssertionAuthenticationToken;
import org.springframework.security.core.userdetails.AuthenticationUserDetailsService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* @author daixin
* @version 1.0
* @description: TODO
* @date 2022/12/30 17:41
*/
@Service
public class CasUserDetailsService implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private ISysUserService userService;
@Autowired
private SysPermissionService permissionService;
public UserDetails createLoginUser(SysUser user) {
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}
@Override
public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {
String username = token.getName();
SysUser user = userService.selectUserByUserName(username);
if (StringUtils.isNull(user)) {
log.info("登录用户:{} 不存在.", username);
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
log.info("登录用户:{} 已被删除.", username);
throw new BaseException("对不起,您的账号:" + username + " 已被删除");
} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
log.info("登录用户:{} 已被停用.", username);
throw new BaseException("对不起,您的账号:" + username + " 已停用");
}
return createLoginUser(user);
}
}
Add the CasAuthenticationSuccessHandler.java class under framework.security.handle
package com.cpmplatform.framework.security.handle;
import com.cpmplatform.common.constant.Constants;
import com.cpmplatform.common.core.domain.model.LoginUser;
import com.cpmplatform.framework.config.properties.CasProperties;
import com.cpmplatform.framework.web.service.TokenService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* @author daixin
* @version 1.0
* @description: TODO
* @date 2022/12/30 17:43
*/
@Service
public class CasAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
protected final Log logger = LogFactory.getLog(this.getClass());
private RequestCache requestCache = new HttpSessionRequestCache();
@Autowired
private TokenService tokenService;
@Autowired
private CasProperties casProperties;
// 令牌有效期(默认30分钟)
@Value("${token.expireTime}")
private int expireTime;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws ServletException, IOException {
String targetUrlParameter = getTargetUrlParameter();
if (isAlwaysUseDefaultTargetUrl()
|| (targetUrlParameter != null && StringUtils.hasText(request.getParameter(targetUrlParameter)))) {
requestCache.removeRequest(request, response);
super.onAuthenticationSuccess(request, response, authentication);
return;
}
clearAuthenticationAttributes(request);
LoginUser userDetails = (LoginUser) authentication.getPrincipal();
String token = tokenService.createToken(userDetails);
//往Cookie中设置token
Cookie casCookie = new Cookie(Constants.WEB_TOKEN_KEY, token);
casCookie.setMaxAge(expireTime * 60);
response.addCookie(casCookie);
//设置后端认证成功标识
HttpSession httpSession = request.getSession();
httpSession.setAttribute(Constants.CAS_TOKEN, token);
//登录成功后跳转到前端登录页面,加了一个jwt参数
getRedirectStrategy().sendRedirect(request, response, casProperties.getWebUrl()+"?jwt="+token);
}
}
Add CasProperties.java class under framework.config.properties
package com.cpmplatform.framework.config.properties;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author daixin
* @version 1.0
* @description: TODO
* @date 2022/12/29 13:22
*/
@Data
@Component
public class CasProperties {
@Value("${cas.server.host.url}")
private String casServerUrl;
@Value("${cas.server.host.login_url}")
private String casServerLoginUrl;
@Value("${cas.server.host.logout_url}")
private String casServerLogoutUrl;
@Value("${app.casEnable}")
private boolean casEnable;
@Value("${app.server.host.url}")
private String appServerUrl;
@Value("${app.login_url}")
private String appLoginUrl;
@Value("${app.logout_url}")
private String appLogoutUrl;
@Value("${app.web_url}")
private String webUrl;
}
LoginUser.java类,修改getAuthorities()方法的返回return new HashSet();
修改Constants.java类,增加代码
/**
* CAS登录成功后的后台标识
*/
public static final String CAS_TOKEN = "cas_token";
/**
* CAS登录成功后的前台Cookie的Key
*/
public static final String WEB_TOKEN_KEY = "Admin-Token";
修改application.yml文件,增加配置
#CAS
cas:
server:
host:
#CAS服务地址
url: http://cpmp.fulongai.cn/cas
#CAS服务登录地址
login_url: ${cas.server.host.url}/login
#CAS服务登出地址
logout_url: ${cas.server.host.url}/logout?service=${app.server.host.url}
# 应用访问地址
app:
#开启cas
casEnable: false
server:
host:
#应用系统后端地址
url: http://cpmp.fulongai.cn/cpmp/prod-api
login_url: / #应用系统登录地址
logout_url: /logout #应用系统登出地址
#应用系统前端首页地址
web_url: http://cpmp.fulongai.cn/cpmp/applicationManagement
修改SecurityConfig.java类
package com.cpmplatform.framework.config;
import com.cpmplatform.framework.config.properties.CasProperties;
import com.cpmplatform.framework.security.filter.JwtAuthenticationTokenFilter;
import com.cpmplatform.framework.security.handle.AuthenticationEntryPointImpl;
import com.cpmplatform.framework.security.handle.CasAuthenticationSuccessHandler;
import com.cpmplatform.framework.security.handle.LogoutSuccessHandlerImpl;
import com.cpmplatform.framework.web.service.CasUserDetailsService;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ServiceTicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.web.filter.CorsFilter;
import com.cpmplatform.framework.config.properties.PermitAllUrlProperties;
/**
* spring security配置
*
* @author cpmplatform
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
{
@Autowired
private CasProperties casProperties;
@Autowired
private CasUserDetailsService customUserDetailsService;
@Autowired
private CasAuthenticationSuccessHandler casAuthenticationSuccessHandler;
/**
* 自定义用户认证逻辑
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 认证失败处理类
*/
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;
/**
* 退出处理类
*/
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
/**
* token认证过滤器
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;
/**
* 允许匿名访问的地址
*/
@Autowired
private PermitAllUrlProperties permitAllUrl;
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
// 注解标记允许匿名访问的url
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
if (!casProperties.isCasEnable()) {
httpSecurity
// CSRF禁用,因为不使用session
.csrf().disable()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/captchaImage").anonymous()
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/profile/**").anonymous()
.antMatchers("/common/download**").anonymous()
.antMatchers("/common/download/resource**").anonymous()
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
}
//开启cas
if (casProperties.isCasEnable()) {
httpSecurity
// CSRF禁用,因为不使用session
.csrf().disable()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 验证码captchaImage 允许匿名访问
.antMatchers("/login","/logout", "/captchaImage").anonymous()
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/profile/**").anonymous()
.antMatchers("/common/download**").anonymous()
.antMatchers("/common/download/resource**").anonymous()
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
//单点登录登出
httpSecurity.logout().permitAll().logoutSuccessHandler(logoutSuccessHandler);
// Custom JWT based security filter
httpSecurity.addFilter(casAuthenticationFilter())
.addFilterBefore(authenticationTokenFilter, CasAuthenticationFilter.class)
//.addFilterBefore(casLogoutFilter(), LogoutFilter.class)
.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class).exceptionHandling()
//认证失败
.authenticationEntryPoint(casAuthenticationEntryPoint());
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
}
/**
* 强散列哈希加密实现
*/
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder()
{
return new BCryptPasswordEncoder();
}
/**
* 解决 无法直接注入 AuthenticationManager
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 定义认证用户信息获取来源,密码校验规则等
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
if (!casProperties.isCasEnable()) {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
}
// cas
if (casProperties.isCasEnable()) {
super.configure(auth);
auth.authenticationProvider(casAuthenticationProvider());
}
}
/**
* 认证的入口
*/
@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLoginUrl());
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
/**
* 指定service相关信息
*/
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(casProperties.getAppServerUrl() + casProperties.getAppLoginUrl());
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}
/**
* CAS认证过滤器
*/
@Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setFilterProcessesUrl(casProperties.getAppLoginUrl());
casAuthenticationFilter.setAuthenticationSuccessHandler(casAuthenticationSuccessHandler);
return casAuthenticationFilter;
}
/**
* cas 认证 Provider
*/
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(customUserDetailsService);
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey("casAuthenticationProviderKey");
return casAuthenticationProvider;
}
@Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(casProperties.getCasServerUrl());
}
/**
* 单点登出过滤器
*/
@Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
//singleSignOutFilter.setCasServerUrlPrefix(casProperties.getCasServerUrl());
singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter;
}
/**
* 请求单点退出过滤器
*/
@Bean
public LogoutFilter casLogoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(casProperties.getCasServerLogoutUrl(),
new SecurityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl(casProperties.getAppLogoutUrl());
return logoutFilter;
}
}
六.自定义数据源
CAS应用到项目时,不可能一直使用默认账号登录,可连接项目数据库进行用户信息验证。
1.修改application.properties文件,添加数据源
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
cas.authn.jdbc.query[0].url=jdbc:mysql://192.168.2.156:3306/cpmplatform?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8
cas.authn.jdbc.query[0].user=cpmp
cas.authn.jdbc.query[0].password=123456
cas.authn.jdbc.query[0].sql=select password from sys_user where user_name=?
cas.authn.jdbc.query[0].fieldPassword=password
cas.authn.jdbc.query[0].passwordEncoder.type=BCRYPT
cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
fieldPassword:数据库中密码字段列名
passwordEncoder.type:密码加密策略,支持NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2等多种配置,需与应用系统的加密策略一致,本项目使用的是强散列哈希加密,也就是BCRYPT。
sql:密码查询语句,项目在实际应用中还可以增加条件,用户删除标志、用户状态等。
2.注释掉application.properties文件最后一行的默认用户名密码
#cas.authn.accept.users=casuser::Mellon
3.重启tomcat,使用系统用户登录,可登陆成功。
七.跨域问题
集成CAS过程中遇到的跨域问题,以及单点登录内嵌页面时的复杂跨域问题,单独整理了一篇,目前遇到的CORS报错都能解决:
https://blog.csdn.net/secretdaixin/article/details/129240863?spm=1001.2014.3001.5501
八.自定义登录页
本例中主题名称为cpmp,以下代码将以cpmp命名进行讲解。
classes/static/themes目录下新建以主题名称命名的文件夹用于存放静态文件,css、js、图片等。
classes目录下创建以主题名称命名的properties文件,本例文件名为cpmp.properties,用于配置主题所引用的资源的路径。前2~4行默认主题样式不能丢,否则页面会丢样式。下面的配置是步骤1创建的目录及文件。等号前的变量名称自定义即可,此变量会在步骤3创建的登录页面中引用。
# 系统默认主题样式
cas.standard.css.file = /css/cas.css
cas.javascript.file = /js/cas.js
cas.admin.css.file = /css/admin.css
# 自定义JS
login.js.element-ui = /themes/cpmp/js/element-ui.js
login.js.jquery = /themes/cpmp/js/jquery.min.js
login.js.cas = /themes/cpmp/js/cas.js
# 自定义CSS
login.css.element-ui = /themes/cpmp/css/element-ui.css
index.css.style = /themes/cpmp/css/cas.css
# 其它
login.img.path = /themes/cpmp/img
cas.page.title = cpmp
classes/templates目录下新建以主题名称命名的文件夹用于存放登录页面,登录页面是一个固定名称的html文件,必须以casLoginView.html命名。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="stylesheet" th:href="@{${#themes.code('index.css.style')}}"/>
<link rel="stylesheet" th:href="@{${#themes.code('login.css.elementui')}}"/>
</head>
<body>
<!-- 第一套, 无th -->
<div class="container">
<div class="login">
<h3>C A S 登录</h3>
<form id="postForm" autocomplete="off" method="post" th:object="${credential}">
<input type="hidden" name="execution" th:value="${flowExecutionKey}"/>
<input type="hidden" name="_eventId" value="submit"/>
<input type="hidden" name="geolocation"/>
<i class="iconfont icon-man"></i>
<input
id="username"
size="25"
v-model.trim="username"
tabindex="1"
placeholder="请输入用户名"
type="text"
th:disabled="${guaEnabled}"
th:field="*{username}"
th:accesskey="#{screen.welcome.label.netid.accesskey}"
autocomplete="off"
/>
<i class="iconfont icon-suo"></i>
<input
type="password"
id="password"
size="25"
tabindex="2"
placeholder="请输入密码"
th:accesskey="#{screen.welcome.label.password.accesskey}"
th:field="*{password}"
autocomplete="off"
/>
<div th:if="${#fields.hasErrors('*')}">
<span th:each="err : ${#fields.errors('*')}" th:utext="${err}" ></span>
</div>
<input
name="submit"
accesskey="l"
th:value="#{screen.welcome.button.login}"
type="submit"
value="登录"
/>
</form>
</div>
</div>
</body>
</html>
在classes/service目录下的服务注册文件中,为每个服务指定主题,以模板中默认的服务注册文件为例,也可以是自建的服务注册文件。
修改classes目录下的application.properties文件,增加代码用于指定默认主题
# 默认主题
cas.theme.defaultThemeName=cpmp
将文件发布到tomcat,重启tomcat,访问登录页面,主题配置成功。