在上一节我们讲述了CAS中的Service配置及管理,对于添加CAS中的服务到注册的表中有了一定的了解,如果不是很熟悉,可以去复习一下CAS单点登录(五)——Service配置及管理。
今天,我们接着前面没有讲解完的文章继续讲解,关于CAS中如何自定义表单信息提交以及如何自定义用户相关页面的知识点。
在上一节中我们讲解了关于Service配置和管理,在Service的配置中,我们可以配置theme参数。比如,我们在使用上一节的代码中使用Json来存储Service配置,在web-10000001.json文件中,我们添加指定主题的参数为anumbrella。配置如下:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|imaps|http)://.*",
"name" : "web",
"id" : 10000001,
"evaluationOrder" : 10,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"enabled" : true,
"ssoEnabled" : true
},
"theme": "anumbrella"
}
接着我们在src/main目录下新建anumbrella.properties文件,文件名与主题参数一致。在官网中推荐我们在配置文件中写法为:
cas.standard.css.file=/themes/[theme_name]/css/cas.css
cas.javascript.file=/themes/[theme_name]/js/cas.js
cas.admin.css.file=/themes/[theme_name]/css/admin.css
这里采用的写法会把CAS系统中自带的页面样式完全覆盖,如果我们只想自定义一部分页面,可以采用自定义部分样式的写法。
anumbrella.javascript.file=/themes/anumbrella/js/cas.js
anumbrella.standard.css.file=/themes/anumbrella/css/cas.css
比如这里我只想自定义登录页面,其他页面不变,可以采用上面的写法。所以anumbrella.properties文件的内容如下:
anumbrella.javascript.file=/themes/anumbrella/js/cas.js
anumbrella.standard.css.file=/themes/anumbrella/css/cas.css
anumbrella.login.images.path=/themes/anumbrella/images
cas.standard.css.file=/css/cas.css
cas.javascript.file=/js/cas.js
cas.admin.css.file=/css/admin.css
anumbrella.login.images.path=/themes/anumbrella/images为要在html页面使用到的图片路径,所以这里自定义图片的地址。
接着我们在src\main\resources文件下新建static和templates文件夹,同时在static文件夹下新建themes/anumbrella文件夹,在templates目录下新建anumbrella文件夹。继续在static/themes/anumbrella下新建css、js、images这三个文件夹,把需要的css、js、图片放入这下面。接着我们在templates/anumbrella目录下新建casLoginView.html文件。
具体路径如下所示:
注意:这里的casLoginView.html文件不能乱命名,必须为casLoginView.html。这里是覆盖登录页面所以命名为casLoginView.html,如果要覆盖退出页面则是casLogoutView.html。
关于如何确定页面名称和如何选择,这里其实在CAS单点登录(二)——搭建基础服务这节有提示,因为采用的是覆盖模式,所以我们建立的附件都是将原来有的文件覆盖掉,所以我们可以在打包的cas.war解压包中或IDE中target文件中查看到具体的各种文件。
比如,在IDE中的target目录下的cas目录中的WEB-INF的classes里面的templates目录下,我们可以找到各种html,还有static文件夹,在该目录中我们可以寻找到各种需要的文件,编写的html也可以参考这里的源码来写。
在编写html时,from表单的内容需要遵循一定的标准th:object等等。
casLoginView.html内容如下:
<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>单点登录SSOtitle>
<link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet">
<link rel='stylesheet prefetch' href='https://fonts.googleapis.com/icon?family=Material+Icons'>
<link rel="stylesheet" th:href="@{${#themes.code('anumbrella.standard.css.file')}}"/>
head>
<body>
<div class="cotn_principal">
<div class="cont_centrar">
<div class="cont_login">
<div class="cont_info_log_sign_up">
<div class="col_md_login">
<div class="cont_ba_opcitiy">
<h2>SSOh2>
<p>点击登录输入信息p>
<button class="btn_login" onClick="cambiar_login()">登录button>
div>
div>
div>
<div class="cont_back_info">
<div class="cont_img_back_grey"> <img th:src="@{${#themes.code('anumbrella.login.images.path')}+'/po.jpeg'}" alt="" /> div>
div>
<div class="cont_forms" >
<div class="cont_img_back_"> <img th:src="@{${#themes.code('anumbrella.login.images.path')}+'/po.jpeg'}" alt="" /> div>
<div class="cont_form_login"> <a href="#" onClick="ocultar_login_sign_up()" ><i class="material-icons">i>a>
<form method="post" th:object="${credential}">
<h2>SSOh2>
<section class="row">
<div th:unless="${openIdLocalId}">
<input class="required"
id="username"
size="25"
tabindex="1"
placeholder="用户名"
type="text"
th:disabled="${guaEnabled}"
th:field="*{username}"
th:accesskey="#{screen.welcome.label.netid.accesskey}"
autocomplete="off"/>
div>
section>
<section class="row">
<div>
<input class="required"
type="password"
id="password"
size="25"
tabindex="2"
placeholder="密码"
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 btn_login"
style="text-align: center"
name="submit"
accesskey="l"
th:value="#{screen.welcome.button.login}"
tabindex="6"
type="submit"/>
section>
<div th:if="${#fields.hasErrors('*')}">
<span th:each="err : ${#fields.errors('*')}" th:utext="${err}"/>
div>
form>
div>
<div class="cont_form_sign_up"/>
div>
div>
div>
div>
<script th:src="@{${#themes.code('anumbrella.javascript.file')}}">script>
body>
html>
这里提一下,关于css、js、图片引用,如果图片引用在css中使用,直接采用相对路径;如果要在前台(html中)写图片地址,可以像这里我使用这样的写法。然后通过th:object来应用在anumbrella.properties配置的路径地址。
由于这里我们采用的Json服务配置,所以在application.properties中开启Json服务注册配置。
##
# Service Registry(服务注册)
#
# 开启识别Json文件,默认false
cas.serviceRegistry.initFromJson=true
#自动扫描服务配置,默认开启
cas.serviceRegistry.watcherEnabled=true
#120秒扫描一遍
cas.serviceRegistry.schedule.repeatInterval=120000
#延迟15秒开启
# cas.serviceRegistry.schedule.startDelay=15000
##
# Json配置
#
cas.serviceRegistry.json.location=classpath:/services
接着我们启动CAS服务,输入路由https://sso.anumbrella.net:8443/cas/login?service=http://localhost:9080/sample,可以发现出现我们自定义的登录界面。
但如果我们直接输入https://sso.anumbrella.net:8443/cas/login则直接返回原来的CAS主题了。
这是因为前面我们是在Service中配置了主题的,只有符合的Servivce才采用配置了的主题。那么如何更改所有的默认主题呢?
这里有两种方法,一种是直接更改默认主题,在application.properties文件中,添加我们需要自定义的文件。
cas.standard.css.file=/css/cas.css
cas.javascript.file=/js/cas.js
cas.admin.css.file=/css/admin.css
然后在static文件下新建css、js、images这三个文件下,把需要的css、js、图片放入这下面。接着我们在templates目录下新建casLoginView.html文件。这里的配置覆盖就是直接覆盖原来默认主题的文件,具体同前面的一致,只是路径不同。
另一种方法是使用默认主题配置,将新建的主题设置为默认主题,建议采用这种方案,更容易控制。在application.properties文件中添加如下配置:
# 默认主题配置
cas.theme.defaultThemeName=anumbrella
重启CAS服务,输入https://sso.anumbrella.net:8443/cas/login,发现登录页面变了。接着我们登录,发现跳转到原来默认的主题了,这是因为我们的主题默认是只覆盖了登录页面,其他继续采用默认主题,这个可以根据需求自定义更改。
先添加依赖类,这里会使用到两个依赖类:
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-core-webflowartifactId>
<version>${cas.version}version>
dependency>
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-core-webflow-apiartifactId>
<version>${cas.version}version>
dependency>
在前面CAS单点登录(四)——自定义认证登录策略中,我们提到过如果要验证其他信息,比如邮箱,手机号,但是邮箱,手机信息在另一个数据库,还有在一段时间内同一IP输入错误次数限制等。这里就需要我们自定义认证策略,自定义CAS的web认证流程。
首先我们创建需要的表单信息,即这里除了要使用用户名和密码外,还要用户输入邮箱,手机号,所以需要自定义Credential,由于有用户名和密码,所以可以直接继承UsernamePasswordCredential,新建类CustomCredential,具体如下:
package net.anumbrella.sso.entity;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.validation.constraints.Size;
/**
* @author Anumbrella
*/
public class CustomCredential extends UsernamePasswordCredential {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomCredential.class);
private static final long serialVersionUID = -4166149641561667276L;
@Size(min = 1, message = "require email")
private String email;
@Size(min = 1, message = "require telephone")
private String telephone;
public String getEmail() {
return email;
}
public void setEmail(final String email) {
this.email = email;
}
public String getTelephone() {
return telephone;
}
public void setTelephone(final String telephone) {
this.telephone = telephone;
}
public CustomCredential() {
}
public CustomCredential(final String email, final String telephone) {
this.email = email;
this.telephone = telephone;
}
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof CustomCredential)) {
return false;
} else {
CustomCredential other = (CustomCredential) o;
if (!other.canEqual(this)) {
return false;
} else {
Object this$email = this.email;
Object other$email = other.email;
if (this$email == null) {
if (other$email != null) {
return false;
}
} else if (!this$email.equals(other$email)) {
return false;
}
Object this$telephone = this.telephone;
Object other$telephone = other.telephone;
if (this$telephone == null) {
if (other$telephone != null) {
return false;
}
} else if (!this$telephone.equals(other$telephone)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof CustomCredential;
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.appendSuper(super.hashCode())
.append(this.email)
.append(this.telephone)
.toHashCode();
}
}
这里新增定义了邮箱和手机号,并且是必须输入项。然后我们重新定义CAS的Web流程,新建类CustomWebflowConfigurer,定义如下:
package net.anumbrella.sso.config;
import net.anumbrella.sso.entity.CustomCredential;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.CasWebflowConstants;
import org.apereo.cas.web.flow.configurer.AbstractCasWebflowConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
/**
* @author anumbrella
*/
public class CustomWebflowConfigurer extends AbstractCasWebflowConfigurer {
public CustomWebflowConfigurer(FlowBuilderServices flowBuilderServices,
FlowDefinitionRegistry flowDefinitionRegistry,
ApplicationContext applicationContext,
CasConfigurationProperties casProperties) {
super(flowBuilderServices, flowDefinitionRegistry, applicationContext, casProperties);
}
@Override
protected void doInitialize() {
final Flow flow = super.getLoginFlow();
bindCredential(flow);
}
/**
* 绑定自定义的Credential信息
*
* @param flow
*/
protected void bindCredential(Flow flow) {
// 重写绑定自定义credential
// 重写绑定自定义credential
createFlowVariable(flow, CasWebflowConstants.VAR_ID_CREDENTIAL, CustomCredential.class);
// 登录页绑定新参数
final ViewState state = (ViewState) flow.getState(CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM);
final BinderConfiguration cfg = getViewStateBinderConfiguration(state);
// 由于用户名以及密码已经绑定,所以只需对新加系统参数绑定即可
// 字段名,转换器,是否必须字段
cfg.addBinding(new BinderConfiguration.Binding("email", null, true));
cfg.addBinding(new BinderConfiguration.Binding("telephone", null, true));
}
}
在初始化doInitialize中,绑定我们需要的邮箱和电话信息,用户名以及密码是早已经绑定了,所以不用添加。
然后再新增类CustomerAuthWebflowConfiguration,更改CAS的Web流程的配置代理,如下所示:
package net.anumbrella.sso.config;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.CasWebflowConfigurer;
import org.apereo.cas.web.flow.CasWebflowExecutionPlan;
import org.apereo.cas.web.flow.CasWebflowExecutionPlanConfigurer;
import org.apereo.cas.web.flow.config.CasWebflowContextConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
/**
* @author anumbrella
*/
@Configuration("customerAuthWebflowConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomerAuthWebflowConfiguration implements CasWebflowExecutionPlanConfigurer {
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("loginFlowRegistry")
private FlowDefinitionRegistry loginFlowDefinitionRegistry;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private FlowBuilderServices flowBuilderServices;
@Bean
public CasWebflowConfigurer customWebflowConfigurer() {
// 实例化自定义的表单配置类
final CustomWebflowConfigurer c = new CustomWebflowConfigurer(flowBuilderServices, loginFlowDefinitionRegistry,
applicationContext, casProperties);
// 初始化
c.initialize();
// 返回对象
return c;
}
@Override
public void configureWebflowExecutionPlan(final CasWebflowExecutionPlan plan) {
plan.registerWebflowConfigurer(customWebflowConfigurer());
}
}
主要是通过继承CasWebflowExecutionPlanConfigurer,实现配置的初始化和注册。最后在resources下的META-INF文件下的spring.factories注入spring boot的配置,就是我们上面的类,如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
net.anumbrella.sso.config.CustomAuthenticationConfiguration,\
net.anumbrella.sso.controller.ServicesManagerController,\
net.anumbrella.sso.config.CustomerAuthWebflowConfiguration
接下来的流程就是处理自定义提交信息的逻辑,与我们在CAS单点登录(四)——自定义认证登录策略中讲解的一致,通过拦截请求获取到Handler,来实现自定义认证策略,主要是继承AbstractPreAndPostProcessingAuthenticationHandler类来实现的,基本思路是一样的,这里需要大致更改一下,如下:
package net.anumbrella.sso.authentication;
import net.anumbrella.sso.entity.CustomCredential;
import net.anumbrella.sso.entity.User;
import org.apereo.cas.authentication.*;
import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.security.auth.login.AccountException;
import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* @author anumbrella
*/
public class CustomerHandlerAuthentication extends AbstractPreAndPostProcessingAuthenticationHandler {
public CustomerHandlerAuthentication(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
super(name, servicesManager, principalFactory, order);
}
@Override
public boolean supports(Credential credential) {
//判断传递过来的Credential 是否是自己能处理的类型
return credential instanceof CustomCredential;
}
@Override
protected AuthenticationHandlerExecutionResult doAuthentication(Credential credential) throws GeneralSecurityException, PreventedException {
CustomCredential customCredential = (CustomCredential) credential;
String username = customCredential.getUsername();
String password = customCredential.getPassword();
String email = customCredential.getEmail();
String telephone = customCredential.getTelephone();
System.out.println("username : " + username);
System.out.println("password : " + password);
System.out.println("email : " + email);
System.out.println("telephone : " + telephone);
// JDBC模板依赖于连接池来获得数据的连接,所以必须先要构造连接池
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/cas");
dataSource.setUsername("root");
dataSource.setPassword("123");
// 创建JDBC模板
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
String sql = "SELECT * FROM user WHERE username = ?";
User info = (User) jdbcTemplate.queryForObject(sql, new Object[]{username}, new BeanPropertyRowMapper(User.class));
System.out.println("database username : "+ info.getUsername());
System.out.println("database password : "+ info.getPassword());
if (info == null) {
throw new AccountException("Sorry, username not found!");
}
if (!info.getPassword().equals(password)) {
throw new FailedLoginException("Sorry, password not correct!");
} else {
final List list = new ArrayList<>();
return createHandlerResult(customCredential,
this.principalFactory.createPrincipal(username, Collections.emptyMap()), list);
}
}
}
在supports方法中,将实例类判断更改为我们自定义的Credential,同时在doAuthentication中将Credential转换为我们自定义的Credential,这里我将邮箱和手机号获取出来,并打印到控制台。实际开发中可以具体处理逻辑。
接着我们在CustomAuthenticationConfiguration类中,将要处理的自定义逻辑更改为上面我们自定义的逻辑CustomerHandlerAuthentication类,如下:
package net.anumbrella.sso.config;
import net.anumbrella.sso.authentication.CustomerHandlerAuthentication;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.ServicesManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author anumbrella
*/
@Configuration("customAuthenticationConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomAuthenticationConfiguration implements AuthenticationEventExecutionPlanConfigurer {
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@Bean
public AuthenticationHandler myAuthenticationHandler() {
// 参数: name, servicesManager, principalFactory, order
// 定义为优先使用它进行认证
// return new CustomUsernamePasswordAuthentication(CustomUsernamePasswordAuthentication.class.getName(),
// servicesManager, new DefaultPrincipalFactory(), 1);
return new CustomerHandlerAuthentication(CustomerHandlerAuthentication.class.getName(),
servicesManager, new DefaultPrincipalFactory(), 1);
}
@Override
public void configureAuthenticationExecutionPlan(final AuthenticationEventExecutionPlan plan) {
plan.registerAuthenticationHandler(myAuthenticationHandler());
}
}
最后在还是在resources下的META-INF文件下的spring.factories注入spring boot的配置。这里与第四节的内容基本相同,不熟悉的可以先去看看第四节的内容————CAS单点登录(四)——自定义认证登录策略。
后台的处理完成了,我们还要更改前台页面casLoginView.html的内容,因为我们除了用户名和密码外,还要邮箱和手机号,所以需要更改界面。
在前面我们的自定义界面的casLoginView.html中,新增两个字段,如下:
<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>单点登录SSOtitle>
<link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet">
<link rel='stylesheet prefetch' href='https://fonts.googleapis.com/icon?family=Material+Icons'>
<link rel="stylesheet" th:href="@{${#themes.code('anumbrella.standard.css.file')}}"/>
head>
<body>
<div class="cotn_principal">
<div class="cont_centrar">
<div class="cont_login">
<div class="cont_info_log_sign_up">
<div class="col_md_login">
<div class="cont_ba_opcitiy">
<h2>SSOh2>
<p>点击登录输入信息p>
<button class="btn_login" onClick="cambiar_login()">登录button>
div>
div>
div>
<div class="cont_back_info">
<div class="cont_img_back_grey"> <img th:src="@{${#themes.code('anumbrella.login.images.path')}+'/po.jpeg'}" alt="" /> div>
div>
<div class="cont_forms" >
<div class="cont_img_back_"> <img th:src="@{${#themes.code('anumbrella.login.images.path')}+'/po.jpeg'}" alt="" /> div>
<div class="cont_form_login"> <a href="#" onClick="ocultar_login_sign_up()" ><i class="material-icons">i>a>
<form method="post" th:object="${credential}">
<h2>SSOh2>
<section class="row">
<div th:unless="${openIdLocalId}">
<input class="required"
id="username"
size="25"
tabindex="1"
placeholder="用户名"
type="text"
th:disabled="${guaEnabled}"
th:field="*{username}"
th:accesskey="#{screen.welcome.label.netid.accesskey}"
autocomplete="off"/>
div>
section>
<section class="row">
<div>
<input class="required"
type="password"
id="password"
size="25"
tabindex="2"
placeholder="密码"
th:accesskey="#{screen.welcome.label.password.accesskey}"
th:field="*{password}"
autocomplete="off"/>
div>
section>
<section class="row">
<div>
<input class="required"
id="email"
size="25"
tabindex="1"
placeholder="邮箱"
type="text"
th:field="*{email}"
autocomplete="off"/>
div>
section>
<section class="row">
<div>
<input class="required"
id="telephone"
size="25"
tabindex="1"
placeholder="手机"
type="text"
th:field="*{telephone}"
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 btn_login"
style="text-align: center"
name="submit"
accesskey="l"
th:value="#{screen.welcome.button.login}"
tabindex="6"
type="submit"/>
section>
<div th:if="${#fields.hasErrors('*')}">
<span th:each="err : ${#fields.errors('*')}" th:utext="${err}"/>
div>
form>
div>
<div class="cont_form_sign_up"/>
div>
div>
div>
div>
<script th:src="@{${#themes.code('anumbrella.javascript.file')}}">script>
body>
html>
主要是新增了两个字段的内容,如下:
row" >
<div>
<input class="required"
id="email"
size="25"
tabindex="1"
placeholder="邮箱"
type="text"
th:field="*{email}"
autocomplete="off"/>
div>
section>
<section class="row">
<div>
<input class="required"
id="telephone"
size="25"
tabindex="1"
placeholder="手机"
type="text"
th:field="*{telephone}"
autocomplete="off"/>
div>
section>
页面绑定参数为新参数th:field=”{email}”、th:field=”{telephone}”这两个字段。保存代码后,重启CAS服务,然后我们可以发现登录界面样式更改如下:
接着我们输入用户名、密码和邮箱,不输入电话号码,点击登录会失败,同时也带有提示。如下:
最后我们输入完整的信息,登录成功,然后我们可以发现后台打印的信息。
OK,本节内容就到此结束,后面再介绍其他关于CAS的内容。
代码实例:Chapter5