Spring Security 系列第1章
2023-5-4|2023-6-29
麦兜
type
Post
status
Published
date
May 4, 2023
slug
summary
tags
安全
Spring
category
技术分享
password
icon
介绍
这一章主要内容是认证(验证身份),因为整个流程是先验证身份(你是谁?)了,再去授权(你能干什么?)。Spring Security 内置了很多认证协议实现,所以只探究 Username and Password 的实现和使用。
以下是 Spring Security 内置认证协议实现。
- Username and Password - how to authenticate with a username/password
- OAuth 2.0 Login - OAuth 2.0 Log In with OpenID Connect and non-standard OAuth 2.0 Login (i.e. GitHub)
- …
Spring Security 框架很喜欢用(Filter)链式设计模式去实现这些认证协议,因为它们直接可以利用 SecurityContext 传递认证信息。
认证架构
对于认证,Spring Security 已经有一套设计好的架构了,实现认证其实只需要实现一些 class 即可。
上方图是 Spring Security 认证的基本流程,
FilterChainPorxy
会根据 url 去匹配 SecurityFilterChain,默认情况下 Chain 包含AbstractAuthenticationProcessingFilter
的实现。AbstractAuthenticationProcessingFilter
是一个抽象的类,如果想定义一些认证规则可以实现这个类,其中里面有一个抽象方法attemptAuthentication()
由用户实现,然后被doFilter()
调用。下面是经过删减的代码,用户实现
attemptAuthentication()
的使用可以根据 request
获取请求参数,然后各种 if 比如密码是否正确等等流程,如果不匹配可以抛出一些异常,在 catch 的时候会调用异常处理器。在实现
attemptAuthentication()
的时候可以选择是否走认证管理器(AuthenticationManager
),这也是一些通用的抽象类只要配置或者实现一些接口就可以实现认证。如上图,首先会创建认证信息(Authentication
),然后传输到认证管理器进行认证。private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) { try { Authentication authenticationResult = attemptAuthentication(request, response); if (authenticationResult == null) { return; } successfulAuthentication(request, response, chain, authenticationResult); } catch (InternalAuthenticationServiceException failed) { unsuccessfulAuthentication(request, response, failed); } catch (AuthenticationException ex) { unsuccessfulAuthentication(request, response, ex); } }
下面看一下官方内置的 Username and Password 的实现。
UsernamePasswordAuthenticationFilter
是实现 AbstractAuthenticationProcessingFilter
对应的是 POST /login 请求(根据 request 判断是否使用这个过滤器)。下方代码是
UsernamePasswordAuthenticationFilter
的 attemptAuthentication()
方法实现,根据 request 获取 username 和 password 参数封装到认证信息 Authentication(UsernamePasswordAuthenticationToken
) 调用认证管理器进行认证。public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } String username = obtainUsername(request); username = (username != null) ? username.trim() : ""; String password = obtainPassword(request); password = (password != null) ? password : ""; // 创建认证信息 UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username, password); // Allow subclasses to set the "details" property setDetails(request, authRequest); // 传输到认证管理器进行认证 return this.getAuthenticationManager().authenticate(authRequest); }
认证管理器默认使用
ProviderManager
,在身份验证的时候其实是调用AuthenticationProvider
的 authenticate()
方法,这个接口可以让用户实现不同的认证信息有不同的身份验证流程。Username and Password 的默认实现是
DaoAuthenticationProvider
,各种判断后如果认证成功返回一个已认证的认证信息,否者会抛出异常。