支持并发全栈MVC(会话)身份验证以及Spring授权服务器中的无状态JWT身份验证(0.2.3+)
在创建授权服务器时,我有一个相对简单的AS。它支持:
- 用户注册 (WebMVC)
- FormLogin (WebMVC)
- 忘记密码 (WebMVC)
- 管理 RegisteredClient (WebMVC) - 一个可以管理其 API 客户端的地方,这些客户端交换访问令牌以访问其他资源服务器。
我还有一些 API @RestController 端点;但是,我发现我无法向他们发出 JWT 身份验证请求,因为身份验证过程不适用于这些请求,因为我正在获得基于会话的 .formlogin() 样式显示登录页面内容,并以 200 呈现好的,而不是我所期望的 - 401 或 403 或 200 好的,但具有 RESTful application/json 结构化答案。
如何同时支持基于会话的 WebMVC 流以及依赖于 JWT 身份验证的 REST 控制器端点?
import org.junit.jupiter.api.Order;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class DefaultSecurityConfiguration {
public static final int FILTER_PRECEDENCE = -5;
@Bean
@Order(FILTER_PRECEDENCE)
SecurityFilterChain defaultSecurityFilterChain(final HttpSecurity http) throws Exception {
return http.authorizeRequests(authorizeRequests ->
authorizeRequests
.mvcMatchers("/favicon.ico", "/favicon-16x16.png", "/favicon-32x32.png", "/mstile-150x150.png", "/apple-touch-icon.png", "/", "/assets/**", "/login/**", "/webjars/**", "/register", "/register-form", "/actuator/health", "/reset-password", "/reset-password-2", "/error")
.permitAll()
).formLogin(oauth2 ->
oauth2
.loginPage("/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/")
.failureUrl("/login?error=1")
.permitAll()
)
.build();
}
}
这是我的 AuthorizationServerConfiguration 的过滤器链配置:
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(final HttpSecurity http) throws Exception {
final OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
final RequestMatcher endpointsMatcher = authorizationServerConfigurer
.getEndpointsMatcher();
http
.requestMatcher(endpointsMatcher)
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.apply(authorizationServerConfigurer);
return http.formLogin(Customizer.withDefaults()).build();
}
并且,为了咯咯笑,这是一个我期望工作的示例 REST 端点:
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/oauth/account")
public class AccountController {
public static final String ACCOUNT_ID_IS_REQUIRED = "Account Id is required.";
private final UserDetailRepository userDetailRepository;
private final AccountService accountService;
/**
* Return List of all account and clients of logged in user
*
* @param authentication
* @return
*/
@GetMapping
public List<AccountResponseDTO> findAllUserAccounts(final Authentication authentication) {
final User user = this.userDetailRepository.findByUsername(authentication.getName()).get();
return this.accountService.findAccountsByUserId(user.getId());
}
除了简单之外,我还很好奇为什么在 自定义同意示例server 在两个不同的安全过滤器中存在 .formLogin() 的重复声明。我已经玩了很多次了,而且我对安全过滤器覆盖有点困惑。
如果我在 .formLogin() 中存在差异,我会观察到默认值或间歇性故障。如果我在更高值的 @Order 上删除 .formLogin ,那么登录将不起作用,但我会得到一个有效的 REST 端点。如果我在两个地方都有 .formLogin() ,那么我就有了一个工作良好的 WebMVC 方面;但是,我的 REST 控制器身份验证仅返回 JWT 的承载值,而不是身份验证主体。这里发生了奇怪的事情——任何帮助将不胜感激!
When creating an Authorization server, I have a relatively simple AS. It supports:
- User Registration (WebMVC)
- FormLogin (WebMVC)
- Forgot Password (WebMVC)
- Managing RegisteredClient (WebMVC) - a place where someone can manage their API clients that exchange for access tokens to access other resource servers.
I also have some API @RestController endpoints; however, I'm observing that I cannot make JWT authenticated requests to them as the authentication process isn't working for those as I'm getting a session-based .formlogin() style display of the login page content rendered out with a 200 OK rather than what I'd expect -- a 401 or 403 or 200 OK but with RESTful application/json structured answers.
How can I simultaneously support the Session-based WebMVC flows as well as REST controller endpoints that rely on JWT authentication?
import org.junit.jupiter.api.Order;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class DefaultSecurityConfiguration {
public static final int FILTER_PRECEDENCE = -5;
@Bean
@Order(FILTER_PRECEDENCE)
SecurityFilterChain defaultSecurityFilterChain(final HttpSecurity http) throws Exception {
return http.authorizeRequests(authorizeRequests ->
authorizeRequests
.mvcMatchers("/favicon.ico", "/favicon-16x16.png", "/favicon-32x32.png", "/mstile-150x150.png", "/apple-touch-icon.png", "/", "/assets/**", "/login/**", "/webjars/**", "/register", "/register-form", "/actuator/health", "/reset-password", "/reset-password-2", "/error")
.permitAll()
).formLogin(oauth2 ->
oauth2
.loginPage("/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/")
.failureUrl("/login?error=1")
.permitAll()
)
.build();
}
}
and here's my AuthorizationServerConfiguration's filter chain configuration:
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(final HttpSecurity http) throws Exception {
final OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer<>();
final RequestMatcher endpointsMatcher = authorizationServerConfigurer
.getEndpointsMatcher();
http
.requestMatcher(endpointsMatcher)
.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.apply(authorizationServerConfigurer);
return http.formLogin(Customizer.withDefaults()).build();
}
and, for giggles, here's a sample REST endpoint that I expect to work:
@Validated
@RestController
@RequiredArgsConstructor
@RequestMapping("/oauth/account")
public class AccountController {
public static final String ACCOUNT_ID_IS_REQUIRED = "Account Id is required.";
private final UserDetailRepository userDetailRepository;
private final AccountService accountService;
/**
* Return List of all account and clients of logged in user
*
* @param authentication
* @return
*/
@GetMapping
public List<AccountResponseDTO> findAllUserAccounts(final Authentication authentication) {
final User user = this.userDetailRepository.findByUsername(authentication.getName()).get();
return this.accountService.findAccountsByUserId(user.getId());
}
Beyond the simple, I'm also curious why in the example for a custom-consent server there are repeated declarations for .formLogin() in the two different security filters. I've been playing with this quite a bit and I'm a bit stumped on the Security Filter overrides.
If I have differences in .formLogin() I observe defaults or intermittent glitchiness. If I remove .formLogin on a higher-valued @Order then login doesn't work, but I get a working REST endpoint. If I have .formLogin() in both spots I have a nicely working WebMVC side of things; however then my REST controller Authentications are returning just the JWT's bearer value rather than the Authentication Principal. Weird stuff going on here -- any help would be appreciated!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
考虑配置3个安全过滤链。第一个是Auth,其次是用于具有携带者令牌的呼叫,而所有其他MVC请求的最后一个是:
至于您的第二个问题,我认为每个过滤器链都有自己的过滤器集。由于登录也被作为过滤器实现,因此应将其添加到相应的过滤器链中。
更新:似乎您对订单注释的导入不正确:
导入org.junit.jupiter.api.order;
Consider configuring 3 security filter chains. First one is for auth, second is for calls having bearer token, and the last one for all other MVC requests:
As for your second question, I think that every filter chain have it's own filter set. Since login is implemented as a filter too, it should be added to the corresponding filter chains.
Update: seems that you have incorrect import for Order annotation:
import org.junit.jupiter.api.Order;