发表于 2019-6-24 17:24

申请会员ID:HarryWan

1、申 请 I D:HarryWan
2、个人邮箱:harrywan1993@163.com

3、原创技术文章:单点登陆CAS源码编译,添加自定义密码加解密校验(后端RSA解密,MD5+Salt加密)

环境准备:Cas Server=4.0.0
               java version=1.7(官方申明cas4只支持jdk7及以下版本,高于1.7源码编译不通过)
               maven=3.6

背景:企业ToB项目需要支持单点登陆,Cas自带的密码加解密校验方式不支持项目本身,需要进行源码的二次开发。
自定义加解密:前端RSA加密,到CAS Server先进行RSA解密,在对明文密码进行Md5+Salt加密,和后端DB里的密码(Md5+Salt)进行比对,从而完成SSO

文章包括:1.针对自定义加解密源码改动
               2.CasServer部分源码解读

1.改动入口:cas-server-webapp下的deployerConfigContext.xml


<bean id="dataSource"
                class="org.springframework.jdbc.datasource.DriverManagerDataSource">
                <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
                <property name="url" value="jdbc:mysql://1.1.1.1:8306/demo"></property>
                <property name="username" value="xixi"></property>
                <property name="password" value="haha"></property>
      </bean>

      <bean id="primaryAuthenticationHandler"
                class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
                <property name="dataSource" ref="dataSource" />
                <property name="sql" value="select xx from xx where xx = ?" />
                <property name="passwordEncoder" ref="Md5SaltPasswordEncoder" />
      </bean>
<!-- <bean id="primaryAuthenticationHandler"
          class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler">
      <property name="users">
            <map>
                <entry key="casuser" value="Mellon"/>
            </map>
      </property>
    </bean> -->
注:1.添加数据源
   2.Cas不同的身份校验方式对应不同的Handler默认的校验方式为AcceptUsersAuthenticationHandler写死的用户名密码casuser/Mellon,这边注释,使用QueryDatabaseAuthenticationHandler,配置数据源,这里的Sql语句用来查询Db里对应的用户名密码进行身份判断,具体的密码加解密流程就在自定义的Md5SaltPasswordEncoder.java中

<bean id="Md5SaltPasswordEncoder"
                class="org.jasig.cas.authentication.handler.Md5SaltPasswordEncoder">
                <constructor-arg index="0">
                        <value>MD5</value>
                </constructor-arg>
                <constructor-arg index="1">
                        <value>此处是Md5加密的盐值</value>
                </constructor-arg>
                <constructor-arg index="2">
                        <value>此处是Rsa的私钥,用于Rsa解密</value>
                </constructor-arg>
                <constructor-arg index="3">
                        <value>UTF-8</value>
                </constructor-arg>
      </bean>
注:这里通过构造器注入的方式,处理的业务逻辑为将前端的rsa密文解密,在进行MD5+Salt加密

/*
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License.You may obtain a
* copy of the License at the following location:
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.cas.authentication.handler;

import javax.validation.constraints.NotNull;

import org.apache.commons.lang.StringUtils;
import org.jasig.cas.util.MD5Utils;
import org.jasig.cas.util.RsaUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Md5SaltPasswordEncoder implements PasswordEncoder {
      protected final Logger logger = LoggerFactory.getLogger(this.getClass());

      @NotNull
      private final String encodingAlgorithm;
      @NotNull
      private final String salt;
      @NotNull
      private final String privateKey;
      @NotNull
      private final String characterEncoding;


      @Override
      public String encode(String password) {
                if (!StringUtils.isEmpty(password)) {
                        String rsaDecryptPwd = RsaUtils.decrypt(password, privateKey);
                        logger.debug("Md5SaltPasswordEncoder#encode,rsaDecryptPwd= {}", rsaDecryptPwd);
                        String md5SaltPwd = MD5Utils.digestWithSalt(rsaDecryptPwd, salt);
                        logger.debug("Md5SaltPasswordEncoder#encode,md5SaltPwd= {}", md5SaltPwd);
                        return md5SaltPwd;
                }
                return null;
      }

      public Md5SaltPasswordEncoder(String encodingAlgorithm, String salt, String privateKey, String characterEncoding) {
                this.encodingAlgorithm = encodingAlgorithm;
                this.salt = salt;
                this.privateKey = privateKey;
                this.characterEncoding = characterEncoding;
      }


}

注:实现cas自带的PasswordEncoder,实现自定义encode方法,对前端的密码进行加解密,具体加解密代码分装在工具类中


https://static.52pojie.cn/static/image/hrline/1.gif


2.部分源码解析(用户身份认证流程)
2.1 顶层接口AuthenticationHandler,通过authenticate(Credential credential)方法进行身份认证
2.2 抽象类AbstractUsernamePasswordAuthenticationHandler实现AuthenticationHandler接口,doAuthentication(final Credential credential)方法做认证前置的部分校验,
      通过模板方法模式暴露authenticateUsernamePasswordInternal钩子方法,共自定义实现,不同的Handler实现对应不同的认证方式,包括Shiro,Ldap,QueryDatabase,以及默认的AcceptUserName方式,这边具体的 Handler在上面的xml中进行指定
@Override
    protected final HandlerResult doAuthentication(final Credential credential)
            throws GeneralSecurityException, PreventedException {
      final UsernamePasswordCredential userPass = (UsernamePasswordCredential) credential;
      if (userPass.getUsername() == null) {
            throw new AccountNotFoundException("Username is null.");
      }
      
      final String transformedUsername= this.principalNameTransformer.transform(userPass.getUsername());
      if (transformedUsername == null) {
            throw new AccountNotFoundException("Transformed username is null.");
      }
      userPass.setUsername(transformedUsername);
      //钩子方法authenticateUsernamePasswordInternal,不同Handler实现类对应不同的认证方式,在deploy.xml配置指定
      return authenticateUsernamePasswordInternal(userPass);
    }
2.3,具体的认证流程在QueryDatabaseAuthenticationHandler类中,已在代码中进行了步骤注释
@Override
    protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential)
            throws GeneralSecurityException, PreventedException {

      final String username = credential.getUsername();
      //1.获取指定的Md5SaltPasswordEncoder进行前端密码加解密
      final String encryptedPassword = this.getPasswordEncoder().encode(credential.getPassword());
      try {
                //2.通过配置的数据源查询sql获取db中的密码
            final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username);
            //3.这里进行密码比对,db里的密码和前端密码
            if (!dbPassword.equals(encryptedPassword)) {
                throw new FailedLoginException("Password does not match value on record.");
            }
      } catch (final IncorrectResultSizeDataAccessException e) {
            if (e.getActualSize() == 0) {
                throw new AccountNotFoundException(username + " not found with SQL query");
            } else {
                throw new FailedLoginException("Multiple records found for " + username);
            }
      } catch (final DataAccessException e) {
            throw new PreventedException("SQL exception while executing query for " + username, e);
      }
      return createHandlerResult(credential, new SimplePrincipal(username), null);
    }


https://static.52pojie.cn/static/image/hrline/1.gif


除此之外,在对Cas 源码进行编译时,也遇到了不少坑,比如绕过License的认证,部分jar包不存在需要手动指定仓库地址,以及如何绕过自带的code style校验!

tips:犯了低级错误,帖子没有备份保存下,发表的时候出错,内容回滚没有回滚全,又重新写了遍,后面申请的可以注意下!!!

Hmily 发表于 2019-6-24 18:45

抱歉,未能达到申请要求,申请不通过,可以关注论坛官方微信(吾爱破解论坛),等待开放注册通知。
页: [1]
查看完整版本: 申请会员ID:HarryWan