CAS 单点登录,用户登录次数、时间、ip限制登录

最近有需求需要对单点登录(cas3.5),用户次数和时间进行限制,例如ghf用户在1分钟内密码错误次数超过三次禁止登录,锁定1分钟,网上很多说配置throttleInterceptor,但最后发现是对频率的限制,以下是参数解释:

thresholdRate = (double) failureThreshold / (double) failureRangeInSeconds
           failureRangeInSeconds 失败范围时间 :多少秒内失败算一次;例如10,表示 10秒为一个周期,这个周期内失败次数累加,超过10秒,重新开始;同时也表示锁定时间。
总结来说即在一秒内用户错误登录的频率大于failureThreshold/failureRangeInSeconds时候,就会被阻止。显然根本实现不了需求,看到有文章说使用日志审计功能可以,特意进行了修改,基本完成需求,仍然有小问题,有时间优化以下吧!

参考文章:https://blog.csdn.net/zhurhyme/article/details/37535697

总体思路是,用户登录日志插入CAS_LOG表中,用户每登录一次就记录一条数据,登录成功失败记录信息不一致,在程序内部查询最近几分钟(例如1分钟)内几(例如3次)次登录失败的次数,超过规定次数跳转到提示页面。修改后时间、次数、表名都可以配置。因为cas默认日志审计默认是COM_AUDIT_TRAIL表,不符合公司项目默认建表规范,需要修改。

以下是修改过程:

1.新建WEB-INF/spring-configuration/throttleInterceptorTrigger.xml文件

注释第一部分是在内存中进行限制的方法,不满足需求,第二部分是cas原生的日志审计功能,需要修改。最后是代码部分是真正的配置信息,注意红色部分。




    
    
          
          
          

    
          
          
        
        
    

    
    MyInspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter"
          p:failureRangeInSeconds="60"
          p:failureThreshold="3"
          p:tableName="CAS_LOG">
        
        
    

同时修改WEB-INF/cas-servlet.xml,中



  
      
          
          
          
      
  

将登录过滤信息加入其中。

2.修改WEB-INF/deployerConfigContext.xml,中代码:

   


MyJdbcAuditTrailManager" >
    
    
    

   

3.创建对应的日志表。

建表语句,这是oracle的:

create table CAS_LOG
(
  aud_user      VARCHAR2(100) not null,
  aud_client_ip VARCHAR2(15) not null,
  aud_server_ip VARCHAR2(15) not null,
  aud_resource  VARCHAR2(100) not null,
  aud_action    VARCHAR2(100) not null,
  applic_cd     VARCHAR2(5) not null,
  aud_date      TIMESTAMP(6) not null
)
tablespace LOG
  pctfree 10
  initrans 1
  maxtrans 255
  storage
  (
    initial 64K
    next 1M
    minextents 1
    maxextents unlimited
  );
-- Add comments to the table 
comment on table CAS_LOG
  is 'cas 登录记录表';
-- Add comments to the columns 
comment on column CAS_LOG.aud_user
  is '审计用户';
comment on column CAS_LOG.aud_client_ip
  is '客户端ip';
comment on column CAS_LOG.aud_server_ip
  is '服务端ip';
comment on column CAS_LOG.aud_resource
  is '资源';
comment on column CAS_LOG.aud_action
  is '审计状态';
comment on column CAS_LOG.applic_cd
  is 'APPLIC_CD';
comment on column CAS_LOG.aud_date
  is '时间戳';
 

4.jar包中修改部分。

 说明:由于项目cas服务器还进行了返回更过信息的修改:登录cas本来只返回用户名,现在返回其他信息,包括角色、权限、菜单信息等,造成登录后用户日志用户名部分变成立了,audit:unknown;用户日志没办法确定用户信息,所有对JdbcAuditTrailManager(inspektr-support-spring-1.0.7.GA.jar)进行了修改(蓝色部分),如果你没有问题不需要修改。

修改好jar包下载地址:https://download.csdn.net/download/guohangfei001/11005804

需要修改2个jar包中内容,cas-server-webapp-support-4.0.0.jar、inspektr-support-spring-1.0.7.GA.jar,(没有说明部分问题不需要修改),修改说明

cas-server-webapp-support-4.0.0.jar中的

InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter修改为:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.jasig.cas.web.support;

import com.github.inspektr.audit.AuditActionContext;
import com.github.inspektr.audit.AuditPointRuntimeInfo;
import com.github.inspektr.audit.AuditTrailManager;
import com.github.inspektr.common.web.ClientInfo;
import com.github.inspektr.common.web.ClientInfoHolder;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

