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