Spring Security主要通过自定义的Filter对相关的URL进行权限控制,本篇文章将主要介绍它的基本原理:
继续接着上篇文章 《Spring Security(二):三分钟入门HelloWorld 》,zjj-security-demo 启动 ZjjSecurityDemoApplication 类main方法,并访问 http://localhost:8080/hi 进行观察:
访问 http://localhost:8080/hi 请求之前,在以下几个类的方法打上断点。
执行步骤
2、首先会走到 ExceptionTranslationFilter类 doFilter()方法【原因是不满足前面几个认证过滤器的要求】
3、执行后到了拦截器 FilterSecurityInterceptor类 invoke()方法,当执行InterceptorStatusToken token = super.beforeInvocation(fi); 后就报错了
4、并返回到了ExceptionTranslationFilter,被捕获到异常信息 Access is denied (拒绝访问)
5、处理异常信息后,根据配置跳转到登录页面
6、登录之后,被UsernamePasswordAuthenticationFilter类attemptAuthentication()方法拦截
7、认证成功之后,再经过 ExceptionTranslationFilter
8、接着再到 FilterSecurityInterceptor 会验证是否有权限访问,如果有的话InterceptorStatusToken token 会返回值
9、验证成功之后,会重定向到登录页面之前的请求
相关源文件代码
1、FilterSecurityInterceptor
// org.springframework.security.web.access.intercept.FilterSecurityInterceptor
public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
InterceptorStatusToken token = super.beforeInvocation(fi);
try {
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}
super.afterInvocation(token, null);
}
}
2、 ExceptionTranslationFilter
// org.springframework.security.web.access.ExceptionTranslationFilter
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) {
// Try to extract a SpringSecurityException from the stacktrace
Throwable[] causeChain = throwableAnalyzer.determineCauseChain(ex);
RuntimeException ase = (AuthenticationException) throwableAnalyzer
.getFirstThrowableOfType(AuthenticationException.class, causeChain);
if (ase == null) {
ase = (AccessDeniedException) throwableAnalyzer.getFirstThrowableOfType(
AccessDeniedException.class, causeChain);
}
if (ase != null) {
handleSpringSecurityException(request, response, chain, ase);
}
else {
// Rethrow ServletExceptions and RuntimeExceptions as-is
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
// Wrap other Exceptions. This shouldn't actually happen
// as we've already covered all the possibilities for doFilter
throw new RuntimeException(ex);
}
}
}
3、 UsernamePasswordAuthenticationFilter
// org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter
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();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
4、 BasicAuthenticationFilter
// org.springframework.security.web.authentication.www.BasicAuthenticationFilter
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
final boolean debug = this.logger.isDebugEnabled();
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Basic ")) {
chain.doFilter(request, response);
return;
}
try {
String[] tokens = extractAndDecodeHeader(header, request);
assert tokens.length == 2;
String username = tokens[0];
if (debug) {
this.logger
.debug("Basic Authentication Authorization header found for user '"
+ username + "'");
}
if (authenticationIsRequired(username)) {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
username, tokens[1]);
authRequest.setDetails(
this.authenticationDetailsSource.buildDetails(request));
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
if (debug) {
this.logger.debug("Authentication success: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
onSuccessfulAuthentication(request, response, authResult);
}
}
catch (AuthenticationException failed) {
SecurityContextHolder.clearContext();
if (debug) {
this.logger.debug("Authentication request for failed: " + failed);
}
this.rememberMeServices.loginFail(request, response);
onUnsuccessfulAuthentication(request, response, failed);
if (this.ignoreFailure) {
chain.doFilter(request, response);
}
else {
this.authenticationEntryPoint.commence(request, response, failed);
}
return;
}
chain.doFilter(request, response);
}
更多内容请关注我的个人博客:http://www.zjjfx68.com/