public class MyInspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter extends AbstractThrottledSubmissionHandlerInterceptorAdapter {
    private static final String DEFAULT_APPLICATION_CODE = "CAS";
    private static final String DEFAULT_AUTHN_FAILED_ACTION = "AUTHENTICATION_FAILED";
    private static final String INSPEKTR_ACTION = "THROTTLED_LOGIN_ATTEMPT";
    private final AuditTrailManager auditTrailManager;
    private final JdbcTemplate jdbcTemplate;
    private String applicationCode = "CAS";
    private String authenticationFailureCode = "AUTHENTICATION_FAILED";
    private String tableName = "CAS_LOG";

    public MyInspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter(AuditTrailManager auditTrailManager, DataSource dataSource) {
        this.auditTrailManager = auditTrailManager;
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

//这是执行cas登录判断的关键部分,返回true,跳转到异常提示页面,flase正常执行。其中有对用户ip的限制,还有时间的判断,具体的可以对比InspektrThrottledSubmissionByIpAddressAndUsernameHandlerInterceptorAdapter进行分析。

    protected boolean exceedsThreshold(HttpServletRequest request) {
        String query = "SELECT AUD_DATE FROM " + this.tableName + " WHERE AUD_CLIENT_IP = ? AND AUD_USER = ? " + "AND AUD_ACTION = ? AND APPLIC_CD = ? AND AUD_DATE >= ? ORDER BY AUD_DATE DESC";
        String userToUse = this.constructUsername(request, this.getUsernameParameter());
        Calendar cutoff = Calendar.getInstance();
        List failures = this.jdbcTemplate.query(query, new Object[]{request.getRemoteAddr(), userToUse, this.authenticationFailureCode, this.applicationCode, new Date(cutoff.getTimeInMillis() - (long)(this.getFailureRangeInSeconds() * 1000))}, new int[]{12, 12, 12, 12, 93}, new RowMapper() {
            public Timestamp mapRow(ResultSet resultSet, int i) throws SQLException {
                return resultSet.getTimestamp(1);
            }
        });
        return failures.size() >= this.getFailureThreshold();
    }

    protected void recordSubmissionFailure(HttpServletRequest request) {
    }

    protected void recordThrottle(HttpServletRequest request) {
        super.recordThrottle(request);
        String userToUse = this.constructUsername(request, this.getUsernameParameter());
        ClientInfo clientInfo = ClientInfoHolder.getClientInfo();
        AuditPointRuntimeInfo auditPointRuntimeInfo = new AuditPointRuntimeInfo() {
            private static final long serialVersionUID = 1L;

            public String asString() {
                return String.format("%s.recordThrottle()", this.getClass().getName());
            }
        };
        AuditActionContext context = new AuditActionContext(userToUse, userToUse, "THROTTLED_LOGIN_ATTEMPT", this.applicationCode, new Date(), clientInfo.getClientIpAddress(), clientInfo.getServerIpAddress(), auditPointRuntimeInfo);
        this.auditTrailManager.record(context);
    }

    public final void setApplicationCode(String applicationCode) {
        this.applicationCode = applicationCode;
    }

    public final void setAuthenticationFailureCode(String authenticationFailureCode) {
        this.authenticationFailureCode = authenticationFailureCode;
    }

    protected String constructUsername(HttpServletRequest request, String usernameParameter) {
        String username = request.getParameter(usernameParameter);
        return "[username: " + (username != null ? username : "") + "]";
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }
}
下面是对inspektr-support-spring-1.0.7.GA.jar,的修改(你可能不需要),日志审计实际上是开启了另一个线程进行插入日志:修改
JdbcAuditTrailManager为:MyJdbcAuditTrailManager:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package com.github.inspektr.audit.support;

import com.github.inspektr.audit.AuditActionContext;
import com.github.inspektr.audit.AuditTrailManager;
import com.github.inspektr.common.Cleanable;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.servlet.http.HttpServletRequest;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.jdbc.core.simple.SimpleJdbcDaoSupport;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;

public class MyJdbcAuditTrailManager extends SimpleJdbcDaoSupport implements AuditTrailManager, Cleanable, DisposableBean {
    private static final String INSERT_SQL_TEMPLATE = "INSERT INTO %s (AUD_USER, AUD_CLIENT_IP, AUD_SERVER_IP, AUD_RESOURCE, AUD_ACTION, APPLIC_CD, AUD_DATE) VALUES (?, ?, ?, ?, ?, ?, ?)";
    private static final String DELETE_SQL_TEMPLATE = "DELETE FROM %s %s";
    private static final int DEFAULT_COLUMN_LENGTH = 100;
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @NotNull
    private final TransactionTemplate transactionTemplate;
    @NotNull
    @Size(
        min = 1
    )
    private String tableName = "COM_AUDIT_TRAIL";
    @Min(50L)
    private int columnLength = 100;
    @NotNull
    private ExecutorService executorService = Executors.newSingleThreadExecutor();
    private boolean defaultExecutorService = true;
    private WhereClauseMatchCriteria cleanupCriteria = new NoMatchWhereClauseMatchCriteria();
    private HttpServletRequest httpServletRequest;

