暂时忽略的必备知识
spring boot
装配spring security
的内容
下文提到的 SecurityAutoConfiguration
和 UserDetailsServiceAutoConfiguration
是Security
的默认实现,至于为什么,涉及spring boot的原理。
spring boot
中@ConditionalOnXXX
、@Order
的用法 (涉及到优先级关系,理解后才能知道如何覆盖框架默认实现)- 由于存在自动装配,手动装配不写的情况下也可以用Security。使用手动配置往往是要覆盖默认配置。其中
@EnableWebSecurity(debug = true)
装配Spring Security
。其中必要的(debug = true)
,开日志用于看被装配的Filter。
spring framework
中ioc
的内容
需要预备知识
-
spring boot版本 2.2.0
Security 的配置入口
SpringBootWebSecurityConfiguration 的【内部类】 DefaultConfigurerAdapter
的实现类为配置入口, 可以看看默认的配置,后期可以模仿编码并覆盖其配置
- 默认是空实现,可以自己从0开始配置。但是不妨看看官方给了哪些配置,继承改一下写个demo还是很方便的。
这里选用WebSecurityConfigurerAdapter
其中重载了父类的configure
方法:configure(HttpSecurity http)
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
- 默认情况下为form表单提交,点进去看看配置了什么。发现
UsernamePasswordAuthenticationFilter
public FormLoginConfigurer<HttpSecurity> formLogin() throws Exception {
return getOrApply(new FormLoginConfigurer<>());
}
public FormLoginConfigurer() {
super(new UsernamePasswordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
-
UsernamePasswordAuthenticationFilter
配置了POST: /login
的登录url。认证规则中的key的用户名密码为username
和password
-
UsernamePasswordAuthenticationFilter
使用父类的模板方法,模板方法中又采用了责任链模式。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (!requiresAuthentication(request, response)) { // 不需要认证的就直接交给下一个Filter
chain.doFilter(request, response);
return;
}
authResult = attemptAuthentication(request, response); // 需要认证, 回调子类实现进行验证
// ... 省略
}
attemptAuthentication()
即为默认的认证实现
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
// 最终都是从request的parameter中拿到username 和 password的value
String username = obtainUsername(request);
String password = obtainPassword(request);
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // 使用value生成被系统识别的token,属于Web项目Security的流程,如果日后需要用jwt作为token, 需要覆盖
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest); // 管理认证的业务逻辑
}
getAuthenticationManager
明显得将获取账号密码、与用账号密码认证的过程解耦了。认证的业务逻辑即可再AuthenticationManager
的实现类中找到。
TODO
AuthenticationManager
的实现类及注入机制比较复杂…搞清楚后再来填坑。这里贴一个参考的流程。
查阅资料知道默认的实现是: ProviderManager
Security 如查询可信用户
- 由
spring boot
装配的UserDetailsServiceAutoConfiguration
public class UserDetailsServiceAutoConfiguration {
@Bean
@ConditionalOnMissingBean(
type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository")
@Lazy
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
ObjectProvider<PasswordEncoder> passwordEncoder) {//... 省略}
}
- 从注解来看是懒加载一个使用内存查询的方式获取用户
- 查看
InMemoryUserDetailsManager
的接口UserDetailsManager
,定义了用户的增删改查。是拓展Security的重要接口
验证未通过
AuthenticationManager 这个接口方法非常奇特,入参和返回值的类型都是 Authentication 。该接
口的作用是对用户的未授信凭据进行认证,认证通过则返回授信状态的凭据,否则将抛出认证异常
AuthenticationException
- 所以通常我们都需要
ControllerAdvice
进行捕获异常做其他业务的定制化处理
后记
要使用好Spring Security
,至少需要懂得一下操作拓展默认实现。
- 自己定义一个
Filte
拓展默认实现,需要用到ioc
的内容。拓展可以是采取责任链模式,放在Security
默认的UsernamePasswordAuthenticationFilter
之前 - 自己定义一个继承
AbstractAuthenticationToken
的Token封装类,后期可用jwt实现 - 自己定义一个实现
UserDetailsManager
的类,实现访问自己数据库中的数据用于认证 - 自己定义一个ControllerAdvice进行捕获AuthenticationException,并进行异常处理
TODO 更详细的操作需要结合具体业务了。
参考
https://felord.cn/categories/spring-security/