Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。简单来说springsecurity主要对用户进行认证以及授权。认证是指验证用户是否为当前系统中的用户, 授权是指验证用户是否有权限执行某个操作。故此本文着重从认证以及授权两个方向对springsecurity进行分析。
SecurityContextHolder
SecurityContextHolder 提供对 SecurityContext 的访问, SecurityContextHolder 通过 threadLocal存储信息,因而 SecurityContext可以在线程执行期间的任何一处访问。
SecurityContext
SecurityContext 持有认证对象的相关信息。
Authentication
spring ssecurity形式的认证主体
GrantedAuthority
在应用层面对认证主体的授权,包含了权限等信息
UserDetails
包含了构建认证主体所必须的信息
UserDetailsService
根据username构建UserDetails
在项目的任何位置我们都可以通过以下代码获取当前用户信息
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (principal instanceof UserDetails) {
String username = ((UserDetails)principal).getUsername();
} else {
String username = principal.toString();
}
spring security 认证授权过程主要是通过核心滤器链实现的
Spring Security核心过滤器链,如下面日志所示:
o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: any request, [
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@29a98d9f,
org.springframework.security.web.context.SecurityContextPersistenceFilter@3d7fb838,
org.springframework.security.web.header.HeaderWriterFilter@3aa41da1,
org.springframework.security.web.csrf.CsrfFilter@1352434e,
org.springframework.security.web.authentication.logout.LogoutFilter@4acb7ecc,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@1afd72ef,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@254f906e,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@2da3b078,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@1b1c538d,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@3a37a501,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@2dc3271b,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@544e8149,
org.springframework.security.web.session.SessionManagementFilter@3c83468e,
org.springframework.security.web.access.ExceptionTranslationFilter@267dc982,
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@7b122839]
下面先简单介绍这些过滤器的作用:
每一个请求在经过这一轮认证通过之后才能真正的访问我们的资源。
重点分析 UsernamePasswordAuthenticationFilter :
用户认证部分主要依靠这个过滤器来实现
该过滤器用于拦截用户登陆请求,看它是否是一个来自用户名/密码表单登录页面提交的用户登录认证请求,缺省使用的匹配模式是:POST /login。一般情况下,如果是用户登录认证请求,该请求就不会在整个过滤器链中继续传递了,而是会被当前过滤器处理并进入响应用户阶段。
public class UsernamePasswordAuthenticationFilter extends
AbstractAuthenticationProcessingFilter {
// 默认的用户名密码字段是username,password
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;
private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;
private boolean postOnly = true;
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException(
"Authentication method not supported: " + request.getMethod());
}
// 从请求中获取用户名/密码
String username = obtainUsername(request);
String password = obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
// 根据用户提供的用户名/密码信息构建一个认证token
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
// 交给 authenticationManager执行真正的用户身份认证
return this.getAuthenticationManager().authenticate(authRequest);
}
@Nullable
protected String obtainPassword(HttpServletRequest request) {
return request.getParameter(passwordParameter);
}
@Nullable
protected String obtainUsername(HttpServletRequest request) {
return request.getParameter(usernameParameter);
}
protected void setDetails(HttpServletRequest request,
UsernamePasswordAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
public void setUsernameParameter(String usernameParameter) {
Assert.hasText(usernameParameter, "Username parameter must not be empty or null");
this.usernameParameter = usernameParameter;
}
public void setPasswordParameter(String passwordParameter) {
Assert.hasText(passwordParameter, "Password parameter must not be empty or null");
this.passwordParameter = passwordParameter;
}
public void setPostOnly(boolean postOnly) {
this.postOnly = postOnly;
}
public final String getUsernameParameter() {
return usernameParameter;
}
public final String getPasswordParameter() {
return passwordParameter;
}
}
父类
public abstract class AbstractAuthenticationProcessingFilter extends GenericFilterBean
implements ApplicationEventPublisherAware, MessageSourceAware {
protected ApplicationEventPublisher eventPublisher;
protected AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
// 认证管理器
private AuthenticationManager authenticationManager;
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private RememberMeServices rememberMeServices = new NullRememberMeServices();
private RequestMatcher requiresAuthenticationRequestMatcher;
// 登录认证成功时是否继续执行filter chain,缺省为false,该属性安全配置阶段会重新指定, 但安全配置阶段缺省使用的值也是false,表示登录认证成功时直接进入响应用户阶段
private boolean continueChainBeforeSuccessfulAuthentication = false;
// session 认证策略
private SessionAuthenticationStrategy sessionStrategy = new NullAuthenticatedSessionStrategy();
private boolean allowSessionCreation = true;
private AuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler();
private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
protected AbstractAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
setFilterProcessesUrl(defaultFilterProcessesUrl);
}
protected AbstractAuthenticationProcessingFilter(
RequestMatcher requiresAuthenticationRequestMatcher) {
Assert.notNull(requiresAuthenticationRequestMatcher,
"requiresAuthenticationRequestMatcher cannot be null");
this.requiresAuthenticationRequestMatcher = requiresAuthenticationRequestMatcher;
}
@Override
public void afterPropertiesSet() {
Assert.notNull(authenticationManager, "authenticationManager must be specified");
}
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) {
// 检查当前请求是否是一个用户名/密码登录表单请求,如果不是,则放行不做处理
chain.doFilter(request, response);
return;
}
if (logger.isDebugEnabled()) {
logger.debug("Request is to process authentication");
}
Authentication authResult;
try {
// AuthenticationManger 执行认证
authResult = attemptAuthentication(request, response);
if (authResult == null) {
// return immediately as subclass has indicated that it hasn't completed
// authentication
return;
}
// 执行session认证策略
sessionStrategy.onAuthentication(authResult, request, response);
}
catch (InternalAuthenticationServiceException failed) {
logger.error(
"An internal error occurred while trying to authenticate the user.",
failed);
// 认证失败
unsuccessfulAuthentication(request, response, failed);
return;
}
catch (AuthenticationException failed) {
// Authentication failed
unsuccessfulAuthentication(request, response, failed);
return;
}
// Authentication success
if (continueChainBeforeSuccessfulAuthentication) {
chain.doFilter(request, response);
}
// 认证成功
successfulAuthentication(request, response, chain, authResult);
}
protected boolean requiresAuthentication(HttpServletRequest request,
HttpServletResponse response) {
return requiresAuthenticationRequestMatcher.matches(request);
}
public abstract Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException, IOException,
ServletException;
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
if (logger.isDebugEnabled()) {
logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
+ authResult);
}
// 设置SecurityContext
SecurityContextHolder.getContext().setAuthentication(authResult);
// 若配置了rememberMe进行相应的记录
rememberMeServices.loginSuccess(request, response, authResult);
// Fire event
if (this.eventPublisher != null) {
eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
authResult, this.getClass()));
}
// 成功回调
successHandler.onAuthenticationSuccess(request, response, authResult);
}
protected void unsuccessfulAuthentication(HttpServletRequest request,
HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
SecurityContextHolder.clearContext();
if (logger.isDebugEnabled()) {
logger.debug("Authentication request failed: " + failed.toString(), failed);
logger.debug("Updated SecurityContextHolder to contain null Authentication");
logger.debug("Delegating to authentication failure handler " + failureHandler);
}
rememberMeServices.loginFail(request, response);
failureHandler.onAuthenticationFailure(request, response, failed);
}
protected AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setFilterProcessesUrl(String filterProcessesUrl) {
setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher(
filterProcessesUrl));
}
public final void setRequiresAuthenticationRequestMatcher(
RequestMatcher requestMatcher) {
Assert.notNull(requestMatcher, "requestMatcher cannot be null");
this.requiresAuthenticationRequestMatcher = requestMatcher;
}
public RememberMeServices getRememberMeServices() {
return rememberMeServices;
}
public void setRememberMeServices(RememberMeServices rememberMeServices) {
Assert.notNull(rememberMeServices, "rememberMeServices cannot be null");
this.rememberMeServices = rememberMeServices;
}
public void setContinueChainBeforeSuccessfulAuthentication(
boolean continueChainBeforeSuccessfulAuthentication) {
this.continueChainBeforeSuccessfulAuthentication = continueChainBeforeSuccessfulAuthentication;
}
public void setApplicationEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource,
"AuthenticationDetailsSource required");
this.authenticationDetailsSource = authenticationDetailsSource;
}
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
protected boolean getAllowSessionCreation() {
return allowSessionCreation;
}
public void setAllowSessionCreation(boolean allowSessionCreation) {
this.allowSessionCreation = allowSessionCreation;
}
public void setSessionAuthenticationStrategy(
SessionAuthenticationStrategy sessionStrategy) {
this.sessionStrategy = sessionStrategy;
}
/**
* Sets the strategy used to handle a successful authentication. By default a
* {@link SavedRequestAwareAuthenticationSuccessHandler} is used.
*/
public void setAuthenticationSuccessHandler(
AuthenticationSuccessHandler successHandler) {
Assert.notNull(successHandler, "successHandler cannot be null");
this.successHandler = successHandler;
}
public void setAuthenticationFailureHandler(
AuthenticationFailureHandler failureHandler) {
Assert.notNull(failureHandler, "failureHandler cannot be null");
this.failureHandler = failureHandler;
}
protected AuthenticationSuccessHandler getSuccessHandler() {
return successHandler;
}
protected AuthenticationFailureHandler getFailureHandler() {
return failureHandler;
}
}
关于认证管理器部分,后面的文章会继续分析,但这不影响我们了解认证的大体过程,
从上面的代码结合注释可以知道,如果认证成功,则会 :
调用所设置的SessionAuthenticationStrategy会话认证策略;
经过完全认证的Authentication对象设置到SecurityContextHolder中的SecurityContext上;
如果请求要求了Remember Me,进行相应记录;
发布事件InteractiveAuthenticationSuccessEvent;
执行配置的successHandler;
授权主要由AbstractSecurityInterceptor及其子类完成;
AbstractSecurityInterceptor 主要有两个实现类
FilterSecurityInterceptor 基于servlet Filter技术,应用于Web请求的授权控制
MethodSecurityInterceptor 基于AOP技术,应用于方法调用的授权控制
简单分析一下这三个类:
首先 FilterSecurityInterceptor:
...
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
// 将请求上下文信息为一个 FilterInvocation 对象
FilterInvocation fi = new FilterInvocation(request, response, chain);
// 调用该FilterInvocation执行安全认证
invoke(fi);
}
...
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// 如果被设置为在整个请求处理过程中只能执行一次 ,并且监测到已经执行过,直接放行,继续 filter chain 的执行
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
// 设置执行标志
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 这里是进行必要的认证和授权检查,具体实现在父类AbstractSecurityInterceptor中
// 如果遇到相关异常则抛出异常,之后的过滤器链不会继续执行
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
// 通过上一步的安全检查,请求继续放行
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
...
MethodSecurityInterceptor :
...
public Object invoke(MethodInvocation mi) throws Throwable {
// 这里将传递进来的MethodInvocation对象,进行必要的认证和授权检查,具体实现也在父类AbstractSecurityInterceptor中
InterceptorStatusToken token = super.beforeInvocation(mi);
Object result;
try {
// 通过了上一步的安全检查则放行方法调用
result = mi.proceed();
}
finally {
super.finallyInvocation(token);
}
return super.afterInvocation(token, result);
}
...
AbstractSecurityInterceptor:
...
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
final boolean debug = logger.isDebugEnabled();
// 类型校验
if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(
"Security invocation attempted for object "
+ object.getClass().getName()
+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+ getSecureObjectClass());
}
// 从安全配置中获取安全元数据,记录在 attributes中
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
if (attributes == null || attributes.isEmpty()) {
// attributes 为空,说明该对象没有配置安全控制,可以公开访问
if (rejectPublicInvocations) {
// 如果已经配置了拒绝公开调用,则抛出异常拒绝继续访问
throw new IllegalArgumentException(
"Secure object invocation "
+ object
+ " was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ "rejectPublicInvocations property is set to 'true'");
}
if (debug) {
logger.debug("Public object - authentication not attempted");
}
publishEvent(new PublicInvocationEvent(object));
// 该资源没有设置安全,并且系统允许了公开访问,则返回 null,即不做安全检查
return null; // no further work post-invocation
}
if (debug) {
logger.debug("Secure object: " + object + "; Attributes: " + attributes);
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(messages.getMessage(
"AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"),
object, attributes);
}
// 检查是否需要认证,如果需要,则执行认证并更行
Authentication authenticated = authenticateIfRequired();
// Attempt authorization
try {
// 用户通过了认证,基于当前用户信息,和目标对象的安全属性配置,进行相应的权限检查
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
// 权限检查不通过则抛出AccessDeniedException异常
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
if (debug) {
logger.debug("Authorization successful");
}
if (publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// Attempt to run as a different user
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object,
attributes);
if (runAs == null) {
if (debug) {
logger.debug("RunAsManager did not change Authentication object");
}
// no further work post-invocation
// 注意这里第二个参数为 false, 表示请求处理完之后再次回到该filter时不需要在刷新安全认证token
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false,
attributes, object);
}
else {
if (debug) {
logger.debug("Switching to RunAs Authentication: " + runAs);
}
// 修改保存在SecurityContext中的Authentication
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
// need to revert to token.Authenticated post-invocation
// 注意这里第二个参数为 true, 表示请求处理完之后再次回到该filter时需要在刷新安全认证token :
// 恢复到 run-as 之前的安全认证token
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
}
...
private Authentication authenticateIfRequired() {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
// 判断身份认证是否完成或者是否需要每次重新认证
if (authentication.isAuthenticated() && !alwaysReauthenticate) {
if (logger.isDebugEnabled()) {
logger.debug("Previously Authenticated: " + authentication);
}
return authentication;
}
// 身份认证
authentication = authenticationManager.authenticate(authentication);
// We don't authenticated.setAuthentication(true), because each provider should do
// that
if (logger.isDebugEnabled()) {
logger.debug("Successfully Authenticated: " + authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
/**
* Cleans up the work of the AbstractSecurityInterceptor after the secure
* object invocation has been completed. This method should be invoked after the
* secure object invocation and before afterInvocation regardless of the secure object
* invocation returning successfully (i.e. it should be done in a finally block).
*
* @param token as returned by the {@link #beforeInvocation(Object)} method
*/
//请求完毕后进行清理
protected void finallyInvocation(InterceptorStatusToken token) {
if (token != null && token.isContextHolderRefreshRequired()) {
if (logger.isDebugEnabled()) {
logger.debug("Reverting to original Authentication: "
+ token.getSecurityContext().getAuthentication());
}
SecurityContextHolder.setContext(token.getSecurityContext());
}
}
/**
* Completes the work of the AbstractSecurityInterceptor after the secure
* object invocation has been completed.
*
* @param token as returned by the {@link #beforeInvocation(Object)} method
* @param returnedObject any object returned from the secure object invocation (may be
* null)
* @return the object the secure object invocation should ultimately return to its
* caller (may be null)
*/
// 返回值进行修改
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
if (token == null) {
// public object
return returnedObject;
}
// 依旧需要清理
finallyInvocation(token); // continue to clean in this method for passivity
if (afterInvocationManager != null) {
// Attempt after invocation handling
try {
returnedObject = afterInvocationManager.decide(token.getSecurityContext()
.getAuthentication(), token.getSecureObject(), token
.getAttributes(), returnedObject);
}
catch (AccessDeniedException accessDeniedException) {
AuthorizationFailureEvent event = new AuthorizationFailureEvent(
token.getSecureObject(), token.getAttributes(), token
.getSecurityContext().getAuthentication(),
accessDeniedException);
publishEvent(event);
throw accessDeniedException;
}
}
return returnedObject;
}
这就是认证与授权的大致过程,后面的文章将继续分析部分细节。