cas5.3.2单点登录-重写Credential自定义表单信息(添加验证码)(十七)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34021712/article/details/82259101

原文地址,转载请注明出处: 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的功能。

具体操作

1.重写credential,继承RememberMeUsernamePasswordCredential,加上capcha属性
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();
    }
}
2.新建DefaultCaptchaWebflowConfigurer修改之前默认的Credential
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。
这里写图片描述

3.创建表单处理器

这块用到的就是我们之前用过的自定义验证。参考之前的博客。

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<String, Object> 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;
    }
}
4.新建配置类来 配置 DefaultCaptchaWebflowConfigurer 和 表单处理器

配置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());
    }
}
5.加载配置类

然后在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
6.验证码控制器 和 工具类

配置什么的 都搞好了,还差验证码的Controller 和 工具
CaptchaUtil

package com.wangsaichao.cas.global.utils;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;

/**
 * 验证码工具类
 *
 * @author &lt;a href="http://www.micmiu.com"&gt;Michael Sun&lt;/a&gt;
 */
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.页面添加验证码标签:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<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()"/>
                <!--(看不清<a href="javascript:void(0)" onclick="javascript:refreshCaptcha()">换一张</a>)-->
            </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 Me</label>
            </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>

测试演示

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq_34021712/article/details/82259101