应该从web.xml中的login-config说起,他指明了用哪种认证,这个只有配一个,如果多个的话以后面的为准,该配置文件会被digester解析,login-config的配置会生成一个loginConfig对象放到webxml对象中成为一个属性,并通过configureContext()方法,使得loginConfig与context相联系
ContextConfig.configureStart()->authenticatorConfig()方法
authenticatorConfig() {
LoginConfig loginConfig = context.getLoginConfig();
SecurityConstraint constraints[] = context.findConstraints();
if (context.getIgnoreAnnotations() &&
(constraints == null || constraints.length ==0) &&
!context.getPreemptiveAuthentication()) {
return;
} else {
if (loginConfig == null) {
// Not metadata-complete or security constraints present, need
// an authenticator to support @ServletSecurity annotations
// and/or constraints
loginConfig = DUMMY_LOGIN_CONFIG;
context.setLoginConfig(loginConfig);
}
}
// Has an authenticator been configured already?
if (context.getAuthenticator() != null)
return;
if (!(context instanceof ContainerBase)) {
return; // Cannot install a Valve even if it would be needed
}
// Has a Realm been configured for us to authenticate against?
if (context.getRealm() == null) {
log.error(sm.getString("contextConfig.missingRealm"));
ok = false;
return;
}
/*
* First check to see if there is a custom mapping for the login
* method. If so, use it. Otherwise, check if there is a mapping in
* org/apache/catalina/startup/Authenticators.properties.
*/
Valve authenticator = null;
if (customAuthenticators != null) {
authenticator = (Valve)
customAuthenticators.get(loginConfig.getAuthMethod());
}
if (authenticator == null) {
if (authenticators == null) {
log.error(sm.getString("contextConfig.authenticatorResources"));
ok = false;
return;
}
// Identify the class name of the Valve we should configure
String authenticatorName = null;
authenticatorName =
authenticators.getProperty(loginConfig.getAuthMethod());//根据loginConfig配置的方法名获取authenticatorName,这个authenticators是从ContextConfig的静态块中获得的,具体是读取了org/apache/catalina/startup/Authenticators.properties文件获得的
if (authenticatorName == null) {
log.error(sm.getString("contextConfig.authenticatorMissing",
loginConfig.getAuthMethod()));
ok = false;
return;
}
// Instantiate and install an Authenticator of the requested class
try {
Class> authenticatorClass = Class.forName(authenticatorName);//根据名字用反射创建认证器的实例
authenticator = (Valve) authenticatorClass.newInstance();
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString(
"contextConfig.authenticatorInstantiate",
authenticatorName),
t);
ok = false;
}
}
if (authenticator != null && context instanceof ContainerBase) {
Pipeline pipeline = ((ContainerBase) context).getPipeline();
if (pipeline != null) {
((ContainerBase) context).getPipeline().addValve(authenticator);//将认证器加入pipeline
if (log.isDebugEnabled()) {
log.debug(sm.getString(
"contextConfig.authenticatorConfigured",
loginConfig.getAuthMethod()));
}
}
}
}
当程序执行到context的pipeline中的valve时,AuthenticatorBase的invoke会被调用
public void invoke(Request request, Response response)
throws IOException, ServletException {
if (log.isDebugEnabled())
log.debug("Security checking request " +
request.getMethod() + " " + request.getRequestURI());
LoginConfig config = this.context.getLoginConfig();
// Have we got a cached authenticated Principal to record?
if (cache) {
Principal principal = request.getUserPrincipal();
if (principal == null) {
Session session = request.getSessionInternal(false);
if (session != null) {
principal = session.getPrincipal();
if (principal != null) {
if (log.isDebugEnabled())
log.debug("We have cached auth type " +
session.getAuthType() +
" for principal " +
session.getPrincipal());
request.setAuthType(session.getAuthType());
request.setUserPrincipal(principal);
}
}
}
}
// Special handling for form-based logins to deal with the case
// where the login form (and therefore the "j_security_check" URI
// to which it submits) might be outside the secured area
String contextPath = this.context.getPath();
String requestURI = request.getDecodedRequestURI();
if (requestURI.startsWith(contextPath) &&
requestURI.endsWith(Constants.FORM_ACTION)) {//form认证相关
if (!authenticate(request, response, config)) {
if (log.isDebugEnabled())
log.debug(" Failed authenticate() test ??" + requestURI );
return;
}
}
// Special handling for form-based logins to deal with the case where
// a resource is protected for some HTTP methods but not protected for
// GET which is used after authentication when redirecting to the
// protected resource.
// TODO: This is similar to the FormAuthenticator.matchRequest() logic
// Is there a way to remove the duplication?
Session session = request.getSessionInternal(false);
if (session != null) {
SavedRequest savedRequest =
(SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE);//form认证相关
if (savedRequest != null) {
String decodedRequestURI = request.getDecodedRequestURI();
if (decodedRequestURI != null &&
decodedRequestURI.equals(
savedRequest.getDecodedRequestURI())) {
if (!authenticate(request, response)) {
if (log.isDebugEnabled()) {
log.debug(" Failed authenticate() test");
}
/*
* ASSERT: Authenticator already set the appropriate
* HTTP status code, so we do not have to do anything
* special
*/
return;
}
}
}
}
// The Servlet may specify security constraints through annotations.
// Ensure that they have been processed before constraints are checked
Wrapper wrapper = (Wrapper) request.getMappingData().wrapper;
if (wrapper != null) {
wrapper.servletSecurityAnnotationScan();
}
Realm realm = this.context.getRealm();
// Is this request URI subject to a security constraint?
SecurityConstraint [] constraints
= realm.findSecurityConstraints(request, this.context);//查找跟这个request相关的SecurityConstraints,这块我们会在web.xml中进行了配置,里配置了目录和相关的访问方法,存储在standardcontext对象中的constraints属性中,在这里会用request里的信息比如要访问的路径和方法(post or ...)与constraints中的路径比较,如果一样就认为该request有安全限制,需要进行后续的auth
if (constraints == null && !context.getPreemptiveAuthentication()) {
if (log.isDebugEnabled())
log.debug(" Not subject to any constraint");
getNext().invoke(request, response);
return;
}
// Make sure that constrained resources are not cached by web proxies
// or browsers as caching can provide a security hole
if (constraints != null && disableProxyCaching &&
!"POST".equalsIgnoreCase(request.getMethod())) {
if (securePagesWithPragma) {
// Note: These can cause problems with downloading files with IE
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
} else {
response.setHeader("Cache-Control", "private");
}
response.setHeader("Expires", DATE_ONE);
}
int i;
if (constraints != null) {
// Enforce any user data constraint for this security constraint
if (log.isDebugEnabled()) {
log.debug(" Calling hasUserDataPermission()");
}
if (!realm.hasUserDataPermission(request, response,
constraints)) {这里是验证request访问的数据是否有安全限制,在web.xml有相关配置(/security-constraint/user-data-constraint/transport-guarantee),如果配置的值不是NONE或者根本就没配置,那说明访问的数据需要加密,如果该访问是不https,会自动重定向https,重定向的端口是server.xml中connector中的属性redirectPort的值
if (log.isDebugEnabled()) {
log.debug(" Failed hasUserDataPermission() test");
}
/*
* ASSERT: Authenticator already set the appropriate
* HTTP status code, so we do not have to do anything special
*/
return;
}
}
// Since authenticate modifies the response on failure,
// we have to check for allow-from-all first.
boolean authRequired;
if (constraints == null) {
authRequired = false;
} else {
authRequired = true;
for(i=0; i < constraints.length && authRequired; i++) {
if(!constraints[i].getAuthConstraint()) {
authRequired = false;
} else if(!constraints[i].getAllRoles()) {
String [] roles = constraints[i].findAuthRoles();
if(roles == null || roles.length == 0) {
authRequired = false;
}
}
}
}
if (!authRequired && context.getPreemptiveAuthentication()) {
authRequired =
request.getCoyoteRequest().getMimeHeaders().getValue(
"authorization") != null;
}
if (!authRequired && context.getPreemptiveAuthentication() &&
HttpServletRequest.CLIENT_CERT_AUTH.equals(getAuthMethod())) {
X509Certificate[] certs = getRequestCertificates(request);
authRequired = certs != null && certs.length > 0;
}
if(authRequired) {
if (log.isDebugEnabled()) {
log.debug(" Calling authenticate()");
}
if (!authenticate(request, response, config)) {//这里调用AuthenticatorBase实现类的authenticate,比如BasicAuthenticator的authenticate
if (log.isDebugEnabled()) {
log.debug(" Failed authenticate() test");
}
/*
* ASSERT: Authenticator already set the appropriate
* HTTP status code, so we do not have to do anything
* special
*/
return;
}
}
if (constraints != null) {
if (log.isDebugEnabled()) {
log.debug(" Calling accessControl()");
}
if (!realm.hasResourcePermission(request, response,
constraints,
this.context)) {//这个是检查要访问的资源的用户角色是否满足之前配置的要求,该配置在web.xml中auth-constraint/role-name中配置的
if (log.isDebugEnabled()) {
log.debug(" Failed accessControl() test");
}
/*
* ASSERT: AccessControl method has already set the
* appropriate HTTP status code, so we do not have to do
* anything special
*/
return;
}
}
// Any and all specified constraints have been satisfied
if (log.isDebugEnabled()) {
log.debug(" Successfully passed all security constraints");
}
getNext().invoke(request, response);
}
简单说说server.xml中的realm,他就是存放用户密码等验证时用到的信息的对象,它可以添加到任意级别的container,并对所有下级container生效
只有代码中的principal我觉得应该叫他当事人,他就是经过认证的用户的信息,存在request和session中,formed验证应该会用到
web.xml中相关配置:
login-config中定义http认证方法
定义realm名字,感觉随便起就行
中说明需要加方法限制的路径,其实还可以配置访问这个路径的方法http-method,如果没配,就是所有方法都被限制,也可以配置http-method-omission,表示不受限制的方法,这个和http-method不能同时配,如果同时配,http-method优先
说明能够访问那个路径的角色
这里也定义了角色,是当中定义为*号时,会是使用这里定义的角色