spring security context
spring security context 谈到他我们就得谈一谈他的作用,生命周期接口如下
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication var1);
}
说白了就是在当前请求下的各个环节如过滤器中或者controller中都能通过context获取该认证数据,我们的认证过后的Authentication 接口实例对象就放在里面,但是spring给我们封装了一个工具类HttpRequestResponseHolder,其实现原理也很简单通过ThreadLocal 将spring security的context方法里面,以保证在当前线程的可以随意获取。
总而言之其作用就是在各个地方通过以下方式获取认证信息
SecurityContextHolder.getContext().getAuthentication().getName()。。。。
SecurityContextPersistenceFilter
该过滤器位于拦截器的较前端,其基本功能就是
构建spring security context环境,并放入HttpRequestResponseHolder中供当前线程访问
调用拦截器链的下一个拦截器
进行HttpRequestResponseHolder中spring security context 的清理
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
//是不是一个新的request,不是则执行拦截器链的下一个
if (request.getAttribute(FILTER_APPLIED) != null) {
// ensure that filter is only applied once per request
chain.doFilter(request, response);
return;
}
final boolean debug = logger.isDebugEnabled();
//标记,如果重定向后就不会重复调用
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
//如果是饥饿模式的话就立即创建session
if (forceEagerSessionCreation) {
HttpSession session = request.getSession();
if (debug && session.isNew()) {
logger.debug("Eagerly created session: " + session.getId());
}
}
//就相当于RequestResponse的 包装器
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
response);
//通过加载context 如果有直接返回,没有返回null
SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
try {
//将context设置到SecurityContextHolder中去
SecurityContextHolder.setContext(contextBeforeChainExecution);
chain.doFilter(holder.getRequest(), holder.getResponse());
}
finally {
/*
退出是清理SecurityContextHolder的当前线程的context
repo进行保存指定context,以便下一次请求获取
*/
SecurityContext contextAfterChainExecution = SecurityContextHolder
.getContext();
SecurityContextHolder.clearContext();
repo.saveContext(contextAfterChainExecution, holder.getRequest(),
holder.getResponse());
request.removeAttribute(FILTER_APPLIED);
if (debug) {
logger.debug("SecurityContextHolder now cleared, as request processing completed");
}
}
}
其中出现了一个我们目前还不熟悉的service 类,即SecurityContextRepository repo ,实现类接口如下:
public interface SecurityContextRepository {
SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);
void saveContext(SecurityContext context, HttpServletRequest request,
HttpServletResponse response);
boolean containsContext(HttpServletRequest request);
}
我们来看一个实现类就一目了然了:
/*
该实现类通过session的attribute进行spring security context的存取功能
*/
public class HttpSessionSecurityContextRepository implements SecurityContextRepository {
public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
HttpServletRequest request = requestResponseHolder.getRequest();
HttpServletResponse response = requestResponseHolder.getResponse();
//获取当前session
HttpSession httpSession = request.getSession(false);
//读取session 中 属性名为SPRING_SECURITY_CONTEXT的属性
SecurityContext context = readSecurityContextFromSession(httpSession);
if (context == null) {
if (logger.isDebugEnabled()) {
logger.debug("No SecurityContext was available from the HttpSession: "
+ httpSession + ". " + "A new one will be created.");
}
//如果没有就创建一个空的
context = generateNewContext();
}
SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(
response, request, httpSession != null, context);
requestResponseHolder.setResponse(wrappedResponse);
requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(
request, wrappedResponse));
return context;
}
}
看到这里我想你已经明白了吧,这相当于是一个 dao,只不过spring security context数据的存取是放到了session中。
总结:
通过session 获取spring security context环境,session中没有就创建一个空的,并放入HttpRequestResponseHolder中供当前线程访问
调用拦截器链的下一个拦截器
进行HttpRequestResponseHolder中spring security context 的清理,并通过session service存取session中。
认证与授权
介绍认证与授权作者将会规定一个场景即:用户匿名访问,被拦截后,进行登录,信息故意填错,在进行登录,信息正确,在访问一个没有权限的页面。
有了以上场景,我们就可以进行流程的分析了。
匿名访问经过的第一个待分析的拦截器AnonymousAuthenticationFilter
public class AnonymousAuthenticationFilter extends GenericFilterBean implements
InitializingBean {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
if (SecurityContextHolder.getContext().getAuthentication() == null) {
/*
创建一个匿名认证并放入context中
这是Authentication的实现类
AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken(key,
principal, authorities);
*/
SecurityContextHolder.getContext().setAuthentication(
createAuthentication((HttpServletRequest) req));
if (logger.isDebugEnabled()) {
logger.debug("Populated SecurityContextHolder with anonymous token: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("SecurityContextHolder not populated with anonymous token, as it already contained: '"
+ SecurityContextHolder.getContext().getAuthentication() + "'");
}
}
chain.doFilter(req, res);
}
}
经过这个过滤器之后,当前线程的spring security context中的authentication就为AnonymousAuthenticationToken的实例了
匿名访问经过的第二个待分析的拦截器ExceptionTranslationFilter
该拦截器看名字也知道他是一个异常处理拦截器,直接调用下一个拦截器,如果下一个拦截器抛出异常在过来分析剩下的代码。
比如认证异常,授权异常等,如果出现了认证异常,会调用相关的异常处理器进行处理,具体处理在handleSpringSecurityException(request, response, chain, ase);这个函数中
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
try {
chain.doFilter(request, response);
logger.debug("Chain processed normally");
}
catch (IOException ex) {
throw ex;
}
catch (Exception ex) {
//省略代码了部分代码,这个是核心代码
handleSpringSecurityException(request, response, chain, ase);
}
}
private void handleSpringSecurityException(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, RuntimeException exception)
throws IOException, ServletException {
//如果异常时认证异常
if (exception instanceof AuthenticationException) {
//调用该方法进行认证异常处理
sendStartAuthentication(request, response, chain,
(AuthenticationException) exception);
}
//如果是授权异常
else if (exception instanceof AccessDeniedException) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//如果是通过匿名认证器生成的 认证,还是触发认证异常处理
if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
sendStartAuthentication(
request,
response,
chain,
new InsufficientAuthenticationException(
messages.getMessage(
"ExceptionTranslationFilter.insufficientAuthentication",
"Full authentication is required to access this resource")));
}
else {
//调用授权异常处理器
accessDeniedHandler.handle(request, response,
(AccessDeniedException) exception);
}
}
}
protected void sendStartAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain,
AuthenticationException reason) throws ServletException, IOException {
SecurityContextHolder.getContext().setAuthentication(null);
requestCache.saveRequest(request, response);
//委托给认证切入点成员 变量进行处理
authenticationEntryPoint.commence(request, response, reason);
}
这里又有两个新的成员变量值得谈一谈,那就是认证切入点,和 授权处理器,下面仅仅看一下认证切入点的代码实现,授权处理类似,当然,这也是和其他认证的整合点,通过认证切入点我们可以重定向到单点登录页面或者其他的什么等。
/*
接口如下
*/
public interface AuthenticationEntryPoint {
void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException;
}
/*
当前默认的实现类为,该实现方法也很简单就是设置 response重定向到/login 路径下
*/
public class LoginUrlAuthenticationEntryPoint implements AuthenticationEntryPoint,InitializingBean {
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
String redirectUrl = null;
if (useForward) {
if (forceHttps && "http".equals(request.getScheme())) {
redirectUrl = buildHttpsRedirectUrlForRequest(request);
}
if (redirectUrl == null) {
String loginForm = determineUrlToUseForThisRequest(request, response,
authException);
RequestDispatcher dispatcher = request.getRequestDispatcher(loginForm);
dispatcher.forward(request, response);
return;
}
}
else {
// 默认重定向的 /login路径下
redirectUrl = buildRedirectUrlToLoginPage(request, response, authException);
}
redirectStrategy.sendRedirect(request, response, redirectUrl);
}
}
匿名访问经过的第三个待分析的拦截器FilterSecurityInterceptor
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements
Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
FilterInvocation fi = new FilterInvocation(request, response, chain);
invoke(fi);
}
public void invoke(FilterInvocation fi) throws IOException, ServletException {
......
//进行权限的校验
InterceptorStatusToken token = super.beforeInvocation(fi);
......
}
}
}
//权限校验方法如下
protected InterceptorStatusToken beforeInvocation(Object object) {
//元数据就是我们使用http/intercept-url 进行设置的
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
....
try {
//交给授权管理器进行权限的校验
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));
throw accessDeniedException;
}
}
这又将会引入两个新的成员 ,accessDecisionManager 和投票器。
accessDecisionManager的默认实现类是AffirmativeBased ,其实现机制是只要有一个投票器返回true,就有权限放行。
匿名访问流程总结
匿名用户经过 匿名访问过滤器 将会在contextHolder中存储一个匿名认证,经过异常处理拦截器,经过权限校验拦截器,由于是匿名认证所以权限管理器通过投票器校验失败,抛出权限校验异常,异常处理拦截器处理调用认证切入点重定向到/login