我在一个 URL 的标头中发送一个身份验证令牌。我需要
从 URL 中获取此身份验证令牌并进行解码。如果用户名和
密码匹配...
通常,使用令牌进行身份验证的目的是摆脱用户名和密码检查。
开箱即用的 Spring Security 支持的基本 HTTP 身份验证假定在 HTTP 标头中传递 base64 编码的用户名和密码:例如Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l(base64 编码Aladdin:OpenSesame)。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/public").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
如果您仍需要以其他方式从令牌中提取用户名和密码,请考虑以下示例。
考虑到您有以下 REST 控制器:
@RestController
public class TestRestController {
@GetMapping("/api/getStudent/v1")
public String helloWorld() {
return "Hello, World!";
}
@GetMapping("/info")
public String test() {
return "Test";
}
}
为了使端点/api/getStudent/v1 受保护和/info 公开,并从HTTP 请求标头中提取主体和凭据,您需要实现自定义AbstractAuthenticationProcessingFilter:
public class HeaderUsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public HeaderUsernamePasswordAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
setAuthenticationSuccessHandler((request, response, authentication) -> {
});
setAuthenticationFailureHandler((request, response, exception) ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, exception.getMessage()));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
String token = request.getHeader("token");
String username = token; //get username from token
String password = token; //get password from token
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, password);
return getAuthenticationManager().authenticate(authenticationToken);
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
}
此过滤器必须从标头中传递的令牌中提取主体和凭据,并尝试使用 Spring Security 进行身份验证。
接下来,您必须创建此自定义过滤器的实例并配置 Spring Security 以将过滤器添加到安全过滤器链中(.addFilterBefore(authenticationFilter(), UsernamePasswordAuthenticationFilter.class)):
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Bean
public HeaderUsernamePasswordAuthenticationFilter authenticationFilter() throws Exception {
HeaderUsernamePasswordAuthenticationFilter authenticationFilter =
new HeaderUsernamePasswordAuthenticationFilter(new AntPathRequestMatcher("/api/**"));
authenticationFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable()
.addFilterBefore(
authenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
//...
}
让过滤器知道 Spring Security authenticationManagerBean: authenticationFilter.setAuthenticationManager(authenticationManagerBean()); 很重要。
您可以通过传递RequestMatcher 来配置通过身份验证保护哪些端点:例如new AntPathRequestMatcher("/api/**").
为了测试,你可以在内存中创建UserDetailsService并使用用户名test、密码test和权限admin进行测试:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//...
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("test")
.password(passwordEncoder().encode("test"))
.authorities("admin");
}
}
运行应用程序并尝试在没有身份验证的情况下访问公共端点:
curl -i http://localhost:8080/info
HTTP/1.1 200
Test
没有身份验证的受保护端点:
curl -i http://localhost:8080/api/getStudent/v1
HTTP/1.1 401
没有无效令牌的受保护端点:
curl -i http://localhost:8080/api/getStudent/v1 -H 'token: not_valid'
HTTP/1.1 401
最后是带有有效令牌的受保护端点:
curl -i http://localhost:8080/api/getStudent/v1 -H 'token: test'
HTTP/1.1 200
Hello, World!