上个礼拜搞安全问题,搞了几天,大部分是关于登录、账密、权限问题,现在做一个简单的总结
一,CAS
我这里是要在CAS里面做一个‘密码错误5次就锁定30分钟’。因为时刚拿到CAS,不太熟悉。问了一下同事,同事说好像CAS支持配置,不过,
在网上看了一下,好像是有配置,分单节点,和多节点。但是%#%¥¥……%*&……,然后我自己是实现。
一般来说,做这种需求,需要在数据库新增字段,例如‘最后登录时间’-->用来与锁定的时间对比,还有‘是否锁定’-->isLock
我所知道的是,CAS时直接用jdbcTemplate操作数据库的,在QueryDatabaseAuthenticationHandler 这里根据用户名查到密码(dbPassword),然后返回boolean值
protected final boolean authenticateUsernamePasswordInternal(final UsernamePasswordCredentials credentials) throws AuthenticationException { final String username = getPrincipalNameTransformer().transform(credentials.getUsername()); final String password = credentials.getPassword(); final String encryptedPassword = this.getPasswordEncoder().encode( password); try { final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username); return dbPassword.equals(encryptedPassword); } catch (final IncorrectResultSizeDataAccessException e) { // this means the username was not found. return false; } }
也可以在这里查其他字段,因为是根据sql直接查的,所有要在deployerConfigContext.xml里面配一下sql
<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"> <property name="dataSource" ref="dataSource" /> <property name="sql" value="select password from p_user where username=?" />
//需要对象的话,先封装一个对象(但我还不知道CAS是咋映射到数据库的) <property name="passwordEncoder" ref="passwordEncoderBean"/> </bean>
如果使用数据库做这个需求的话,可以参考上面的
如果是用缓存做的话就简单多了,贴代码
protected Pair<AuthenticationHandler, Principal> authenticateAndObtainPrincipal(final Credentials credentials) throws AuthenticationException { boolean foundSupported = false; boolean authenticated = false; AuthenticationHandler authenticatedClass = null; String handlerName; for (final AuthenticationHandler authenticationHandler : this.authenticationHandlers) { if (authenticationHandler.supports(credentials)) { foundSupported = true; handlerName = authenticationHandler.getClass().getName(); try { String username = ((UsernamePasswordCredentials) credentials).getUsername(); ValueOperations<String, String> valueOperations = redisTemplate.opsForValue(); // reached the threshold--->warning String loginFailTimesKey = LOGIN_FAIL_TIMES + username; //loginFailTimesKey > 5,loginFailFlagKey才有值 String loginFailFlagKey = LOGIN_FAIL_FLAG + username; String failFlag = valueOperations.get(loginFailFlagKey); if (StringUtils.isNotEmpty(failFlag)) { throw OutBindAuthenticationException.ERROR; } //!authenticationHandler.authenticate(credentials)去数据库校验用戶密碼 if (!authenticationHandler.authenticate(credentials)) { //登录校验用户名密码失败 log.info("{} failed to authenticate {}", handlerName, credentials); // redis operation 记录密码出错次数 if (valueOperations.get(loginFailTimesKey) == null) { long milliSecondsLeftToday = 86400000 - DateUtils.getFragmentInMilliseconds(Calendar.getInstance(), Calendar.DATE); //首次出错,记录为1 valueOperations.set(loginFailTimesKey, "1", milliSecondsLeftToday, TimeUnit.MILLISECONDS); } else { //非首次出错,次数+1 valueOperations.increment(loginFailTimesKey, 1); } //出错次数>5次 ,锁定30分钟 if (Long.valueOf(valueOperations.get(loginFailTimesKey)) >= 5) { valueOperations.set(loginFailFlagKey, "1", 30, TimeUnit.MINUTES); } } else { //登录成功 log.info("{} successfully authenticated {}", handlerName, credentials); authenticatedClass = authenticationHandler; authenticated = true; //清除密碼錯誤次數緩存 redisTemplate.delete(loginFailTimesKey); break; } } catch (final Exception e) { handleError(handlerName, credentials, e); } } }
casLoginView 这是前端页面,然后提交form表单数据到:AuthenticationViaFormAction,这里的submit方法是校验入口,然后再到QueryDatabaseAuthenticationHandler, 这个是从数据库拿值判断,然后再到AuthenticationManagerImpl 返回到这里,所以最终实现是在该类的authenticateAndObtainPrincipal方法里面,代码就在上面。
这里要提一下,我这个需求如果用户错5次,不允许其登录了,但是也要给个提示信息啊,所以这里我自定义了一个异常类OutBindAuthenticationException,如果用户错5次,
抛出该异常 throw OutBindAuthenticationException.ERROR;这里面的CODE,要在messages_zh_CN.properties里面定义,是ascII码。OK就这样