为 Basic Auth 和 JWT 配置具有 spring 安全性的多种身份验证类型
Configure multiple authentication types wit spring security for Basic Auth & JWT
我有一个 API 需要用两种不同的方式保护它:
1) 将 JWT 用于除 1 之外的所有请求 URL,需要使用 Basic Auth
进行保护
2) 一个 url.
的基本身份验证
我已经为 JWT 和 Basic Auth 设置了安全配置。我的问题是,当我向 Basic Authenticated URL 发出请求时
使用有效的用户名和密码,它成功地验证了我的身份并完成了在 cassandra 中存储数据的工作。
然后我希望必须通过 /api/login 为所有其他请求 URL 生成令牌并将其添加到授权:Bearer {Token} header..
但是,如果我通过 Basic Auth 进行了身份验证,我就可以访问其他 URL(受 JWT 身份验证保护),甚至在请求中没有令牌。
当我访问 JWT 受保护的 URL 而无需使用 Basic Auth 进行身份验证时,我必须在 header 中发送令牌并且它按预期工作..
我应该期待这个吗?正如我所相信的,即使我已经通过一个端点的基本身份验证进行了身份验证,我仍然应该在请求中为所有其他受保护的 JWT 端点发送令牌..
我找到了这个答案:
SpringBoot multiple authentication adapter
还有这篇文章:
https://docs.spring.io/spring-security/site/docs/4.2.x/reference/htmlsingle/#multiple-httpsecurity
并尝试实施解决方案,但问题仍然存在。
安全配置class如下:
@Configuration
@EnableWebSecurity
public class SecurityHttpConfig extends WebSecurityConfigurerAdapter {
@Configuration
@Order(1)
public static class BasicAuthSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${basic.auth.user}")
private String basicAuthUsername;
@Value("${basic.auth.password}")
private String basicAuthPassword;
@Value("${crashboxx.consume.endpoint}")
private String crashBoxxConsumeEndpoint;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/v1/crash/consumeCrashBoxxEvent").hasRole("ADMIN").and()
.httpBasic().authenticationEntryPoint(getBasicAuthEntryPoint()).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);// We don't need sessions to be created.
}
@Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
return new CustomBasicAuthenticationEntryPoint();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth.inMemoryAuthentication().withUser(basicAuthUsername).password(encoder.encode(basicAuthPassword))
.roles("ADMIN");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Configuration
@Order(2)
public static class JwtWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Autowired
private JwtAuthenticationProvider jwtAuthenticationProvider;
// Any endpoints that require no authorization should be added here..
@Value("${api.login.endpoint}")
private String loginEndpoint;
@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(jwtAuthenticationProvider);
}
@Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() {
return new JwtAuthenticationTokenFilter();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers("/api/login").permitAll().anyRequest().authenticated();
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
httpSecurity.headers().cacheControl();
}
}
使用 BasicAuthEntryPoint class:
public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
private static final Gson gson = new Gson();
@Override
public void commence(final HttpServletRequest request, final HttpServletResponse response,
final AuthenticationException authException) throws IOException, ServletException {
// Authentication failed, send error response.
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println(gson.toJson("HTTP Status 401 : " + authException.getMessage()));
}
@Override
public void afterPropertiesSet() throws Exception {
setRealmName("Realm");
super.afterPropertiesSet();
}
还有 JWT 实现:
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.header}")
private String tokenHeader;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
final String requestHeader = request.getHeader(tokenHeader);
// Ensure Auth Header contains 'Bearer'
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
String authToken = requestHeader.substring(7);
JwtAuthentication authentication = new JwtAuthentication(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
我希望这是有道理的。如果还有其他问题,请告诉我,但似乎无法解决这个问题。
我首先添加了 'special case',这是用于基本身份验证的 url,但仍然没有任何区别。
谢谢
您在 @Order(1)
的安全配置中 post 编码
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/v1/crash/consumeCrashBoxxEvent").hasRole("ADMIN").and()
.httpBasic().authenticationEntryPoint(getBasicAuthEntryPoint()).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
如果这正是您使用的代码,那么将不会参考您的 @Order(2)
配置。这将是死配置。
让我解释一下!
http.authorizeRequests()
== http.antMatcher("/**").authorizeRequests()
在您的第一个配置中,您使用了通配符并且您的配置结果是
/v1/crash/consumeCrashBoxxEvent
如果用户通过身份验证并具有角色 ADMIN 则访问
Rest of URL's
如果用户通过身份验证则访问
让我猜猜发生了什么!
1. 您正在点击 URL /v1/crash/consumeCrashBoxxEvent
或 any URL
您将被提示进行基本身份验证。
2. 认证成功后你可以访问任何URL因为你是认证用户
However, if ive been authenticated via Basic Auth, I can then access the other URL's (protected by JWT auth) without even having a token in the request.
因为我已经告诉您可以访问任何 URL 因为您是经过身份验证的用户
When I access the JWT protected URL's without authenticating with Basic Auth, I have to send the token in the header and it works as expected
检查是否可以访问没有令牌。因为一旦您通过基本身份验证登录,就不会从服务器端注销(即使您重新启动服务器)。只有关闭浏览器才能实现注销。所以你通过关闭并再次启动浏览器来测试它。并通过不发送 JWT 令牌对其进行测试。
还要确保您的请求到达 JwtAuthenticationTokenFilter,将调试日志用于验证。
因为在你的问题中有很多抽象,除非你 post 你的完整代码,否则很难准确预测发生了什么。
如果我的预测与实际情况有偏差,请在评论中告诉我。
这是通过使用 Praveen Kumar Lalasangi 的上述答案中提供的信息解决的。
对 configure
方法的一个小改动就成功了。更新是:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher(crashBoxxConsumeEndpoint).authorizeRequests().anyRequest()
.hasRole("ADMIN")
.and().httpBasic().authenticationEntryPoint(getBasicAuthEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
我有一个 API 需要用两种不同的方式保护它:
1) 将 JWT 用于除 1 之外的所有请求 URL,需要使用 Basic Auth
进行保护2) 一个 url.
的基本身份验证我已经为 JWT 和 Basic Auth 设置了安全配置。我的问题是,当我向 Basic Authenticated URL 发出请求时 使用有效的用户名和密码,它成功地验证了我的身份并完成了在 cassandra 中存储数据的工作。
然后我希望必须通过 /api/login 为所有其他请求 URL 生成令牌并将其添加到授权:Bearer {Token} header..
但是,如果我通过 Basic Auth 进行了身份验证,我就可以访问其他 URL(受 JWT 身份验证保护),甚至在请求中没有令牌。
当我访问 JWT 受保护的 URL 而无需使用 Basic Auth 进行身份验证时,我必须在 header 中发送令牌并且它按预期工作..
我应该期待这个吗?正如我所相信的,即使我已经通过一个端点的基本身份验证进行了身份验证,我仍然应该在请求中为所有其他受保护的 JWT 端点发送令牌..
我找到了这个答案: SpringBoot multiple authentication adapter
还有这篇文章: https://docs.spring.io/spring-security/site/docs/4.2.x/reference/htmlsingle/#multiple-httpsecurity
并尝试实施解决方案,但问题仍然存在。
安全配置class如下:
@Configuration
@EnableWebSecurity
public class SecurityHttpConfig extends WebSecurityConfigurerAdapter {
@Configuration
@Order(1)
public static class BasicAuthSecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${basic.auth.user}")
private String basicAuthUsername;
@Value("${basic.auth.password}")
private String basicAuthPassword;
@Value("${crashboxx.consume.endpoint}")
private String crashBoxxConsumeEndpoint;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/v1/crash/consumeCrashBoxxEvent").hasRole("ADMIN").and()
.httpBasic().authenticationEntryPoint(getBasicAuthEntryPoint()).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);// We don't need sessions to be created.
}
@Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint() {
return new CustomBasicAuthenticationEntryPoint();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth.inMemoryAuthentication().withUser(basicAuthUsername).password(encoder.encode(basicAuthPassword))
.roles("ADMIN");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@Configuration
@Order(2)
public static class JwtWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
@Autowired
private JwtAuthenticationProvider jwtAuthenticationProvider;
// Any endpoints that require no authorization should be added here..
@Value("${api.login.endpoint}")
private String loginEndpoint;
@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) {
authenticationManagerBuilder.authenticationProvider(jwtAuthenticationProvider);
}
@Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() {
return new JwtAuthenticationTokenFilter();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests().antMatchers("/api/login").permitAll().anyRequest().authenticated();
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
httpSecurity.headers().cacheControl();
}
}
使用 BasicAuthEntryPoint class:
public class CustomBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {
private static final Gson gson = new Gson();
@Override
public void commence(final HttpServletRequest request, final HttpServletResponse response,
final AuthenticationException authException) throws IOException, ServletException {
// Authentication failed, send error response.
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println(gson.toJson("HTTP Status 401 : " + authException.getMessage()));
}
@Override
public void afterPropertiesSet() throws Exception {
setRealmName("Realm");
super.afterPropertiesSet();
}
还有 JWT 实现:
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Value("${jwt.header}")
private String tokenHeader;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
final String requestHeader = request.getHeader(tokenHeader);
// Ensure Auth Header contains 'Bearer'
if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
String authToken = requestHeader.substring(7);
JwtAuthentication authentication = new JwtAuthentication(authToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
我希望这是有道理的。如果还有其他问题,请告诉我,但似乎无法解决这个问题。
我首先添加了 'special case',这是用于基本身份验证的 url,但仍然没有任何区别。
谢谢
您在 @Order(1)
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers("/v1/crash/consumeCrashBoxxEvent").hasRole("ADMIN").and()
.httpBasic().authenticationEntryPoint(getBasicAuthEntryPoint()).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
如果这正是您使用的代码,那么将不会参考您的 @Order(2)
配置。这将是死配置。
让我解释一下!
http.authorizeRequests()
== http.antMatcher("/**").authorizeRequests()
在您的第一个配置中,您使用了通配符并且您的配置结果是
/v1/crash/consumeCrashBoxxEvent
如果用户通过身份验证并具有角色 ADMIN 则访问
Rest of URL's
如果用户通过身份验证则访问
让我猜猜发生了什么!
1. 您正在点击 URL /v1/crash/consumeCrashBoxxEvent
或 any URL
您将被提示进行基本身份验证。
2. 认证成功后你可以访问任何URL因为你是认证用户
However, if ive been authenticated via Basic Auth, I can then access the other URL's (protected by JWT auth) without even having a token in the request.
因为我已经告诉您可以访问任何 URL 因为您是经过身份验证的用户
When I access the JWT protected URL's without authenticating with Basic Auth, I have to send the token in the header and it works as expected
检查是否可以访问没有令牌。因为一旦您通过基本身份验证登录,就不会从服务器端注销(即使您重新启动服务器)。只有关闭浏览器才能实现注销。所以你通过关闭并再次启动浏览器来测试它。并通过不发送 JWT 令牌对其进行测试。
还要确保您的请求到达 JwtAuthenticationTokenFilter,将调试日志用于验证。
因为在你的问题中有很多抽象,除非你 post 你的完整代码,否则很难准确预测发生了什么。
如果我的预测与实际情况有偏差,请在评论中告诉我。
这是通过使用 Praveen Kumar Lalasangi 的上述答案中提供的信息解决的。
对 configure
方法的一个小改动就成功了。更新是:
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher(crashBoxxConsumeEndpoint).authorizeRequests().anyRequest()
.hasRole("ADMIN")
.and().httpBasic().authenticationEntryPoint(getBasicAuthEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}