提交 3922bb23 编写于 作者: K kohsuke

SecurityRealms can now better control the servlet filter chain.

    (<a href="http://www.nabble.com/proposed-patch-to-expose-filters-through-SecurityRealms-tt21062397.html">report</a>)


git-svn-id: https://hudson.dev.java.net/svn/hudson/trunk/hudson/main@14203 71c3de6d-444a-0410-be80-ed276b4c234a
上级 c9df23cb
...@@ -46,8 +46,6 @@ import hudson.security.Permission; ...@@ -46,8 +46,6 @@ import hudson.security.Permission;
import hudson.security.PermissionGroup; import hudson.security.PermissionGroup;
import hudson.security.SecurityMode; import hudson.security.SecurityMode;
import hudson.security.SecurityRealm; import hudson.security.SecurityRealm;
import hudson.security.SecurityRealm.SecurityComponents;
import hudson.security.TokenBasedRememberMeServices2;
import hudson.slaves.ComputerListener; import hudson.slaves.ComputerListener;
import hudson.slaves.RetentionStrategy; import hudson.slaves.RetentionStrategy;
import hudson.tasks.BuildStep; import hudson.tasks.BuildStep;
...@@ -75,10 +73,7 @@ import hudson.util.XStream2; ...@@ -75,10 +73,7 @@ import hudson.util.XStream2;
import hudson.util.HudsonIsRestarting; import hudson.util.HudsonIsRestarting;
import hudson.widgets.Widget; import hudson.widgets.Widget;
import net.sf.json.JSONObject; import net.sf.json.JSONObject;
import org.acegisecurity.AccessDeniedException; import org.acegisecurity.*;
import org.acegisecurity.Authentication;
import org.acegisecurity.GrantedAuthority;
import org.acegisecurity.GrantedAuthorityImpl;
import org.acegisecurity.context.SecurityContextHolder; import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken; import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.acegisecurity.ui.AbstractProcessingFilter; import org.acegisecurity.ui.AbstractProcessingFilter;
...@@ -427,13 +422,6 @@ public final class Hudson extends AbstractModelObject implements ItemGroup<TopLe ...@@ -427,13 +422,6 @@ public final class Hudson extends AbstractModelObject implements ItemGroup<TopLe
for (ItemListener l : itemListeners) for (ItemListener l : itemListeners)
l.onLoaded(); l.onLoaded();
// create TokenBasedRememberMeServices, which depends on the availability of the secret key
TokenBasedRememberMeServices2 rms = new TokenBasedRememberMeServices2();
rms.setUserDetailsService(HudsonFilter.USER_DETAILS_SERVICE_PROXY);
rms.setKey(getSecretKey());
rms.setParameter("remember_me"); // this is the form field name in login.jelly
HudsonFilter.REMEMBER_ME_SERVICES_PROXY.setDelegate(rms);
WindowsInstallerLink.registerIfApplicable(); WindowsInstallerLink.registerIfApplicable();
UsageStatistics.register(); UsageStatistics.register();
} }
...@@ -1191,9 +1179,13 @@ public final class Hudson extends AbstractModelObject implements ItemGroup<TopLe ...@@ -1191,9 +1179,13 @@ public final class Hudson extends AbstractModelObject implements ItemGroup<TopLe
if(securityRealm==null) if(securityRealm==null)
securityRealm= SecurityRealm.NO_AUTHENTICATION; securityRealm= SecurityRealm.NO_AUTHENTICATION;
this.securityRealm = securityRealm; this.securityRealm = securityRealm;
SecurityComponents sc = securityRealm.createSecurityComponents(); // reset the filters and proxies for the new SecurityRealm
HudsonFilter.AUTHENTICATION_MANAGER.setDelegate(sc.manager); try {
HudsonFilter.USER_DETAILS_SERVICE_PROXY.setDelegate(sc.userDetails); HudsonFilter.get(servletContext).reset(securityRealm);
} catch (ServletException e) {
// for binary compatibility, this method cannot throw a checked exception
throw new AcegiSecurityException("Failed to configure filter",e) {};
}
} }
public Lifecycle getLifecycle() { public Lifecycle getLifecycle() {
......
package hudson.security; package hudson.security;
import groovy.lang.Binding; import javax.servlet.*;
import hudson.model.Hudson; import java.io.IOException;
import hudson.util.spring.BeanBuilder;
import org.acegisecurity.AuthenticationManager; import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.ui.rememberme.RememberMeServices;
import org.acegisecurity.userdetails.UserDetailsService; import org.acegisecurity.userdetails.UserDetailsService;
import org.springframework.web.context.WebApplicationContext;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/** /**
* {@link Filter} that Hudson uses to implement security support. * {@link Filter} that Hudson uses to implement security support.
* *
* <p> * <p>
* This is the instance the servlet container creates, but * This is the instance the servlet container creates, but
* internally this is just a dispatcher that delegates the request * internally this just acts as a proxy to the real {@link Filter},
* to the appropriate filter pipeline based on the current * created by {@link SecurityRealm#createFilter(FilterConfig)}.
* configuration.
* *
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
* @since 1.160 * @since 1.160
*/ */
public class HudsonFilter implements Filter { public class HudsonFilter implements Filter {
/** /**
* To be used with {@link SecurityMode#LEGACY}. * The SecurityRealm specific filter.
*/ */
private Filter legacy; private volatile Filter filter;
/** /**
* To be used with {@link SecurityMode#SECURED}. * The {@link #init(FilterConfig)} may be called before the Hudson instance is up (which is
* required for initialization of the filter). So we store the
* filterConfig for later lazy-initialization of the filter.
*/ */
private Filter acegi; private FilterConfig filterConfig;
/** /**
* {@link AuthenticationManager} proxy so that the acegi filter chain can stay the same * {@link AuthenticationManager} proxy so that the acegi filter chain can stay the same
* even when security setting is reconfigured. * even when security setting is reconfigured.
*
* @deprecated in 1.271.
* This proxy always delegate to {@code Hudson.getInstance().getSecurityRealm().getSecurityComponents().manager},
* so use that instead.
*/ */
public static final AuthenticationManagerProxy AUTHENTICATION_MANAGER = new AuthenticationManagerProxy(); public static final AuthenticationManagerProxy AUTHENTICATION_MANAGER = new AuthenticationManagerProxy();
/** /**
* {@link UserDetailsService} proxy so that the acegi filter chain can stay the same * {@link UserDetailsService} proxy so that the acegi filter chain can stay the same
* even when security setting is reconfigured. * even when security setting is reconfigured.
*
* @deprecated in 1.271.
* This proxy always delegate to {@code Hudson.getInstance().getSecurityRealm().getSecurityComponents().userDetails},
* so use that instead.
*/ */
public static final UserDetailsServiceProxy USER_DETAILS_SERVICE_PROXY = new UserDetailsServiceProxy(); public static final UserDetailsServiceProxy USER_DETAILS_SERVICE_PROXY = new UserDetailsServiceProxy();
/**
* {@link RememberMeServices} proxy so that the acegi filter chain can stay the same
* even when security setting is reconfigured.
*
* @deprecated in 1.271.
* This proxy always delegate to {@code Hudson.getInstance().getSecurityRealm().getSecurityComponents().rememberMe},
* so use that instead.
*/
public static final RememberMeServicesProxy REMEMBER_ME_SERVICES_PROXY = new RememberMeServicesProxy(); public static final RememberMeServicesProxy REMEMBER_ME_SERVICES_PROXY = new RememberMeServicesProxy();
public void init(FilterConfig filterConfig) throws ServletException { public void init(FilterConfig filterConfig) throws ServletException {
Binding binding = new Binding(); this.filterConfig = filterConfig;
binding.setVariable("authenticationManagerProxy", AUTHENTICATION_MANAGER); // this is how we make us available to the rest of Hudson.
binding.setVariable("userDetailsServiceProxy", USER_DETAILS_SERVICE_PROXY); filterConfig.getServletContext().setAttribute(HudsonFilter.class.getName(),this);
binding.setVariable("rememberMeServicesProxy", REMEMBER_ME_SERVICES_PROXY); }
// on some containers this is not ready yet
// binding.setVariable("app", Hudson.getInstance());
BeanBuilder builder = new BeanBuilder();
builder.parse(filterConfig.getServletContext().getResourceAsStream("/WEB-INF/security/SecurityFilters.groovy"),binding);
WebApplicationContext context = builder.createApplicationContext(); /**
* Gets the {@link HudsonFilter} created for the given {@link ServletContext}.
acegi = (Filter) context.getBean("filter"); */
acegi.init(filterConfig); public static HudsonFilter get(ServletContext context) {
return (HudsonFilter)context.getAttribute(HudsonFilter.class.getName());
}
legacy = (Filter) context.getBean("legacy"); /**
legacy.init(filterConfig); * Reset the proxies and filter for a change in {@link SecurityRealm}.
*/
public void reset(SecurityRealm securityRealm) throws ServletException {
if (securityRealm != null) {
SecurityRealm.SecurityComponents sc = securityRealm.getSecurityComponents();
AUTHENTICATION_MANAGER.setDelegate(sc.manager);
USER_DETAILS_SERVICE_PROXY.setDelegate(sc.userDetails);
REMEMBER_ME_SERVICES_PROXY.setDelegate(sc.rememberMe);
// make sure this.filter is always a valid filter.
Filter oldf = this.filter;
Filter newf = securityRealm.createFilter(this.filterConfig);
newf.init(this.filterConfig);
this.filter = newf;
oldf.destroy();
} else {
// no security related filter needed.
AUTHENTICATION_MANAGER.setDelegate(null);
USER_DETAILS_SERVICE_PROXY.setDelegate(null);
REMEMBER_ME_SERVICES_PROXY.setDelegate(null);
filter = null;
}
} }
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Hudson h = Hudson.getInstance(); // to deal with concurrency, we need to capture the object.
if(h==null) { Filter f = filter;
if(f==null) {
// Hudson is starting up. // Hudson is starting up.
chain.doFilter(request,response); chain.doFilter(request,response);
return; } else {
} f.doFilter(request,response,chain);
switch (h.getSecurity()) {
case LEGACY:
legacy.doFilter(request,response,chain);
break;
case SECURED:
acegi.doFilter(request,response,chain);
break;
case UNSECURED:
chain.doFilter(request,response);
break;
} }
} }
public void destroy() { public void destroy() {
// these fields can be null if HudsonFilter.init() fails in the middle // the filter can be null if the filter is not initialized yet.
if(legacy!=null) if(filter != null)
legacy.destroy(); filter.destroy();
if(acegi!=null)
acegi.destroy();
} }
} }
...@@ -87,7 +87,7 @@ public class HudsonPrivateSecurityRealm extends SecurityRealm implements ModelOb ...@@ -87,7 +87,7 @@ public class HudsonPrivateSecurityRealm extends SecurityRealm implements ModelOb
if(u!=null) { if(u!=null) {
// ... and let him login // ... and let him login
Authentication a = u.getProperty(Details.class).createAuthentication(); Authentication a = u.getProperty(Details.class).createAuthentication();
a = HudsonFilter.AUTHENTICATION_MANAGER.authenticate(a); a = this.getSecurityComponents().manager.authenticate(a);
SecurityContextHolder.getContext().setAuthentication(a); SecurityContextHolder.getContext().setAuthentication(a);
// then back to top // then back to top
......
...@@ -212,7 +212,7 @@ public class LDAPSecurityRealm extends SecurityRealm { ...@@ -212,7 +212,7 @@ public class LDAPSecurityRealm extends SecurityRealm {
if(!(hudson.getSecurityRealm() instanceof LDAPSecurityRealm)) if(!(hudson.getSecurityRealm() instanceof LDAPSecurityRealm))
return null; return null;
try { try {
LdapUserDetails details = (LdapUserDetails) HudsonFilter.USER_DETAILS_SERVICE_PROXY.loadUserByUsername(u.getId()); LdapUserDetails details = (LdapUserDetails) hudson.getSecurityRealm().getSecurityComponents().userDetails.loadUserByUsername(u.getId());
Attribute mail = details.getAttributes().get("mail"); Attribute mail = details.getAttributes().get("mail");
if(mail==null) return null; // not found if(mail==null) return null; // not found
return (String)mail.get(); return (String)mail.get();
......
...@@ -3,10 +3,16 @@ package hudson.security; ...@@ -3,10 +3,16 @@ package hudson.security;
import org.acegisecurity.AuthenticationManager; import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.Authentication; import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException; import org.acegisecurity.AuthenticationException;
import org.springframework.web.context.WebApplicationContext;
import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerRequest;
import groovy.lang.Binding;
import hudson.model.Descriptor; import hudson.model.Descriptor;
import hudson.util.spring.BeanBuilder;
import net.sf.json.JSONObject; import net.sf.json.JSONObject;
import javax.servlet.Filter;
import javax.servlet.FilterConfig;
/** /**
* {@link SecurityRealm} that accepts {@link ContainerAuthentication} object * {@link SecurityRealm} that accepts {@link ContainerAuthentication} object
* without any check (that is, by assuming that the such token is * without any check (that is, by assuming that the such token is
...@@ -40,6 +46,25 @@ public final class LegacySecurityRealm extends SecurityRealm implements Authenti ...@@ -40,6 +46,25 @@ public final class LegacySecurityRealm extends SecurityRealm implements Authenti
return "loginEntry"; return "loginEntry";
} }
/**
* Filter to run for the LegacySecurityRealm is the
* ChainServletFilter legacy from /WEB-INF/security/SecurityFilters.groovy.
*/
@Override
public Filter createFilter(FilterConfig filterConfig) {
Binding binding = new Binding();
SecurityComponents sc = this.createSecurityComponents();
binding.setVariable("authenticationManagerProxy", sc.manager);
binding.setVariable("userDetailsServiceProxy", sc.userDetails);
binding.setVariable("rememberMeServicesProxy", sc.rememberMe);
BeanBuilder builder = new BeanBuilder();
builder.parse(filterConfig.getServletContext().getResourceAsStream("/WEB-INF/security/SecurityFilters.groovy"),binding);
WebApplicationContext context = builder.createApplicationContext();
return (Filter) context.getBean("legacy");
}
public Descriptor<SecurityRealm> getDescriptor() { public Descriptor<SecurityRealm> getDescriptor() {
return DESCRIPTOR; return DESCRIPTOR;
} }
......
package hudson.security; package hudson.security;
import groovy.lang.Binding;
import hudson.ExtensionPoint; import hudson.ExtensionPoint;
import hudson.model.Describable; import hudson.model.Describable;
import hudson.model.Descriptor; import hudson.model.Descriptor;
import hudson.model.Hudson; import hudson.model.Hudson;
import hudson.util.DescriptorList; import hudson.util.DescriptorList;
import hudson.util.PluginServletFilter; import hudson.util.PluginServletFilter;
import hudson.util.spring.BeanBuilder;
import org.acegisecurity.Authentication; import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationManager; import org.acegisecurity.AuthenticationManager;
import org.acegisecurity.context.SecurityContextHolder;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.ui.rememberme.RememberMeServices;
import org.acegisecurity.userdetails.UserDetailsService; import org.acegisecurity.userdetails.UserDetailsService;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse; import org.kohsuke.stapler.StaplerResponse;
...@@ -24,6 +31,7 @@ import com.octo.captcha.service.CaptchaServiceException; ...@@ -24,6 +31,7 @@ import com.octo.captcha.service.CaptchaServiceException;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.servlet.Filter; import javax.servlet.Filter;
import javax.servlet.FilterConfig;
/** /**
* Pluggable security realm that connects external user database to Hudson. * Pluggable security realm that connects external user database to Hudson.
...@@ -34,9 +42,41 @@ import javax.servlet.Filter; ...@@ -34,9 +42,41 @@ import javax.servlet.Filter;
* <p> * <p>
* If additional views/URLs need to be exposed, * If additional views/URLs need to be exposed,
* an active {@link SecurityRealm} is bound to <tt>CONTEXT_ROOT/securityRealm/</tt> * an active {@link SecurityRealm} is bound to <tt>CONTEXT_ROOT/securityRealm/</tt>
* through {@link Hudson#getSecurityRealm()}. * through {@link Hudson#getSecurityRealm()}, so you can define additional pages and
* operations on your {@link SecurityRealm}.
*
* <h2>How do I implement this class?</h2>
* <p>
* For compatibility reasons, there are two somewhat different ways to implement a custom SecurityRealm.
* *
* <h3>Supported Views</h3> * <p>
* One is to override the {@link #createSecurityComponents()} and create key Acegi components
* that control the authentication process.
* The default {@link SecurityRealm#createFilter(FilterConfig)} implementation then assembles them
* into a chain of {@link Filter}s. All the incoming requests to Hudson go through this filter chain,
* and when the filter chain is done, {@link SecurityContext#getAuthentication()} would tell us
* who the current user is.
*
* <p>
* If your {@link SecurityRealm} needs to touch the default {@link Filter} chain configuration
* (e.g., adding new ones), then you can also override {@link #createFilter(FilterConfig)} to do so.
*
* <p>
* This model is expected to fit most {@link SecurityRealm} implementations.
*
*
* <p>
* The other way of doing this is to ignore {@link #createSecurityComponents()} completely (by returning
* {@link SecurityComponents} created by the default constructor) and just concentrate on {@link #createFilter(FilterConfig)}.
* As long as the resulting filter chain properly sets up {@link Authentication} object at the end of the processing,
* Hudson doesn't really need you to fit the standard Acegi models like {@link AuthenticationManager} and
* {@link UserDetailsService}.
*
* <p>
* This model is for those "weird" implementations.
*
*
* <h2>Views</h2>
* <dl> * <dl>
* <dt>loginLink.jelly</dt> * <dt>loginLink.jelly</dt>
* <dd> * <dd>
...@@ -44,6 +84,10 @@ import javax.servlet.Filter; ...@@ -44,6 +84,10 @@ import javax.servlet.Filter;
* is anonymous. For {@link SecurityRealm}s that support user sign-up, this is a good place * is anonymous. For {@link SecurityRealm}s that support user sign-up, this is a good place
* to show a "sign up" link. See {@link HudsonPrivateSecurityRealm} implementation * to show a "sign up" link. See {@link HudsonPrivateSecurityRealm} implementation
* for an example of this. * for an example of this.
*
* <dt>config.jelly</dt>
* <dd>
* This view is used to render the configuration page in the system config screen.
* </dl> * </dl>
* *
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
...@@ -57,10 +101,11 @@ public abstract class SecurityRealm implements Describable<SecurityRealm>, Exten ...@@ -57,10 +101,11 @@ public abstract class SecurityRealm implements Describable<SecurityRealm>, Exten
* is configured. * is configured.
* *
* <p> * <p>
* {@link AuthenticationManager} instantiation often depends on the user configuration * {@link AuthenticationManager} instantiation often depends on the user-specified parameters
* (for example, if the authentication is based on LDAP, the host name of the LDAP server * (for example, if the authentication is based on LDAP, the user needs to specify
* depends on the user configuration), and such configuration is expected to be * the host name of the LDAP server.) Such configuration is expected to be
* captured as instance variables of {@link SecurityRealm} implementation. * presented to the user via <tt>config.jelly</tt> and then
* captured as instance variables inside the {@link SecurityRealm} implementation.
* *
* <p> * <p>
* Your {@link SecurityRealm} may also wants to install a servlet {@link Filter} * Your {@link SecurityRealm} may also wants to install a servlet {@link Filter}
...@@ -166,6 +211,42 @@ public abstract class SecurityRealm implements Describable<SecurityRealm>, Exten ...@@ -166,6 +211,42 @@ public abstract class SecurityRealm implements Describable<SecurityRealm>, Exten
} }
} }
/**
* Holder for the SecurityComponents.
*/
private transient SecurityComponents securityComponents;
/**
* Use this function to get the security components, without necessarily
* recreating them.
*/
public synchronized SecurityComponents getSecurityComponents() {
if (this.securityComponents == null) {
this.securityComponents = this.createSecurityComponents();
}
return this.securityComponents;
}
/**
* Filter to run for this SecurityRealm. The default is the
* ChainServletFilter "filter" from
* /WEB-INF/security/SecurityFilters.groovy.
* Subclasses can override this to completely change the filter sequence.
* For merely adding filters, it may be easier to use
* {@link PluginServletFilter}.
*/
public Filter createFilter(FilterConfig filterConfig) {
Binding binding = new Binding();
SecurityComponents sc = this.getSecurityComponents();
binding.setVariable("authenticationManagerProxy", sc.manager);
binding.setVariable("userDetailsServiceProxy", sc.userDetails);
binding.setVariable("rememberMeServicesProxy", sc.rememberMe);
BeanBuilder builder = new BeanBuilder();
builder.parse(filterConfig.getServletContext().getResourceAsStream("/WEB-INF/security/SecurityFilters.groovy"),binding);
WebApplicationContext context = builder.createApplicationContext();
return (Filter) context.getBean("filter");
}
/** /**
* Singleton constant that represents "no authentication." * Singleton constant that represents "no authentication."
*/ */
...@@ -188,6 +269,14 @@ public abstract class SecurityRealm implements Describable<SecurityRealm>, Exten ...@@ -188,6 +269,14 @@ public abstract class SecurityRealm implements Describable<SecurityRealm>, Exten
return null; return null;
} }
/**
* We don't need any filter for this {@link SecurityRealm}.
*/
@Override
public Filter createFilter(FilterConfig filterConfig) {
return new ChainedServletFilter();
}
/** /**
* Maintain singleton semantics. * Maintain singleton semantics.
*/ */
...@@ -200,21 +289,46 @@ public abstract class SecurityRealm implements Describable<SecurityRealm>, Exten ...@@ -200,21 +289,46 @@ public abstract class SecurityRealm implements Describable<SecurityRealm>, Exten
* Just a tuple so that we can create various inter-related security related objects and * Just a tuple so that we can create various inter-related security related objects and
* return them all at once. * return them all at once.
* *
* <p>
* None of the fields are ever null.
*
* @see SecurityRealm#createSecurityComponents() * @see SecurityRealm#createSecurityComponents()
*/ */
public static final class SecurityComponents { public static final class SecurityComponents {
public AuthenticationManager manager; public final AuthenticationManager manager;
public UserDetailsService userDetails; public final UserDetailsService userDetails;
public final RememberMeServices rememberMe;
public SecurityComponents() {} public SecurityComponents() {
// we use AuthenticationManagerProxy here just as an implementation that fails all the time,
// not as a proxy. No one is supposed to use this as a proxy.
this(new AuthenticationManagerProxy());
}
public SecurityComponents(AuthenticationManager manager) { public SecurityComponents(AuthenticationManager manager) {
this.manager = manager; // we use UserDetailsServiceProxy here just as an implementation that fails all the time,
// not as a proxy. No one is supposed to use this as a proxy.
this(manager,new UserDetailsServiceProxy());
} }
public SecurityComponents(AuthenticationManager manager, UserDetailsService userDetails) { public SecurityComponents(AuthenticationManager manager, UserDetailsService userDetails) {
this(manager,userDetails,createRememberMeService(userDetails));
}
public SecurityComponents(AuthenticationManager manager, UserDetailsService userDetails, RememberMeServices rememberMe) {
assert manager!=null && userDetails!=null && rememberMe!=null;
this.manager = manager; this.manager = manager;
this.userDetails = userDetails; this.userDetails = userDetails;
this.rememberMe = rememberMe;
}
private static RememberMeServices createRememberMeService(UserDetailsService uds) {
// create our default TokenBasedRememberMeServices, which depends on the availability of the secret key
TokenBasedRememberMeServices2 rms = new TokenBasedRememberMeServices2();
rms.setUserDetailsService(uds);
rms.setKey(Hudson.getInstance().getSecretKey());
rms.setParameter("remember_me"); // this is the form field name in login.jelly
return rms;
} }
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册