CAS 实现站内单点登录及实现第三方 OAuth、OpenId 登录(二)

Stella981
• 阅读 476

一、登录表单

<form:form id="loginForm" method="post" commandName="${commandName}" htmlEscape="true">
    <form:errors path="*" element="em" cssClass="" />
    <c:if test="${not empty sessionScope.openIdLocalId}">
        用户名<input id="username" name="username" type="text" value="${sessionScope.openIdLocalId}" />
    </c:if>
    <c:if test="${empty sessionScope.openIdLocalId}">
        <!-- 这里您不要直接使用 input,而使用 springframework 的 form:input 能够在登录出错时,还能保证显示之前登录时输入的用户名 -->
        <form:input id="username" path="username" htmlEscape="true" />
    </c:if>
    密码:<input id="password" name="password" type="password" />
    验证码:<input id="validateCode" name="validateCode" type="text" /><img src="https://my.oschina.net/captcha.jpg" />
    <input id="rememberMe" name="rememberMe" type="checkbox" value="true" /><label for="rememberMe">记住用户名</label>
    <input id="login-Btn" type="button" value="登录" />
    <input name="lt" type="hidden" value="${loginTicket}" />
    <input name="execution" type="hidden" value="${flowExecutionKey}" />
    <input name="_eventId" type="hidden" value="submit" />
</form:form>
<!-- 需要 jquery.cookie 和 jquery.md5 插件 -->
<script type="text/javascript">
    $(document).ready(function(){
        var loginUsername = $.cookie("loginUsername");
        if(loginUsername){
            $("#username").val(loginUsername);
            $("#rememberMe").attr("checked", true);
        }
        $("#login-Btn").click(function(){
            /* 为了密码传输安全,表单提交之前,进行 md5 加密 */
            $("#password").val($.md5($("#password").val()));
            if($("#rememberMe").is(":checked") == true){
                $.cookie("loginUsername", $("#username").val(), {expires: 365});
            }else{
                $.cookie("loginUsername", null, {expires: -1});
            }
            $("#loginForm").submit();
        });
    });
</script>

二、验证码

        此例子中的验证实现是使用的 Google 的 Kaptcha

  1. Google Kaptcha 配置(在 WEB-INF/spring-configuration/applicationContext.xml 添加)

    <bean id="captchaConfig" class="com.google.code.kaptcha.util.Config">
        ... ...
    </bean>
    <bean id="captchaProducer" class="com.google.code.kaptcha.impl.DefaultKaptcha" p:config-ref="captchaConfig" />
    
  2. Servlet 配置

    1)修改 WEB-INF/cas-servlet.xml 中的

    <bean id="authenticationViaFormAction" class="org.jasig.cas.web.flow.AuthenticationViaFormAction"
            p:centralAuthenticationService-ref="centralAuthenticationService"
            p:warnCookieGenerator-ref="warnCookieGenerator"/>
    

        为 : AuthenticationCaptchaViaFormAction.java(用户验证 Action)

    <bean id="authenticationViaFormAction" class="com.buession.cas.web.flow.AuthenticationCaptchaViaFormAction"
            p:captchaConfig-ref="captchaConfig"
            p:centralAuthenticationService-ref="centralAuthenticationService"
            p:warnCookieGenerator-ref="warnCookieGenerator" />
    

    2)在 WEB-INF/cas-servlet.xml 添加
    CaptchaController.java(验证码控制器)
    ValidateCaptchaController.java(验证码异步验证控制器)

    <bean id="handlerMappingC" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <props>
                <prop key="/captcha.jpg">captchaController</prop><!-- 验证码 Controller -->
                <prop key="/validateCaptcha">validateCaptchaController</prop><!-- 验证码异步验证 Controller,不是必须 -->
                ... ...
            </props>
        </property>
    </bean>
    <bean id="captchaController" class="com.buession.cas.web.controller.CaptchaController"
        p:config-ref="captchaConfig" />
    <bean id="validateCaptchaController" class="com.buession.cas.web.controller.ValidateCaptchaController" 
        p:config-ref="captchaConfig" />
    

    3)记得在 web.xml 添加 Servlet 和 URL 之间映射

