CAS单点登录(六)——自定义登录界面和表单信息

在上一节我们讲述了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文件。

具体路径如下所示:

path

注意:这里的casLoginView.html文件不能乱命名,必须为casLoginView.html。这里是覆盖登录页面所以命名为casLoginView.html,如果要覆盖退出页面则是casLogoutView.html。

关于如何确定页面名称和如何选择,这里其实在CAS单点登录(二)——搭建基础服务这节有提示,因为采用的是覆盖模式,所以我们建立的附件都是将原来有的文件覆盖掉,所以我们可以在打包的cas.war解压包中或IDE中target文件中查看到具体的各种文件。

target

比如,在IDE中的target目录下的cas目录中的WEB-INF的classes里面的templates目录下,我们可以找到各种html,还有static文件夹,在该目录中我们可以寻找到各种需要的文件,编写的html也可以参考这里的源码来写。

在编写html时,from表单的内容需要遵循一定的标准th:object等等。

casLoginView.html内容如下:

<!DOCTYPE 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>单点登录SSO</title>
    <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>SSO</h2>
                        <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">&#xE5C4;</i></a>
                    <form method="post" th:object="${credential}">
                        <h2>SSO</h2>
                        <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,可以发现出现我们自定义的登录界面。

登录1

登录2

但如果我们直接输入https://sso.anumbrella.net:8443/cas/login则直接返回原来的CAS主题了。

登录3

这是因为前面我们是在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.cas</groupId>
     <artifactId>cas-server-core-webflow</artifactId>
     <version>${cas.version}</version>
</dependency>

<dependency>
     <groupId>org.apereo.cas</groupId>
     <artifactId>cas-server-core-webflow-api</artifactId>
     <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<MessageDescriptor> 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中,新增两个字段,如下:

<!DOCTYPE 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>单点登录SSO</title>
    <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>SSO</h2>
                        <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">&#xE5C4;</i></a>
                    <form method="post" th:object="${credential}">
                        <h2>SSO</h2>
                        <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>

主要是新增了两个字段的内容,如下:

<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>

页面绑定参数为新参数th:field=”{email}”、th:field=”{telephone}”这两个字段。保存代码后,重启CAS服务,然后我们可以发现登录界面样式更改如下:
登录4

接着我们输入用户名、密码和邮箱,不输入电话号码,点击登录会失败,同时也带有提示。如下:

登录5

最后我们输入完整的信息,登录成功,然后我们可以发现后台打印的信息。

logs

OK,本节内容就到此结束,后面再介绍其他关于CAS的内容。

代码实例:Chapter5

参考

  • 7
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值