原文地址,转载请注明出处: https://blog.csdn.net/qq_34021712/article/details/82259101 ©王赛超
本篇主要的知识点是为了讲解重写Credential来实现自定义表单信息,我们使用sso的时候往往登录不只是需要用户名密码,有时候可能还需要验证码,下面就要讲解如何重写Credential添加验证码。添加别的值也是一样的操作。
验证码是有效防止暴力破解的一种手段,常用做法是在服务端产生一串随机字符串与当前用户会话关联(我们通常说的放入 Session),然后向终端用户展现一张经过“扰乱”的图片,只有当用户输入的内容与服务端产生的内容相同时才允许进行下一步操作.
https://blog.csdn.net/u010588262/article/details/80014083
在cas-server-webapp-tomcat-5.3.2的login-webflow.xml中,可以看到登陆的用户名和密码信息绑定到了credential这个对象上。
那credential这个model到底是哪个类呢,我在cas-server-core-api-webflow-5.3.2包里找了与cas webflow相关的类,找到了一个关键的配置接口CasWeflowConfigurer,它的继承类如下:
在该类中我们找到了createRememberMeAuthnWebflowConfig这个方法,具体代码如下:
从代码上可以看到,如果开启了RememberMe的功能就使用
RememberMeUsernamePasswordCredential
如果没有就使用
UsernamePasswordCredential
了,我们上一篇已经开启了RememberMe的功能。
package com.wangsaichao.cas.adaptors.generic;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apereo.cas.authentication.RememberMeUsernamePasswordCredential;
import javax.validation.constraints.Size;
/**
* @author: wangsaichao
* @date: 2018/8/31
* @description: 验证码 Credential
*/
public class RememberMeUsernamePasswordCaptchaCredential extends RememberMeUsernamePasswordCredential {
@Size(min = 5,max = 5, message = "require captcha")
private String captcha;
public String getCaptcha() {
return captcha;
}
public void setCaptcha(String captcha) {
this.captcha = captcha;
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.appendSuper(super.hashCode())
.append(this.captcha)
.toHashCode();
}
}
package com.wangsaichao.cas.adaptors.generic;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.CasWebflowConstants;
import org.apereo.cas.web.flow.configurer.DefaultLoginWebflowConfigurer;
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: wangsaichao
* @date: 2018/8/31
* @description: 重新定义 Credential model
*/
public class DefaultCaptchaWebflowConfigurer extends DefaultLoginWebflowConfigurer {
/**
* Instantiates a new Default webflow configurer.
*
* @param flowBuilderServices the flow builder services
* @param flowDefinitionRegistry the flow definition registry
* @param applicationContext the application context
* @param casProperties the cas properties
*/
public DefaultCaptchaWebflowConfigurer(FlowBuilderServices flowBuilderServices, FlowDefinitionRegistry flowDefinitionRegistry, ApplicationContext applicationContext, CasConfigurationProperties casProperties) {
super(flowBuilderServices, flowDefinitionRegistry, applicationContext, casProperties);
}
/**
* Create remember me authn webflow config.
*
* @param flow the flow
*/
@Override
protected void createRememberMeAuthnWebflowConfig(Flow flow) {
if (casProperties.getTicket().getTgt().getRememberMe().isEnabled()) {
createFlowVariable(flow, CasWebflowConstants.VAR_ID_CREDENTIAL, RememberMeUsernamePasswordCaptchaCredential.class);
final ViewState state = getState(flow, CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM, ViewState.class);
final BinderConfiguration cfg = getViewStateBinderConfiguration(state);
cfg.addBinding(new BinderConfiguration.Binding("rememberMe", null, false));
cfg.addBinding(new BinderConfiguration.Binding("captcha", null, true));
} else {
createFlowVariable(flow, CasWebflowConstants.VAR_ID_CREDENTIAL, UsernamePasswordCredential.class);
}
}
}
主要修改了这里,把原来的RememberMeUsernamePasswordCredential换成了我们自己的
RememberMeUsernamePasswordCaptchaCredential,并且加上cpacha的bind。
这块用到的就是我们之前用过的自定义验证。参考之前的博客。
package com.wangsaichao.cas.adaptors.generic;
import com.wangsaichao.cas.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;
import java.util.Map;
/**
* @author: wangsaichao
* @date: 2018/8/31
* @description:
*/
public class RememberMeUsernamePasswordCaptchaAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler {
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
public RememberMeUsernamePasswordCaptchaAuthenticationHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
super(name, servicesManager, principalFactory, order);
}
@Override
protected AuthenticationHandlerExecutionResult doAuthentication(Credential credential) throws GeneralSecurityException, PreventedException {
RememberMeUsernamePasswordCaptchaCredential myCredential = (RememberMeUsernamePasswordCaptchaCredential) credential;
String requestCaptcha = myCredential.getCaptcha();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
Object attribute = attributes.getRequest().getSession().getAttribute("captcha");
String realCaptcha = attribute == null ? null : attribute.toString();
if(StringUtils.isBlank(requestCaptcha) || !requestCaptcha.toUpperCase().equals(realCaptcha)){
throw new FailedLoginException("验证码错误");
}
String username = myCredential.getUsername();
Map user = userService.findByUserName(username);
//可以在这里直接对用户名校验,或者调用 CredentialsMatcher 校验
if (user == null || !user.get("password").equals(myCredential.getPassword())) {
throw new UnknownAccountException("用户名或密码错误!");
}
//这里将 密码对比 注销掉,否则 无法锁定 要将密码对比 交给 密码比较器 在这里可以添加自己的密码比较器等
//if (!password.equals(user.getPassword())) {
// throw new IncorrectCredentialsException("用户名或密码错误!");
//}
if ("1".equals(user.get("state"))) {
throw new LockedAccountException("账号已被锁定,请联系管理员!");
}
return createHandlerResult(credential, this.principalFactory.createPrincipal(username));
}
@Override
public boolean supports(Credential credential) {
return credential instanceof RememberMeUsernamePasswordCaptchaCredential;
}
}
配置DefaultCaptchaWebflowConfigurer
package com.wangsaichao.cas.config;
import com.wangsaichao.cas.adaptors.generic.DefaultCaptchaWebflowConfigurer;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.CasWebflowConfigurer;
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.autoconfigure.AutoConfigureBefore;
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: wangsaichao
* @date: 2018/8/31
* @description:
*/
@Configuration("captchaWebflowConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
@AutoConfigureBefore(value = CasWebflowContextConfiguration.class)
public class CaptchaWebflowConfiguration {
@Autowired
@Qualifier("loginFlowRegistry")
private FlowDefinitionRegistry loginFlowRegistry;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
private FlowBuilderServices flowBuilderServices;
@Bean("defaultLoginWebflowConfigurer")
public CasWebflowConfigurer defaultLoginWebflowConfigurer() {
DefaultCaptchaWebflowConfigurer c = new DefaultCaptchaWebflowConfigurer(flowBuilderServices, loginFlowRegistry, applicationContext, casProperties);
c.initialize();
return c;
}
}
配置表单处理器
package com.wangsaichao.cas.config;
import com.wangsaichao.cas.adaptors.generic.RememberMeUsernamePasswordCaptchaAuthenticationHandler;
import com.wangsaichao.cas.service.UserService;
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: wangsaichao
* @date: 2018/8/31
* @description:
*/
@Configuration("rememberMeConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class RememberMeConfiguration implements AuthenticationEventExecutionPlanConfigurer {
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@Autowired
private UserService userService;
/**
* 放到 shiro(order=10) 验证器的前面 先验证验证码
* @return
*/
@Bean
public AuthenticationHandler rememberMeUsernamePasswordCaptchaAuthenticationHandler() {
RememberMeUsernamePasswordCaptchaAuthenticationHandler handler = new RememberMeUsernamePasswordCaptchaAuthenticationHandler(
RememberMeUsernamePasswordCaptchaAuthenticationHandler.class.getSimpleName(),
servicesManager,
new DefaultPrincipalFactory(),
9);
handler.setUserService(userService);
return handler;
}
@Override
public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
plan.registerAuthenticationHandler(rememberMeUsernamePasswordCaptchaAuthenticationHandler());
}
}
然后在resources/META-INF/spring.factories
中配置该项
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wangsaichao.cas.config.SpringConfig,\
com.wangsaichao.cas.config.RememberMeConfiguration,\
com.wangsaichao.cas.config.CaptchaWebflowConfiguration,\
com.wangsaichao.cas.config.DataSourceConfig
配置什么的 都搞好了,还差验证码的Controller 和 工具
CaptchaUtil
package com.wangsaichao.cas.global.utils;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* 验证码工具类
*
* @author <a href="http://www.micmiu.com">Michael Sun</a>
*/
public class CaptchaUtil {
// 随机产生的字符串
private static final String RANDOM_STRS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final String FONT_NAME = "Fixedsys";
private static final int FONT_SIZE = 18;
private Random random = new Random();
private int width = 80;// 图片宽
private int height = 25;// 图片高
private int lineNum = 50;// 干扰线数量
private int strNum = 4;// 随机产生字符数量
/**
* 生成随机图片
*/
public BufferedImage genRandomCodeImage(StringBuffer randomCode) {
// BufferedImage类是具有缓冲区的Image类
BufferedImage image = new BufferedImage(width, height,
BufferedImage.TYPE_INT_BGR);
// 获取Graphics对象,便于对图像进行各种绘制操作
Graphics g = image.getGraphics();
// 设置背景色
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height);
// 设置干扰线的颜色
g.setColor(getRandColor(110, 120));
// 绘制干扰线
for (int i = 0; i <= lineNum; i++) {
drowLine(g);
}
// 绘制随机字符
g.setFont(new Font(FONT_NAME, Font.ROMAN_BASELINE, FONT_SIZE));
for (int i = 1; i <= strNum; i++) {
randomCode.append(drowString(g, i));
}
g.dispose();
return image;
}
/**
* 给定范围获得随机颜色
*/
private Color getRandColor(int fc, int bc) {
if (fc > 255){
fc = 255;
}
if (bc > 255){
bc = 255;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
/**
* 绘制字符串
*/
private String drowString(Graphics g, int i) {
g.setColor(new Color(random.nextInt(101), random.nextInt(111), random
.nextInt(121)));
String rand = String.valueOf(getRandomString(random.nextInt(RANDOM_STRS
.length())));
g.translate(random.nextInt(3), random.nextInt(3));
g.drawString(rand, 13 * i, 16);
return rand;
}
/**
* 绘制干扰线
*/
private void drowLine(Graphics g) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int x0 = random.nextInt(16);
int y0 = random.nextInt(16);
g.drawLine(x, y, x + x0, y + y0);
}
/**
* 获取随机的字符
*/
private String getRandomString(int num) {
return String.valueOf(RANDOM_STRS.charAt(num));
}
// public static void main(String[] args) {
// CaptchaUtil tool = new CaptchaUtil();
// StringBuffer code = new StringBuffer();
// BufferedImage image = tool.genRandomCodeImage(code);
// System.out.println("random code = " + code);
// try {
// // 将内存中的图片通过流动形式输出到客户端
// ImageIO.write(image, "JPEG", new FileOutputStream(new File(
// "/Users/wangsaichao/Desktop/random-code.jpg")));
// } catch (Exception e) {
// e.printStackTrace();
// }
//
// }
}
Controller
package com.wangsaichao.cas.controller;
import com.wangsaichao.cas.global.utils.CaptchaUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
* @author: wangsaichao
* @date: 2018/5/26
* @description:
*/
@Controller
public class CaptchaController {
public static final String KEY_CAPTCHA = "captcha";
@RequestMapping("/captcha.jpg")
public void getCaptcha(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
// 设置相应类型,告诉浏览器输出的内容为图片
response.setContentType("image/jpeg");
// 不缓存此内容
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expire", 0);
try {
HttpSession session = request.getSession();
CaptchaUtil tool = new CaptchaUtil();
StringBuffer code = new StringBuffer();
BufferedImage image = tool.genRandomCodeImage(code);
session.removeAttribute(KEY_CAPTCHA);
session.setAttribute(KEY_CAPTCHA, code.toString());
// 将内存中的图片通过流动形式输出到客户端
ImageIO.write(image, "JPEG", response.getOutputStream());
} catch (Exception e) {
e.printStackTrace();
}
}
}
7.页面添加验证码标签:
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title th:text="${#themes.code('cas.page.title')}">title>
<link rel="stylesheet" th:href="@{${#themes.code('cas.myself.css')}}"/>
head>
<body>
<h2 th:text="${#themes.code('cas.page.title')}">h2>
<div>
<form method="post" th:object="${credential}">
<div th:if="${#fields.hasErrors('*')}">
<span th:each="err : ${#fields.errors('*')}" th:utext="${err}" style="color: red" />
div>
<h4 th:utext="#{screen.welcome.instructions}">h4>
<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"
th:value="admin" />
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"
th:value="123456" />
div>
section>
<section class="row">
<label for="captcha">验证码:label>
<div>
<input class="required"
id="captcha"
name="captcha"
size="10"
tabindex="2"
th:field="*{captcha}"
autocomplete="off"/>
<img th:src="@{/captcha.jpg}" id="captcha_img" onclick="javascript:refreshCaptcha()"/>
div>
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 Melabel>
p>
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>
<script th:src="@{${#themes.code('cas.javascript.file')}}">script>
<script type="text/javascript">
function refreshCaptcha(){
$("#captcha_img").attr("src","/cas/captcha.jpg?id=" + new Date() + Math.floor(Math.random()*24));
}
script>
html>