我很难确切地理解Spring Security/Sping Boot 在引擎盖下做了什么,以及我需要实现什么来启动和运行基于表单的身份验证(https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html)。
作为参考,我正在构建一个web应用程序,目前正在开发后端,它是用Sping Boot 开发的。数据存储在一个非关系数据库中。我还没有构建前端,我使用Postman来测试我的API。
鉴于官方文档(https://docs.spring.io/spring-security/reference/features/index.html)的庞大规模和分散性,我遵循了这篇文章(https://www.youtube.com/watch?v=her_7pa0vrg)和这篇教程(https://www.marcobehler.com/guides/spring-security),以了解如何使用Spring Security。这两篇教程都使用了一个已弃用的类,但我选择暂时使用它,以便更容易地构建功能性应用程序-稍后将对其进行更改。
我设法理解的是SpringSecurity使用一系列方法过滤客户机请求(包含在一系列Filter类中),我们所做的基本上是 * 声明 * 这些过滤器应该如何操作,而不是自己编写代码。这个声明是通过一个Java配置类完成的,它确定哪些资源是公共可用的,这些设备被隐藏在认证墙之后并且除了被认证之外还需要特定的许可才能被访问。此外,该配置文件也是我们声明允许使用哪些身份验证方法的地方(基于表单的身份验证属于此类别)。
以下是我的配置文件(经过编辑以便于理解):
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final PasswordEncoder passwordEncoder;
private final AppUserDetailsService appUserService;
@Autowired
public SecurityConfiguration(PasswordEncoder passwordEncoder, AppUserDetailsService appUserService){
this.passwordEncoder = passwordEncoder;
this.appUserService = appUserService;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
// ... other configuration to protect resources
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll()
.logoutSuccessUrl("/login")
.and()
.httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(appUserService);
return provider;
}
}
其中passwordEncoder和appUserService是两个组件,它们在各自的类中声明,应分别用于对用户密码进行编码和从数据库中检索用户身份验证详细信息(这些信息位于实现UserDetails接口的类中,请参见https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/core/userdetails/UserDetails.html和)。
现在,根据我对官方文档(https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html)的理解,我在配置类中构建的DaoAuthenticationProvider应该负责身份验证问题。除了上面提到的内容,我不需要在代码中定义任何其他内容。对吗?今天这似乎不起作用,但我可能在我的Postman请求中出错了-提前感谢!
EDIT(参考我在@Toerktumlare的回答下的第二批评论):
我的配置文件现在如下所示(省略了UserDetailsService和PasswordEncrypter):
@Configuration
public class SecurityConfiguration {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.anyRequest().authenticated()
.antMatchers("/").permitAll()
.antMatchers("/register/**").permitAll()
.antMatchers("someUrl/{username}").access("@userSecurity.isSameUser(authentication, #username)")
.antMatchers("/someOtherUrl/{username}/**").access("@userSecurity.isSameUser(authentication, #username)")
)
.formLogin((formLogin) ->
formLogin.loginPage("/login")
.permitAll()
)
.logout((logout) ->
logout.deleteCookies("remove")
.invalidateHttpSession(false)
.logoutSuccessUrl("/login")
);
return http.build();
}
}
我得到了这个编译错误:“类型AuthorizeHttpRequestsConfigurer.AuthorizedUrl中的方法access(AuthorizationManager)不适用于参数(String)",这是我得到的。我不明白的是,官方文档似乎确实使用了带有字符串参数的.access()方法(https://docs.spring.io/spring-security/reference/servlet/authorization/expression-based.html#el-access-web-beans)。我猜他们使用了不同的.access()方法,但我看不出是怎么回事。
2条答案
按热度按时间cxfofazt1#
你正在做同样的错误,因为大多数人开始与 Spring 安全。
你没有阅读spring security文档的architecture和Authentication Architecture章节,而是在谷歌上搜索并遵循一个过时的教程。
您链接到https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/form.html,它清楚地显示没有使用
DaoAuthenticationProvider
,但您仍然实现了一个。因此,让我们回顾一下您的代码:
WebSecurityConfigurerAdapter
是deprecated,不应使用,因此请将其删除。您现在需要做的就是构建一个安全配置,并将其作为Bean返回到上下文。那么你已经添加了
FormLogin
和httpBasic
,这是两种完全不同的身份验证方式。我希望你知道这一点。基本记录为here。表单登录记录为here。
最后,如果您提供了一个
UserDetailsService
和一个PasswordEncoder
,但不需要配置您自己的AuthenticationProvider
,那么Spring Security将自己理解您需要一个DaoAuthenticationProvider
,并为您构建它,只要您将它们作为@Bean
提供给上下文即可。这样就可以删除其余的大部分代码。
这就是你最终的代码:
老实说,你不需要更多。
kzipqqlq2#
我终于设法让整个事情的工作。请注意,我还没有广泛测试这一点,所以我会回到这一点,一旦我这样做,并更新这个答案,如果必要的。
关于我的问题的第一部分(原来是整个问题:“我需要自己实现什么?"),我需要自己实现的唯一东西是UserDetailsService和PasswordEncoder接口。前者是负责检索用户详细信息的类(显然,对数据库的实际访问可以委托给另一个类,SpringSecurity确实提供了一种实现,以防用户拥有内存中的数据库或关系数据库,但这不是我的情况,用户必须编写他们自己的实现是完全有意义的,因为Spring不会强制您使用任何特定的方式来存储用户凭据,因此无法自动知道在哪里检索所述凭据。
我还实现了一个SecurityFilterChain bean(就像@Toerktumlare在他的回答中所做的那样)。当构建自己的应用程序时,您很可能也需要这样做,但是即使没有声明SecurityFilterChain bean*,您也可以使用基于表单的身份验证。我还没有测试过这个,但是文档中的某个地方肯定提到了它(如果我找到它并且有时间的话,稍后会链接到它)。
一旦您的自定义UserDetailsService和PasswordEncoder实现被编写并配置为bean,并可供Spring自己示例化,您就大功告成了。Spring会自动将它们添加到其AuthenticationProvider中(无需实现!),并使用它们进行身份验证。
现在,进入我问题的第二部分(“切换到http.authorizeHttpRequests后,如何使.access()方法工作()?"),我最终不得不切换到实现这个新设置中.access方法所请求的functional interface。它现在可以按预期工作了。我'我仍然对Spring文档中的一个片段感到困惑,在那里他们使用了带有String参数的方法(注意:该链接将把您重定向到该部分,我说的是该部分的第二个代码片段(标题为“示例1.参考方法”的代码片段):要么是我遗漏了什么,要么是他们的失误。如果有时间,我也会进一步调查。