|
背景:在Jira的生产环境中,由于各种原因,不能直接设置密码策略(比如,用户来自LDAP),存在不少用户的密码设置过于简单,容易导致机器人的攻击,带来安全隐患。另外,由于历史诸如Remote API调用的原因,Jira也不提供登录页面直接提供验证码的功能,这使得安全隐患进一步暴露。
本文尝试对Jira源码进行一定的修改,使得用户首次登录时强行出现验证码。当然也可以通过扩展Jira的安全验证接口,通过插件的形式发布Plugin。
在购买Jira产品后,厂家会有一份对应的Jira源码,有关安全登录的有两个类,分别是LoginInfoImpl.java,LoginServiceImpl.java
1,修改LoginInfoImpl.java的构造函数
public LoginInfoImpl(final Long lastLoginTime, final Long previousLoginTime, final Long lastFailedLoginTime, final Long loginCount,
final Long currentFailedLoginCount, final Long totalFailedLoginCount, final Long maxAuthenticationAttemptsAllowed, final boolean elevatedSecurityCheckRequired)
{
this.lastLoginTime = lastLoginTime;
this.previousLoginTime = previousLoginTime;
this.lastFailedLoginTime = lastFailedLoginTime;
this.loginCount = loginCount;
this.currentFailedLoginCount = currentFailedLoginCount;
this.totalFailedLoginCount = totalFailedLoginCount;
this.maxAuthenticationAttemptsAllowed = maxAuthenticationAttemptsAllowed;
this.elevatedSecurityCheckRequired = true;//elevatedSecurityCheckRequired;
}
完整代码:
package com.atlassian.jira.bc.security.login;
import org.apache.commons.lang.builder.ToStringBuilder;
/**
* @since v4.0.1
*/
public class LoginInfoImpl implements LoginInfo
{
private final Long lastLoginTime;
private final Long previousLoginTime;
private final Long loginCount;
private final Long currentFailedLoginCount;
private final Long totalFailedLoginCount;
private final Long lastFailedLoginTime;
private final boolean elevatedSecurityCheckRequired;
private final Long maxAuthenticationAttemptsAllowed;
public LoginInfoImpl(final Long lastLoginTime, final Long previousLoginTime, final Long lastFailedLoginTime, final Long loginCount,
final Long currentFailedLoginCount, final Long totalFailedLoginCount, final Long maxAuthenticationAttemptsAllowed, final boolean elevatedSecurityCheckRequired)
{
this.lastLoginTime = lastLoginTime;
this.previousLoginTime = previousLoginTime;
this.lastFailedLoginTime = lastFailedLoginTime;
this.loginCount = loginCount;
this.currentFailedLoginCount = currentFailedLoginCount;
this.totalFailedLoginCount = totalFailedLoginCount;
this.maxAuthenticationAttemptsAllowed = maxAuthenticationAttemptsAllowed;
this.elevatedSecurityCheckRequired = true;//elevatedSecurityCheckRequired;
}
public LoginInfoImpl(final LoginInfo loginInfo, final boolean elevatedSecurityCheckRequired)
{
this(loginInfo.getLastLoginTime(), loginInfo.getPreviousLoginTime(), loginInfo.getLastFailedLoginTime(), loginInfo.getLoginCount(),
loginInfo.getCurrentFailedLoginCount(), loginInfo.getTotalFailedLoginCount(), loginInfo.getMaxAuthenticationAttemptsAllowed(), elevatedSecurityCheckRequired);
}
public Long getMaxAuthenticationAttemptsAllowed()
{
return maxAuthenticationAttemptsAllowed;
}
public Long getLastLoginTime()
{
return lastLoginTime;
}
public Long getPreviousLoginTime()
{
return previousLoginTime;
}
public Long getLoginCount()
{
return loginCount;
}
public Long getCurrentFailedLoginCount()
{
return currentFailedLoginCount;
}
public Long getTotalFailedLoginCount()
{
return totalFailedLoginCount;
}
public Long getLastFailedLoginTime()
{
return lastFailedLoginTime;
}
public boolean isElevatedSecurityCheckRequired()
{
System.out.println("===replace isElevatedSecurityCheckRequired");
return elevatedSecurityCheckRequired;
}
@Override
public String toString()
{
return ToStringBuilder.reflectionToString(this);
}
}
2、修改LoginServiceImpl.java的isElevatedSecurityCheckAlwaysShown()方法
public boolean isElevatedSecurityCheckAlwaysShown()
{
//return loginManager.isElevatedSecurityCheckAlwaysShown();
return true;
}
package com.atlassian.jira.bc.security.login;
import com.atlassian.crowd.embedded.api.User;
import com.atlassian.jira.config.properties.APKeys;
import com.atlassian.jira.config.properties.ApplicationProperties;
import com.atlassian.jira.security.JiraAuthenticationContext;
import com.atlassian.jira.security.login.LoginManager;
import com.atlassian.jira.user.util.UserManager;
import com.atlassian.jira.util.JiraContactHelper;
import com.atlassian.jira.util.JiraUtils;
import com.atlassian.jira.util.http.JiraUrl;
import com.atlassian.seraph.auth.AuthenticationErrorType;
import com.atlassian.seraph.filter.BaseLoginFilter;
import com.atlassian.seraph.filter.LoginFilterRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import static com.atlassian.jira.util.dbc.Assertions.notNull;
/**
* Implementation of LoginManager
*
* @since v4.0.1
*/
public class LoginServiceImpl implements LoginService
{
private final LoginManager loginManager;
private final ApplicationProperties applicationProperties;
private final UserManager userManager;
private final JiraContactHelper contactHelper;
private final JiraAuthenticationContext authenticationContext;
public LoginServiceImpl(LoginManager loginManager, ApplicationProperties applicationProperties,
UserManager userManager, JiraContactHelper contactHelper, JiraAuthenticationContext authenticationContext)
{
this.contactHelper = contactHelper;
this.authenticationContext = authenticationContext;
this.applicationProperties = notNull("applicationProperties", applicationProperties);
this.loginManager = notNull("loginManager", loginManager);
this.userManager = notNull("userManager", userManager);
}
public LoginInfo getLoginInfo(final String userName)
{
return loginManager.getLoginInfo(userName);
}
public boolean isElevatedSecurityCheckAlwaysShown()
{
//return loginManager.isElevatedSecurityCheckAlwaysShown();
return true;
}
public void resetFailedLoginCount(final User user)
{
loginManager.resetFailedLoginCount(user);
}
public LoginResult authenticate(final User user, final String password)
{
return loginManager.authenticate(user, password);
}
public void logout(final HttpServletRequest request, final HttpServletResponse response)
{
loginManager.logout(request, response);
}
public LoginProperties getLoginProperties(final User remoteUser, final HttpServletRequest request)
{
notNull("request", request);
// see loginform.jsp for example of how this needs to work
final LoginResult lastLoginResult = (LoginResult) request.getAttribute(LoginService.LOGIN_RESULT);
final LoginInfo loginInfo = lastLoginResult == null ? null : lastLoginResult.getLoginInfo();
final boolean loginSucceeded = remoteUser != null;
final boolean loginError = BaseLoginFilter.LOGIN_ERROR.equals(LoginFilterRequest.getAuthenticationStatus(request));
final boolean communicationError = AuthenticationErrorType.CommunicationError.equals(LoginFilterRequest.getAuthenticationErrorType(request));
final boolean captchaFailure = (lastLoginResult != null && lastLoginResult.getReason() == LoginReason.AUTHENTICATION_DENIED);
final boolean loginFailedCausedByPermissions = (lastLoginResult != null && lastLoginResult.getReason() == LoginReason.AUTHORISATION_FAILED);
final boolean isElevatedSecurityCheckShown = isElevatedSecurityCheckShown(loginInfo);
if (LoginLoggers.LOGIN_GADGET_LOG.isDebugEnabled())
{
LoginLoggers.LOGIN_GADGET_LOG.debug("Gadget login called with lastLoginResult : " + String.valueOf(lastLoginResult));
}
return LoginProperties.builder().
loginSucceeded(loginSucceeded).
loginError(loginError).
communicationError(communicationError).
allowCookies(applicationProperties.getOption(APKeys.JIRA_OPTION_ALLOW_COOKIES)).
externalPasswordManagement(!userManager.hasPasswordWritableDirectory()).
externalUserManagement(applicationProperties.getOption(APKeys.JIRA_OPTION_USER_EXTERNALMGT)).
isPublicMode(isPublicMode()).
isElevatedSecurityCheckShown(isElevatedSecurityCheckShown).
captchaFailure(captchaFailure).
loginFailedByPermissions(loginFailedCausedByPermissions).
contactAdminLink(getContactLink(request)).
build();
}
private String getContactLink(HttpServletRequest request)
{
return contactHelper.getAdministratorContactLinkHtml(JiraUrl.constructBaseUrl(request), authenticationContext.getI18nHelper());
}
boolean isPublicMode()
{
return JiraUtils.isPublicMode();
}
private boolean isElevatedSecurityCheckShown(final LoginInfo loginInfo)
{
return isElevatedSecurityCheckAlwaysShown() || (loginInfo != null && loginInfo.isElevatedSecurityCheckRequired());
}
}
3、在Maven的pom.xml中引入Atlassian Jira 依赖,并进行编译
<dependency>
<groupId>com.atlassian.jira</groupId>
<artifactId>jira-api</artifactId>
<version>${jira.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.atlassian.jira</groupId>
<artifactId>jira-core</artifactId>
<version>${jira.version}</version>
<scope>provided</scope>
</dependency>
<properties>
<jira.version>6.3.14</jira.version>
<spring.version>2.5.6</spring.version>
<amps.version>5.0.13</amps.version>
<spring.osgi.version>1.2.1</spring.osgi.version>
<plugin.testrunner.version>1.2.3</plugin.testrunner.version>
<!-- TestKit version 5.x for JIRA 5.x, 6.x for JIRA 6.x -->
<testkit.version>5.2.26</testkit.version>
</properties>
4、把编译的class文件拷贝到atlassian-jira-6.3.15-standalone/atlassian-jira/WEB-INF/classes/com/atlassian/jira/bc/security/login,并重启Jira
|