• CAS单点登录:配置记住我,添加验证码(五)


    1.配置RememberMe

    1.1.修改application.properties

    ##
    # 记住我
    #
    cas.ticket.tgt.rememberMe.enabled=true
    cas.ticket.tgt.rememberMe.timeToKillInSeconds=3600

    1.2.修改登录界面

    <div class="form-group" th:if="${rememberMeAuthenticationEnabled}">
      <input type="checkbox" name="rememberMe" id="rememberMe" value="true" tabindex="5"/>
      <label for="rememberMe" th:text="#{screen.rememberme.checkbox.title}">Remember Me</label>
    </div>

    1.3.测试流程

    第一步:首先 不选择记住我登录 然后退出浏览器。

    第二步:打开浏览器,再次访问服务 发现需要登录。

    第三步:选择 记住我登录,然后退出浏览器。

    第四步:打开浏览器,访问服务,直接就是登录成功状态。(前提是退出浏览器前不要登出)

    2.添加验证码

    从页面登录页面上我们可以知道,登陆的用户名和密码信息绑定到了credential这个对象上的。

    如果开启了RememberMe的功能就使用RememberMeUsernamePasswordCredential。

    如果没有就使用UsernamePasswordCredential了,这里我们使用RememberMeUsernamePasswordCredential。

    2.1.添加依赖

    <dependency>
        <groupId>org.apereo.cas</groupId>
        <artifactId>cas-server-core-authentication</artifactId>
        <version>${cas.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apereo.cas</groupId>
        <artifactId>cas-server-core-authentication-api</artifactId>
        <version>${cas.version}</version>
    </dependency>
    <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>

    这里可能会在后续继承DefaultLoginWebflowConfigurer时无法导入依赖

    虽然cas-server-core-webflow中有那个类,但是无法导入,所以单独引入cas-server-core-webflow-api这个依赖

    2.2.重写credential

    import org.apache.commons.lang3.builder.HashCodeBuilder;
    import org.apereo.cas.authentication.RememberMeUsernamePasswordCredential;public class RememberMeUsernamePasswordCaptchaCredential extends RememberMeUsernamePasswordCredential {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.3.新建DefaultCaptchaWebflowConfigurer修改之前默认的Credential

    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;
    
    public class DefaultCaptchaWebflowConfigurer extends DefaultLoginWebflowConfigurer {
    
        public DefaultCaptchaWebflowConfigurer(FlowBuilderServices flowBuilderServices, FlowDefinitionRegistry flowDefinitionRegistry, ApplicationContext applicationContext, CasConfigurationProperties casProperties) {
            super(flowBuilderServices, flowDefinitionRegistry, applicationContext, casProperties);
        }
    
        @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);
                final ViewState state = getState(flow, CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM, ViewState.class);
                final BinderConfiguration cfg = this.getViewStateBinderConfiguration(state);
                cfg.addBinding(new BinderConfiguration.Binding("captcha", null, true));
            }
        }
    }

    2.4.创建表单处理器

    import com.fdzang.cas.service.framework.ApiResult;
    import com.fdzang.cas.service.service.UserService;
    import com.fdzang.cas.service.util.Constant;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    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.RegisteredService;
    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 javax.servlet.http.HttpServletRequest;
    import java.security.GeneralSecurityException;
    
    @Slf4j
    public class RememberMeUsernamePasswordCaptchaAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler {
    
        private 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 {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
    
            RememberMeUsernamePasswordCaptchaCredential captchaCredential = (RememberMeUsernamePasswordCaptchaCredential) credential;
            String requestCaptcha = captchaCredential.getCaptcha();
            String username = captchaCredential.getUsername();
            String password = captchaCredential.getPassword();
    
            // 校验验证码
            Object attribute = request.getSession().getAttribute(Constant.CAPTCHA_SESSION_KEY);
            String realCaptcha = attribute == null ? null : attribute.toString();
            if(StringUtils.isBlank(requestCaptcha) || !requestCaptcha.equalsIgnoreCase(realCaptcha)){
                throw new FailedLoginException("验证码错误");
            }
    
            // 获取请求来源URL
            String referer = request.getHeader("referer");
            if(referer.indexOf("service=")>0){
                referer = referer.substring(referer.indexOf("service=")+8);
                referer.replace("%3A",":");
                referer.replace("%2F","/");
            }
    
            RegisteredService service = findByServiceId(referer);
            if (service != null){
                throw new FailedLoginException("未查询到Service错误");
            }
            String appCode = service.getName();
    
            // 登录校验
            ApiResult result = userService.userLogin(username,password,appCode);
            if(!result.getCode().equals(0L)){
                throw new FailedLoginException(result.getMsg());
            }
    
            return createHandlerResult(credential, this.principalFactory.createPrincipal(username));
        }
    
        @Override
        public boolean supports(Credential credential) {
            return credential instanceof RememberMeUsernamePasswordCaptchaCredential;
        }
    
        public RegisteredService findByServiceId(String serviceId){
            RegisteredService service = null;
            try {
                service = servicesManager.findServiceBy(serviceId);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
    
            return service;
        }
    
        public void setUserService(UserService userService) {
            this.userService = userService;
        }
    }

    这里我根据自己的需求做了特定的登录校验,仅做参考。

    2.5.配置DefaultCaptchaWebflowConfigurer 

    import com.fdzang.cas.service.captcha.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;
    
    @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
        @Qualifier("builder")
        private FlowBuilderServices builder;
    
        @Bean("defaultLoginWebflowConfigurer")
        public CasWebflowConfigurer defaultLoginWebflowConfigurer() {
            DefaultCaptchaWebflowConfigurer c = new DefaultCaptchaWebflowConfigurer(builder, loginFlowRegistry, applicationContext, casProperties);
            c.initialize();
            
            return c;
        }
    }

    2.6.配置表单处理器

    import com.fdzang.cas.service.captcha.RememberMeUsernamePasswordCaptchaAuthenticationHandler;
    import com.fdzang.cas.service.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;
    
    @Configuration("rememberMeConfiguration")
    @EnableConfigurationProperties(CasConfigurationProperties.class)
    public class RememberMeCaptchaConfiguration implements AuthenticationEventExecutionPlanConfigurer {
        @Autowired
        @Qualifier("servicesManager")
        private ServicesManager servicesManager;
    
        @Autowired
        private UserService userService;
    
        @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());
        }
    }

    2.7.加载配置类,spring.factories

    org.springframework.boot.autoconfigure.EnableAutoConfiguration=
      org.apereo.cas.config.CasEmbeddedContainerTomcatConfiguration,
      org.apereo.cas.config.CasEmbeddedContainerTomcatFiltersConfiguration,
      com.fdzang.cas.service.config.SpringConfig,
      com.fdzang.cas.service.config.RememberMeCaptchaConfiguration,
      com.fdzang.cas.service.config.CaptchaWebflowConfiguration

    2.8.验证码生成工具类

    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.util.Random;
    
    public class CaptchaUtil {
    
        // 随机产生的字符串
        private static final String RANDOM_STRS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    
        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 = new BufferedImage(width, height,
                    BufferedImage.TYPE_INT_BGR);
            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));
        }
    }

    2.9.验证码控制层

    import com.fdzang.cas.service.util.CaptchaUtil;
    import com.fdzang.cas.service.util.Constant;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    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;
    
    @RestController
    public class CaptchaController {
    
        @GetMapping("/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(Constant.CAPTCHA_SESSION_KEY);
                session.setAttribute(Constant.CAPTCHA_SESSION_KEY, code.toString());
    
                // 将内存中的图片通过流动形式输出到客户端
                ImageIO.write(image, "JPEG", response.getOutputStream());
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    2.10.修改登录页面

    <div class="form-group">
        <label for="captcha">验证码:</label>
        <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>
    
    <script type="text/javascript">
        function refreshCaptcha(){
            $("#captcha_img").attr("src","/cas/captcha.jpg?id=" + new Date() + Math.floor(Math.random()*24));
        }
    </script>

    2.11.注释默认登录逻辑

    #cas.authn.accept.users=admin::123456

    参考:https://blog.csdn.net/qq_34021712/article/details/82259101

  • 相关阅读:
    Leangoo:用敏捷开发管理思维做团队协作的SaaS软件
    张江男的逆袭,我如何使用leangoo提升团队效率
    探索leangoo常用快捷键
    Tkinter教程之Event篇(3)
    Tkinter教程之Event篇(2)
    Tkinter教程之Event篇(1)'
    Tkinter教程之Grid篇
    Tkinter教程之Pack篇
    Tkinter教程之Canvas篇(4)
    Tkinter教程之Canvas篇(3)
  • 原文地址:https://www.cnblogs.com/fdzang/p/12931514.html
Copyright © 2020-2023  润新知