引言
在试题系统开发过程中,认证方式越来越完善,也对Spring Security
有了更加深刻的理解。
本文,我们一起来领略Spring Security
的设计原理。
原理
必备基础
Servlet
是Java Web
领域中的软件开发规范,Tomcat
是实现Servlet
规范的Java Web
服务器。
package javax.servlet;
public interface Servlet {
public void init(ServletConfig config) throws ServletException;
public ServletConfig getServletConfig();
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException;
public String getServletInfo();
public void destroy();
}
Servlet
长这样,生命周期十分简单,初始化init
、业务逻辑service
、销毁destroy
。
简单来说:我们根据Servlet
接口开发我们的应用,服务器开发商根据Servlet
接口开发Servlet
服务器。
前端HTTP
请求,Tomcat
选择路由匹配的Servlet
,如果已经实例化,直接调用service
;如果未实例化,实例化后调用service
,处理完之后返回响应。
图中示例的url
统一采用小写,关于url
大小写的问题,以下是结论,虽然有些url
是大小写不敏感的,但是这使得表示唯一性变得困难,我们通常应该认为大小写是敏感的。
There may be URLs, or parts of URLs, where case doesn't matter, but identifying these may not be easy. Users should always consider that URLs are case-sensitive.
为了在Servlet
前后执行其他逻辑,定义了Filter
接口,在请求前后都会执行。
原理分析
从Spring Security
官方文档摘抄的一张图:
Spring Security
的原理其实就是通过Servlet
中的Filter
技术进行实现的,通过一系列内置的或自定义的安全Filter
,实现接口认证与授权。
Spring Security
官方也给出了默认Filter
的执行顺序,先执行认证的过滤器,后执行授权的过滤器,其中就有我们常用的UsernamePasswordAuthenticationFilter
、BasicAuthenticationFilter
等等。
阅读源码一个Filter
源码即可理解整个认证架构设计。
注:以下代码中部分无关代码已被删减。
public class BasicAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain chain) {
try {
/** 从请求中获取用户名密码信息 */
UsernamePasswordAuthenticationToken authRequest = authenticationConverter.convert(request);
/** 如果没有相关信息,说明不是此种认证方式,执行后续过滤器 */
if (authRequest == null) {
chain.doFilter(request, response);
return;
}
/** 获取用户名 */
String username = authRequest.getName();
/** 判断该用户是否需要认证 */
if (authenticationIsRequired(username)) {
/** 尝试使用 token 进行认证 */
Authentication authResult = this.authenticationManager
.authenticate(authRequest);
/** 认证成功,将认证结果置入上下文 */
SecurityContextHolder.getContext().setAuthentication(authResult);
/** 认证成功相关回调 */
this.rememberMeServices.loginSuccess(request, response, authResult);
onSuccessfulAuthentication(request, response, authResult);
}
}
catch (AuthenticationException failed) {
/** 认证失败,清空当前上下文信息 */
SecurityContextHolder.clearContext();
/** 认证失败相关回调 */
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);
}
}
其实很简单是不是?
添加自定义认证逻辑
如果默认的验证方式不满足要求,要怎么添加自定义验证方式呢?其实只需要添加自定义的Filter
即可。
就比如说常见的短信验证码登录方式:
默认不支持短信方式,我们可以在过滤器链中植入一个自定义的短信验证过滤器,认证成功后设置认证信息即可。
SecurityContextHolder.getContext().setAuthentication(authResult);
Reactive
这个是上个月遇到的问题,尝试了一下OAuth 2.0
认证架构。
其实这个架构很普遍,许多项目都采用该种架构。只不过都是采用Spring Cloud Netflix Zuul + Spring Security Resource Server
的实现。
这里我尝试将Zuul
换成Spring Cloud Gateway
,因为Zuul
的阻塞IO
在网关层面实在太影响性能了。
前面已经说了,Spring Security
中的认证与授权方式是通过Servlet
技术套装中的Filter
实现的,Tomcat
提供了Servlet
的运行环境。
Spring Cloud Netflix Zuul
集成Spring Boot Starter Web
也就是默认的Tomcat
实现网关,所以Zuul
中是有Servlet
的运行环境的。
而非阻塞的Spring Cloud Gateway
基于Spring WebFlux
框架,不支持Servlet
,底层采用高性能的Netty
服务器。
Spring WebFlux
与Spring MVC
的对比请看下图:
所以当Spring Security
与Spring Cloud Gateway
集成时,就会出错,Spring Security
需要的Servlet
环境没有被满足。
这也是之前对Reactive
的理解不到位而引起的错误,WebFlux
环境下,应该集成Spring Security Reactive
,而非默认的Spring Security
。
总结
经资料查阅,高性能的 Netty
十分受各大互联网企业欢迎,Twitter
、Facebook
、苹果、微博都在使用Netty
,具体Netty
为什么更适合高并发?以后我们一起学习。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。