目录
1、cas服务端配置mysql
2、cas服务端自定义密码加密方式
3、cas服务端自定义主题,也就是登录页面,也可以其他的页面
4、cas服务端在登录时提交的表单中添加字段
5、cas服务端自定义登录校验
6、cas服务端自定义返回异常
7、cas服务端使用ajax进行登录
环境:
CAS版本:5.3
下载链接:git地址是:https://github.com/apereo/cas-overlay-template
tomcat:8
jdk:1.8
1、先把cas服务端的源代码拷贝出来,方法如下:
1)使用idea打开
红框中是通过idea配置tomcat后,编译出来的代码。
2)创建src目录,如下,并且把target/cas/META-INF文件、target/cas/services、target/cas/application.properties,这三个文件拷贝到resources中。
由于CAS默认支持https,如果想要改成支持http,获取一些其他CAS服务端的基本配置,那么可以看这篇文章:SSO单点登录(一)创建CAS服务端_我是混IT圈的-CSDN博客_cas服务器搭建
1、cas服务端配置mysql
1、pom.xml
<!--数据库认证相关 start-->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc-drivers</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<!--数据库认证相关 end-->
2、application.properties 配置文件
1)添加与mysql的链接
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
cas.authn.jdbc.query[0].url=jdbc:mysql://127.0.0.1:3306/shiro?useUnicode=true&characterEncoding=utf-8
cas.authn.jdbc.query[0].user=root #数据库账号
cas.authn.jdbc.query[0].password=123456 #数据库密码
cas.authn.jdbc.query[0].sql=select password from system_admin_user where account = ? #表的查询sql
cas.authn.jdbc.query[0].fieldPassword=password #表中属于密码的字段
2)注释掉cas默认的帐号密码,不然这个账号密码也是可以登录的
#cas.authn.accept.users=casuser::Mellon
注意:这里使用mysql进行登录,密码是没有经过加密的,比如,登录密码是123456,那么数据库的密码就是123456。
2、cas服务端自定义密码加密方式
1、创建类:MyPasswordEncoder
package com.example.cas.password;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* 密码加密
*/
public class MyPasswordEncoder implements PasswordEncoder {
@Override
public String encode(CharSequence charSequence) {
System.out.println("encode===========用户输入的密码:" + charSequence);
return charSequence.toString();
}
@Override
public boolean matches(CharSequence charSequence, String str) {
//charSequence 为用户输入的密码
System.out.println("matches---------------用户输入的密码:"+charSequence);
//str 为数据库密码
System.out.println("matches---------------数据库密码:"+str);
if (charSequence.equals(str)){
return true;
}
return false;
}
}
2、application.properties 配置文件
#开启自定义密码验证
cas.authn.jdbc.query[0].passwordEncoder.type=com.example.cas.MyPasswordEncoder
3、cas服务端自定义主题,也就是登录页面,也可以其他的页面
1、pom.xml
<!--自定义登录页面 start-->
<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>
<!--自定义登录页面 end-->
2、注册 json,resources 下创建 services/login-10000001.json,主题名称为 loginTheme
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|http|imaps)://.*",
"name" : "web",
"id" : 10000001,
"evaluationOrder" : 10,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"enabled" : true,
"ssoEnabled" : true
},
"theme": "loginTheme"
}
3、配置文件:application.properties 开启json扫码
# Service Registry(服务注册)
# 开启识别Json文件,默认false,这里就是开启识别 services 中的json文件的
cas.serviceRegistry.initFromJson=true
#自动扫描服务配置,默认开启
cas.serviceRegistry.watcherEnabled=true
#120秒扫描一遍
cas.serviceRegistry.schedule.repeatInterval=120000
#延迟15秒开启
# cas.serviceRegistry.schedule.startDelay=15000
cas.serviceRegistry.json.location=classpath:/services
#JSON文件主题名称 loginTheme
cas.theme.defaultThemeName=loginTheme
4、在resources下创建 loginTheme.properties
loginTheme.javascript.file=/themes/loginTheme/js/cas.js
loginTheme.standard.css.file=/themes/loginTheme/css/cas.css
loginTheme.login.images.path=/themes/loginTheme/images
cas.standard.css.file=/css/cas.css
cas.javascript.file=/js/cas.js
cas.admin.css.file=/css/admin.css
创建静态资源和登录文件,路径如下:
# 静态资源文件
resources/static/themes/loginTheme/css/cas.css
resources/static/themes/loginTheme/js/cas.js
resources/static/themes/loginTheme/images/
# 登录页面
resources/templates/loginTheme/casLoginView.html
5、casLoginView.html:名称是固定的,不能瞎干,页面内容可以自己些,下面这个页面是拷贝的cas登录页面。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript" th:src="@{/themes/customTheme/js/cas.js}"></script>
<script type="text/javascript" th:src="@{/themes/customTheme/js/jquery.min.js}"></script>
</head>
<body>
<span>登录页面</span><br>
<form method="post" id="fm1" th:object="${credential}" action="login">
<div class="alert alert-danger" th:if="${#fields.hasErrors('*')}">
<span th:each="err : ${#fields.errors('*')}" th:utext="${err}">Example error</span>
</div>
<h3 th:utext="#{screen.welcome.instructions}">Enter your Username and Password</h3>
<section class="form-group">
<label for="username" th:utext="#{screen.welcome.label.netid}">Username</label>
<div th:if="${openIdLocalId}">
<strong>
<span th:utext="${openIdLocalId}"/>
</strong>
<input type="hidden"
id="username"
name="username"
th:value="${openIdLocalId}"/>
</div>
<div th:unless="${openIdLocalId}">
<input class="form-control 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="form-group">
<label for="password" th:utext="#{screen.welcome.label.password}">Password</label>
<div>
<input class="form-control required"
type="password"
id="password"
size="25"
tabindex="2"
th:accesskey="#{screen.welcome.label.password.accesskey}"
th:field="*{password}"
autocomplete="off"/>
<span id="capslock-on" style="display:none;">
<p>
<i class="fa fa-exclamation-circle"></i>
<span th:utext="#{screen.capslock.on}"/>
</p>
</span>
</div>
</section>
<section class="form-group">
<label for="telephone">电话</label>
<div th:if="${openIdLocalId}">
<strong>
<span th:utext="${openIdLocalId}"/>
</strong>
<input type="hidden"
id="telephone"
name="username"
th:value="${openIdLocalId}"/>
</div>
<div th:unless="${openIdLocalId}">
<input class="form-control required"
id="telephone"
size="25"
tabindex="1"
type="text"
th:disabled="${guaEnabled}"
th:field="*{telephone}"
th:accesskey="#{screen.welcome.label.netid.accesskey}"
autocomplete="off"/>
</div>
</section>
<section class="form-check" th:if="${passwordManagementEnabled && param.doChangePassword != null}">
<p>
<input type="checkbox" name="doChangePassword" id="doChangePassword"
value="true" th:checked="${param.doChangePassword != null}" tabindex="4"/>
<label for="doChangePassword" th:text="#{screen.button.changePassword}">Change Password</label>
</p>
</section>
<section class="form-check" th:if="${rememberMeAuthenticationEnabled}">
<p>
<input type="checkbox" name="rememberMe" id="rememberMe" value="true" tabindex="5"/>
<label for="rememberMe" th:text="#{screen.rememberme.checkbox.title}">Remember Me</label>
</p>
</section>
<section class="row"
th:if="${recaptchaSiteKey != null AND recaptchaInvisible != null AND recaptchaSiteKey != null AND !recaptchaInvisible}">
<div class="g-recaptcha" th:attr="data-sitekey=${recaptchaSiteKey}"/>
</section>
<input type="hidden" name="execution" th:value="${flowExecutionKey}"/>
<input type="hidden" name="_eventId" value="submit"/>
<input type="hidden" name="geolocation"/>
<p th:if="${#request.getMethod().equalsIgnoreCase('POST')}">
<span th:each="entry : ${httpRequestInitialPostParameters}" th:remove="tag">
<span th:each="entryValue : ${entry.value}" th:remove="tag">
<input type="hidden" th:name="${entry.key}" th:value="${entryValue}"/>
</span>
</span>
</p>
<input class="btn btn-block btn-submit"
th:unless="${recaptchaSiteKey != null AND recaptchaInvisible != null AND recaptchaSiteKey != null AND recaptchaInvisible}"
name="submit"
accesskey="l"
th:value="#{screen.welcome.button.login}"
tabindex="6"
type="submit"
value="Login3"/>
<button class="btn btn-block btn-submit g-recaptcha"
th:if="${recaptchaSiteKey != null AND recaptchaInvisible != null AND recaptchaSiteKey != null AND recaptchaInvisible}"
th:attr="data-sitekey=${recaptchaSiteKey}, data-badge=${recaptchaPosition}"
data-callback="onSubmit"
name="submitBtn"
accesskey="l"
th:text="#{screen.welcome.button.login}"
tabindex="6"/>
</form>
</body>
</html>
4、cas服务端在登录时提交的表单中添加字段
在登录表单中添加字段
1、创建接受表单中新增的字段的类:CustomCredential
package com.example.cas.login;
import org.apereo.cas.authentication.UsernamePasswordCredential;
public class CustomCredential extends UsernamePasswordCredential {
private String telephone;
public String getTelephone() {
return telephone;
}
public void setTelephone(String telephone) {
this.telephone = telephone;
}
}
2、添加自定义字段类:CustomWebflowConfigurer
package com.example.cas.config;
import com.example.cas.login.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
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);
// 由于用户名以及密码已经绑定,所以只需对新加系统参数绑定即可
// 字段名,转换器,是否必须字段 telephone:它是新添加的字段,可以添加多个字段
cfg.addBinding(new BinderConfiguration.Binding("telephone", null, true));
cfg.addBinding(new BinderConfiguration.Binding("phone", null, true));
}
}
3、配置 添加自定义字段 类:CustomerAuthWebflowConfiguration
package com.example.cas.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.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;
/**
* 配置 添加自定义字段 类
*/
@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());
}
}
4、在文件 resources/META-INF/spring.factories 中,添加配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.cas.config.CustomerAuthWebflowConfiguration
5、cas服务端自定义登录校验
1、pom.xml
<!--自定义登录校验 start-->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-json-service-registry</artifactId>
<version>${cas.version}</version>
</dependency>
<!-- Custom Authentication -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-authentication-api</artifactId>
<version>${cas.version}</version>
</dependency>
<!-- Custom Configuration -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-configuration-api</artifactId>
<version>${cas.version}</version>
</dependency>
<!--自定义登录校验 end-->
2、自定义校验类:CustomUsernamePasswordAuthentication
package com.example.cas.login;
import com.example.cas.Model.UserInfo;
import com.example.cas.Model.CustomCredential;
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.AccountLockedException;
import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* 自定义登录校验
*/
public class CustomUsernamePasswordAuthentication extends AbstractPreAndPostProcessingAuthenticationHandler {
public CustomUsernamePasswordAuthentication(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
super(name, servicesManager, principalFactory, order);
}
@Override
public boolean supports(Credential credential) {
//判断传递过来的Credential 是否是自己能处理的类型
return credential instanceof CustomCredential;
}
/**
* 身份验证,也就是登录
* @param credential
* @return
* @throws GeneralSecurityException
* @throws PreventedException
*/
@Override
protected AuthenticationHandlerExecutionResult doAuthentication(Credential credential) throws GeneralSecurityException, PreventedException {
System.out.println("++========================="+4);
UsernamePasswordCredential usernamePasswordCredential = (UsernamePasswordCredential) credential;
String username = usernamePasswordCredential.getUsername();
String password = usernamePasswordCredential.getPassword();
System.out.println("******CustomUsernamePasswordAuthentication******:" + username);
System.out.println("******CustomUsernamePasswordAuthentication******:" + password);
/*通过jdbc查询数据库的数据*/
// JDBC模板依赖于连接池来获得数据的连接,所以必须先要构造连接池
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/shiro?useUnicode=true&characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("123456");
// 创建JDBC模板
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
String sql = "SELECT * FROM system_admin_user WHERE account = ?";
UserInfo info = (UserInfo) jdbcTemplate.queryForObject(sql, new Object[]{username}, new BeanPropertyRowMapper(UserInfo.class));
System.out.println("database username : "+ info.getAccount());
System.out.println("database password : "+ info.getPassword());
//这里的异常还不行,用不了的
if (info != null) {
throw new AccountLockedException();
}
if (!info.getPassword().equals(password)) {
throw new FailedLoginException("Sorry, 密码不正确!");
} else {
//可自定义返回给客户端的多个属性信息
HashMap<String, Object> returnInfo = new HashMap<>();
returnInfo.put("id", info.getId());
returnInfo.put("account", info.getAccount());
returnInfo.put("disableStatus", info.getDisableStatus());
final List<MessageDescriptor> list = new ArrayList<>();
return createHandlerResult(usernamePasswordCredential, this.principalFactory.createPrincipal(username, returnInfo), list);
}
}
}
3、注入配置 CustomAuthenticationConfiguration
package com.example.cas.login;
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.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 注入配置信息
*/
@Configuration("CustomAuthenticationConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomAuthenticationConfiguration implements AuthenticationEventExecutionPlanConfigurer {
@Autowired
private ServicesManager servicesManager;
@Bean
public AuthenticationHandler myAuthenticationHandler() {
// 参数: name, servicesManager, principalFactory, order
// 定义为优先使用它进行认证
return new CustomUsernamePasswordAuthentication(CustomUsernamePasswordAuthentication.class.getName(),
servicesManager, new DefaultPrincipalFactory(), 1);
}
@Override
public void configureAuthenticationExecutionPlan(final AuthenticationEventExecutionPlan plan) {
plan.registerAuthenticationHandler(myAuthenticationHandler());
}
}
4、在文件 resources/META-INF/spring.factories 中,添加配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.cas.login.CustomAuthenticationConfiguration
注意:上面自定义登录中的异常返回,是不行的。
6、cas服务端自定义返回异常
7、cas服务端使用ajax进行登录
这两个目录没有时间搞了。