一、登录表单
1<form:form id="loginForm" method="post" commandName="${commandName}" htmlEscape="true"> 2 <form:errors path="*" element="em" cssClass="" /> 3 <c:if test="${not empty sessionScope.openIdLocalId}"> 4 用户名<input id="username" name="username" type="text" value="${sessionScope.openIdLocalId}" /> 5 </c:if> 6 <c:if test="${empty sessionScope.openIdLocalId}"> 7 <!-- 这里您不要直接使用 input,而使用 springframework 的 form:input 能够在登录出错时,还能保证显示之前登录时输入的用户名 --> 8 <form:input id="username" path="username" htmlEscape="true" /> 9 </c:if> 10 密码:<input id="password" name="password" type="password" /> 11 验证码:<input id="validateCode" name="validateCode" type="text" /><img src="https://my.oschina.net/captcha.jpg" /> 12 <input id="rememberMe" name="rememberMe" type="checkbox" value="true" /><label for="rememberMe">记住用户名</label> 13 <input id="login-Btn" type="button" value="登录" /> 14 <input name="lt" type="hidden" value="${loginTicket}" /> 15 <input name="execution" type="hidden" value="${flowExecutionKey}" /> 16 <input name="_eventId" type="hidden" value="submit" /> 17</form:form> 18<!-- 需要 jquery.cookie 和 jquery.md5 插件 --> 19<script type="text/javascript"> 20 $(document).ready(function(){ 21 var loginUsername = $.cookie("loginUsername"); 22 if(loginUsername){ 23 $("#username").val(loginUsername); 24 $("#rememberMe").attr("checked", true); 25 } 26 $("#login-Btn").click(function(){ 27 /* 为了密码传输安全,表单提交之前,进行 md5 加密 */ 28 $("#password").val($.md5($("#password").val())); 29 if($("#rememberMe").is(":checked") == true){ 30 $.cookie("loginUsername", $("#username").val(), {expires: 365}); 31 }else{ 32 $.cookie("loginUsername", null, {expires: -1}); 33 } 34 $("#loginForm").submit(); 35 }); 36 }); 37</script>
二、验证码
此例子中的验证实现是使用的 Google 的 Kaptcha
-
Google Kaptcha 配置(在 WEB-INF/spring-configuration/applicationContext.xml 添加)
1<bean id="captchaConfig" class="com.google.code.kaptcha.util.Config"> 2 ... ... 3</bean> 4<bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha" p:config-ref="captchaConfig" /> -
Servlet 配置
1)修改 WEB-INF/cas-servlet.xml 中的
1<bean id="authenticationViaFormAction" class="org.jasig.cas.web.flow.AuthenticationViaFormAction" 2 p:centralAuthenticationService-ref="centralAuthenticationService" 3 p:warnCookieGenerator-ref="warnCookieGenerator"/>为 : AuthenticationCaptchaViaFormAction.java(用户验证 Action)
1<bean id="authenticationViaFormAction" class="com.buession.cas.web.flow.AuthenticationCaptchaViaFormAction" 2 p:captchaConfig-ref="captchaConfig" 3 p:centralAuthenticationService-ref="centralAuthenticationService" 4 p:warnCookieGenerator-ref="warnCookieGenerator" />2)在 WEB-INF/cas-servlet.xml 添加
CaptchaController.java(验证码控制器)
ValidateCaptchaController.java(验证码异步验证控制器)1<bean id="handlerMappingC" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> 2 <property name="mappings"> 3 <props> 4 <prop key="/captcha.jpg">captchaController</prop><!-- 验证码 Controller --> 5 <prop key="/validateCaptcha">validateCaptchaController</prop><!-- 验证码异步验证 Controller,不是必须 --> 6 ... ... 7 </props> 8 </property> 9</bean> 10<bean id="captchaController" class="com.buession.cas.web.controller.CaptchaController" 11 p:config-ref="captchaConfig" /> 12<bean id="validateCaptchaController" class="com.buession.cas.web.controller.ValidateCaptchaController" 13 p:config-ref="captchaConfig" />3)记得在 web.xml 添加 Servlet 和 URL 之间映射
三、用户认证
-
数据库配置(在添加 WEB-INF/spring-configuration/applicationContext.xml 添加)
1<bean id="masterJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" 2 p:dataSource-ref="masterDataSource" /> 3<bean id="slaveJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" 4 p:dataSource-ref="slaveDataSource" /> 5 6<bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource" 7 ... ... 8 destroy-method="close" /> 9<bean id="slaveDataSource" class="org.apache.commons.dbcp.BasicDataSource" 10 ... ... 11 destroy-method="close" />为什么要配置 master 数据源和 slave 数据源?
用户登录时,查询用户信息是只读,我们就从从库中查询,主库是可以用于向数据库中写入用户登录日志,或者密码错误次数(当然这里可以根据实际需求调整,比如用户密码输入错误次数,也是可以记录到 Memcache、Redis 等缓存服务器中的) -
修改用户凭证 credentials(WEB-INF/login-webflow.xml)
修改
<var name="credentials" class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />为:RememberMeUsernamePasswordCaptchaCredentials.java(带有 ”RememberMe“ 和 ”验证码“的用户凭证)
<var name="credentials" class="com.buession.cas.authentication.principal.RememberMeUsernamePasswordCaptchaCredentials" /> -
修改 AuthenticationViaFormAction(WEB-INF/login-webflow.xml)
修改
1<view-state id="viewLoginForm" view="casLoginView" model="credentials"> 2 <binder> 3 <binding property="username" /> 4 <binding property="password" /> 5 </binder> 6 ... ... 7</view-state>为(binding 的 property 属性值一定要与 Credentials 的属性名和登录表单控件的 name 属性的值保持一致)
1<view-state id="viewLoginForm" view="casLoginView" model="credentials"> 2 <binder> 3 <binding property="username" /> 4 <binding property="password" /> 5 <binding property="validateCode" /> 6 </binder> 7 ... ... 8</view-state> -
添加数据库 authentication handlers(在 WEB-INF/deployerConfigContext.xml 中添加)
1<bean id="authenticationManager" class="org.jasig.cas.authentication.AuthenticationManagerImpl"> 2 ... ... 3 <property name="authenticationHandlers"> 4 <list> 5 <bean class="com.domain.authentication.handler.DatabaseAuthenticationHandler"> 6 <property name="jdbcTemplate" ref="slaveJdbcTemplate" /> 7 <!--(1)--><property name="sql" value="SELECT `password`, `salt`, `algo` FROM `member` WHERE `username` = ? LIMIT 1" /> 8 <!--(2)--><property name="sql" value="SELECT `password`, `salt`, `algo` FROM `member` WHERE `username` = ? OR `email` = ? OR `mobile` = ? LIMIT 1" /> 9 <!--(3)--><property name="sql" value="SELECT `password`, `salt`, `algo` FROM `member` WHERE `username` = ? OR `email` = ? OR `mobile` = ?" /> 10 <!-- (1)适用于单一条件登录;(2)适用于多条件,且这些条件中不会出现相同值的情况;(3)适用于多条件,但这些条件中可能会存在相同值的情况,例如:存在 username 为:13800138000,和 mobile 也为:13800138000 的不同两条用户数据 --> 11 <property name="passwordEncoder" ref="passwordEncoder" /> 12 </bean> 13 </list> 14 </property> 15</bean> 16 17class DatabaseAuthenticationHandler extends com.buession.cas.authentication.handler.support.DatabaseQueryAuthenticationHandler { 18 19 @Override 20 protected boolean authenticateUsernamePasswordInternal(UsernamePasswordCredentials credentials) 21 throws AuthenticationException { 22 PasswordEncoder passwordEncoder = (PasswordEncoder) getPasswordEncoder(); 23 String username = getPrincipalNameTransformer().transform(credentials.getUsername()); 24 String password = credentials.getPassword(); 25 26 (1)、(2) 27 try { 28 final Map<String, Object> r = jdbcTemplate.queryForMap(sql, username, username, 29 username); 30 if (valid(username, password, r, passwordEncoder) == true) { 31 /** 32 * 对旧用户数据更换加密方式,且重新生成密码 33 */ 34 if (Mcrypt.MD5.equals(r.get("algo")) == true) { 35 modifyEncoder((String) r.get("username"), credentials.getPassword(), 36 (String) r.get("salt")); 37 } 38 return ture; 39 } 40 } catch (IncorrectResultSizeDataAccessException e) { 41 } 42 43 (3) 44 try { 45 List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, username, username, 46 username); 47 48 if (result != null && result.size() > 0) { 49 for (Map<String, Object> r : result) { 50 if (valid(username, password, r, passwordEncoder) == true) { 51 /** 52 * 对旧用户数据更换加密方式,且重新生成密码 53 */ 54 if (Mcrypt.MD5.equals(r.get("algo")) == true) { 55 modifyEncoder((String) r.get("username"), credentials.getPassword(), 56 (String) r.get("salt")); 57 } 58 return true; 59 } 60 } 61 } 62 } catch (IncorrectResultSizeDataAccessException e) { 63 } 64 65 return false; 66 } 67 68 private boolean valid(String username, String password, final Map<String, Object> object, 69 PasswordEncoder passwordEncoder) { 70 String salt = (String) object.get("salt"); 71 String algo = (String) object.get("algo"); 72 73 passwordEncoder.setAlgo(algo); 74 75 /** 76 * 如果加密方式为 "MD5",则为老的加密方式 77 * 旧系统中,密码为明文传输,在后端 MD5 加密的, 78 * 现在密码在前端进行了 MD5 加密传输,所以无需双重加密,而直接 encode(password+salt) 79 */ 80 if (Mcrypt.MD5.equals(algo) == true) { 81 password += salt; 82 } else { 83 passwordEncoder.setSalt(salt); 84 } 85 86 final String encodedPassword = passwordEncoder.encode(password); 87 return encodedPassword != null && encodedPassword.equalsIgnoreCase((String) object.get("password")); 88 } 89 90 private void modifyEncoder(final String username, final String password, final String salt) { 91 PasswordEncoder passwordEncoder = (PasswordEncoder) getPasswordEncoder(); 92 93 passwordEncoder.setAlgo(Mcrypt.SHA512); 94 passwordEncoder.setSalt(salt); 95 96 String sql = "UPDATE `member` SET `algo` = ?, `password` = ? WHERE `username` = ?"; 97 jdbcTemplate.update(sql, Mcrypt.SHA512, passwordEncoder.encode(password), username); 98 } 99} -
密码加密配置(在添加 WEB-INF/deployerConfigContext.xml 添加)
1<bean id="passwordEncoder" class="com.buession.cas.authentication.handler.PasswordEncoder" 2 p:characterEncoding="UTF-8" />PasswordEncoder.java
-
添加验证码验证消息(WEB-INF/classes/messages_xxx.properties)
1required.captcha=验证码不能为空 2INVALID_CAPTCHA=验证码错误