三、用户认证

  1. 数据库配置(在添加 WEB-INF/spring-configuration/applicationContext.xml 添加)

    <bean id="masterJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
        p:dataSource-ref="masterDataSource" />
    <bean id="slaveJdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
        p:dataSource-ref="slaveDataSource" />
    
    <bean id="masterDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        ... ...
        destroy-method="close" />
    <bean id="slaveDataSource" class="org.apache.commons.dbcp.BasicDataSource"
        ... ...
        destroy-method="close" />
    

    为什么要配置 master 数据源和 slave 数据源?
    用户登录时,查询用户信息是只读,我们就从从库中查询,主库是可以用于向数据库中写入用户登录日志,或者密码错误次数(当然这里可以根据实际需求调整,比如用户密码输入错误次数,也是可以记录到 Memcache、Redis 等缓存服务器中的)

  2. 修改用户凭证 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" />
    
  3. 修改 AuthenticationViaFormAction(WEB-INF/login-webflow.xml)

    修改

    <view-state id="viewLoginForm" view="casLoginView" model="credentials">
        <binder>
            <binding property="username" />
            <binding property="password" />
        </binder>
        ... ...
    </view-state>
    

    为(binding 的 property 属性值一定要与 Credentials 的属性名和登录表单控件的 name 属性的值保持一致)

    <view-state id="viewLoginForm" view="casLoginView" model="credentials">
        <binder>
            <binding property="username" />
            <binding property="password" />
            <binding property="validateCode" />
        </binder>
        ... ...
    </view-state>
    
  4. 添加数据库 authentication handlers(在 WEB-INF/deployerConfigContext.xml 中添加)

    <bean id="authenticationManager" class="org.jasig.cas.authentication.AuthenticationManagerImpl">
        ... ...
        <property name="authenticationHandlers">
            <list>
                <bean class="com.domain.authentication.handler.DatabaseAuthenticationHandler">
                    <property name="jdbcTemplate" ref="slaveJdbcTemplate" />
                    <!--(1)--><property name="sql" value="SELECT `password`, `salt`, `algo` FROM `member` WHERE `username` = ? LIMIT 1" />
                    <!--(2)--><property name="sql" value="SELECT `password`, `salt`, `algo` FROM `member` WHERE `username` = ? OR `email` = ? OR `mobile` = ? LIMIT 1" />
                    <!--(3)--><property name="sql" value="SELECT `password`, `salt`, `algo` FROM `member` WHERE `username` = ? OR `email` = ? OR `mobile` = ?" />
                    <!-- (1)适用于单一条件登录;(2)适用于多条件,且这些条件中不会出现相同值的情况;(3)适用于多条件,但这些条件中可能会存在相同值的情况,例如:存在 username 为:13800138000,和 mobile 也为:13800138000 的不同两条用户数据 -->
                    <property name="passwordEncoder" ref="passwordEncoder" />
                </bean>
            </list>
        </property>
    </bean>
    
    class DatabaseAuthenticationHandler extends com.buession.cas.authentication.handler.support.DatabaseQueryAuthenticationHandler {
    
        @Override
        protected boolean authenticateUsernamePasswordInternal(UsernamePasswordCredentials credentials)
                throws AuthenticationException {
            PasswordEncoder passwordEncoder = (PasswordEncoder) getPasswordEncoder();
            String username = getPrincipalNameTransformer().transform(credentials.getUsername());
            String password = credentials.getPassword();
    
            (1)、(2)
            try {
                final Map<String, Object> r = jdbcTemplate.queryForMap(sql, username, username,
                        username);
                if (valid(username, password, r, passwordEncoder) == true) {
                     /**
                       * 对旧用户数据更换加密方式,且重新生成密码
                     */
                    if (Mcrypt.MD5.equals(r.get("algo")) == true) {
                        modifyEncoder((String) r.get("username"), credentials.getPassword(),
                                (String) r.get("salt"));
                    }
                    return ture;
                }
            } catch (IncorrectResultSizeDataAccessException e) {
            }
    
            (3)
            try {
                List<Map<String, Object>> result = jdbcTemplate.queryForList(sql, username, username,
                        username);
    
                if (result != null && result.size() > 0) {
                    for (Map<String, Object> r : result) {
                        if (valid(username, password, r, passwordEncoder) == true) {
                            /**
                             * 对旧用户数据更换加密方式,且重新生成密码
                             */
                            if (Mcrypt.MD5.equals(r.get("algo")) == true) {
                                modifyEncoder((String) r.get("username"), credentials.getPassword(),
                                        (String) r.get("salt"));
                            }
                            return true;
                        }
                    }
                }
            } catch (IncorrectResultSizeDataAccessException e) {
            }
    
            return false;
        }
    
        private boolean valid(String username, String password, final Map<String, Object> object,
                PasswordEncoder passwordEncoder) {
            String salt = (String) object.get("salt");
            String algo = (String) object.get("algo");
    
            passwordEncoder.setAlgo(algo);
    
            /**
             * 如果加密方式为 "MD5",则为老的加密方式
             * 旧系统中,密码为明文传输,在后端 MD5 加密的,
             * 现在密码在前端进行了 MD5 加密传输,所以无需双重加密,而直接 encode(password+salt)
             */
            if (Mcrypt.MD5.equals(algo) == true) {
                password += salt;
            } else {
                passwordEncoder.setSalt(salt);
            }
    
            final String encodedPassword = passwordEncoder.encode(password);
            return encodedPassword != null && encodedPassword.equalsIgnoreCase((String) object.get("password"));
        }
    
        private void modifyEncoder(final String username, final String password, final String salt) {
            PasswordEncoder passwordEncoder = (PasswordEncoder) getPasswordEncoder();
    
            passwordEncoder.setAlgo(Mcrypt.SHA512);
            passwordEncoder.setSalt(salt);
    
            String sql = "UPDATE `member` SET `algo` = ?, `password` = ? WHERE `username` = ?";
            jdbcTemplate.update(sql, Mcrypt.SHA512, passwordEncoder.encode(password), username);
        }
    }
    
  5. 密码加密配置(在添加 WEB-INF/deployerConfigContext.xml 添加)

    <bean id="passwordEncoder" class="com.buession.cas.authentication.handler.PasswordEncoder"
        p:characterEncoding="UTF-8" />
    

    PasswordEncoder.java

  6. 添加验证码验证消息(WEB-INF/classes/messages_xxx.properties)

    required.captcha=验证码不能为空
    INVALID_CAPTCHA=验证码错误
    
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
2年前
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法
Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法参考文章:(1)Google地球出现“无法连接到登录服务器(错误代码:c00a0194)”解决方法(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fwww.codeprj.com%2Fblo
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这