    public MyJdbcAuditTrailManager(TransactionTemplate transactionTemplate) {
        this.transactionTemplate = transactionTemplate;
    }

    public void record(AuditActionContext auditActionContext) {
        this.executorService.execute(new MyJdbcAuditTrailManager.LoggingTask(auditActionContext, this.transactionTemplate, this.columnLength));
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public void setCleanupCriteria(WhereClauseMatchCriteria criteria) {
        this.cleanupCriteria = criteria;
    }

    public void setExecutorService(ExecutorService executorService) {
        this.executorService = executorService;
        this.defaultExecutorService = false;
    }

    public void setColumnLength(int columnLength) {
        this.columnLength = columnLength;
    }

    public void destroy() throws Exception {
        if (this.defaultExecutorService) {
            this.executorService.shutdown();
        }

    }

    public void clean() {
        this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                String sql = String.format("DELETE FROM %s %s", MyJdbcAuditTrailManager.this.tableName, MyJdbcAuditTrailManager.this.cleanupCriteria);
                List params = MyJdbcAuditTrailManager.this.cleanupCriteria.getParameterValues();
                MyJdbcAuditTrailManager.this.logger.info("Cleaning audit records with query " + sql);
                MyJdbcAuditTrailManager.this.logger.debug("Query parameters: " + params);
                int count = MyJdbcAuditTrailManager.this.getSimpleJdbcTemplate().update(sql, params.toArray());
                MyJdbcAuditTrailManager.this.logger.info(count + " records deleted.");
            }
        });
    }

    protected class LoggingTask implements Runnable {
        private final AuditActionContext auditActionContext;
        private final TransactionTemplate transactionTemplate;
        private final int columnLength;

        public LoggingTask(AuditActionContext auditActionContext, TransactionTemplate transactionTemplate, int columnLength) {
            this.auditActionContext = auditActionContext;
            this.transactionTemplate = transactionTemplate;
            this.columnLength = columnLength;
        }

        public void run() {
            this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    String userId_L = LoggingTask.this.auditActionContext.getResourceOperatedUpon().trim();
                    String userId = userId_L.length() < 23 ? "username" : "[username: " + userId_L.substring(23, userId_L.length() - 10) + "]";
                    String resource = LoggingTask.this.auditActionContext.getResourceOperatedUpon().length() <= LoggingTask.this.columnLength ? LoggingTask.this.auditActionContext.getResourceOperatedUpon() : LoggingTask.this.auditActionContext.getResourceOperatedUpon().substring(0, LoggingTask.this.columnLength);
                    String action = LoggingTask.this.auditActionContext.getActionPerformed().length() <= LoggingTask.this.columnLength ? LoggingTask.this.auditActionContext.getActionPerformed() : LoggingTask.this.auditActionContext.getActionPerformed().substring(0, LoggingTask.this.columnLength);
                    MyJdbcAuditTrailManager.this.getSimpleJdbcTemplate().update(String.format("INSERT INTO %s (AUD_USER, AUD_CLIENT_IP, AUD_SERVER_IP, AUD_RESOURCE, AUD_ACTION, APPLIC_CD, AUD_DATE) VALUES (?, ?, ?, ?, ?, ?, ?)", MyJdbcAuditTrailManager.this.tableName), new Object[]{userId, LoggingTask.this.auditActionContext.getClientIpAddress(), LoggingTask.this.auditActionContext.getServerIpAddress(), resource, action, LoggingTask.this.auditActionContext.getApplicationCode(), LoggingTask.this.auditActionContext.getWhenActionWasPerformed()});
                }
            });
        }
    }
}

红色部分是查入日志的关键信息部分,userid就是AUD_USER字段,我修改成了[username: ghf]。问题也出现在这里,登录失败的时候可以获得用户名ghf,但是成功的时候变成了一串长字符串,所有log信息变成了一下图片,不过用来判断登录没有问题了

5.总结

现在已经实现登录次数、时间,根据ip地址、用户信息进行限制了,问题是时间的限制部分其实不是很完善,需要优化。

你可能感兴趣的:(java,orcal)