hot*_*oup 6 java spring-security spring-boot
这里是 Java 11 和 Spring Security 2.7.x。我正在尝试将我的配置从基于(已弃用的)WebSecurityConfigurerAdapter的实现升级为使用SecurityFilterChain.
我的实现的重要之处在于我能够定义和配置/连接我自己的:
UsernamePasswordAuthenticationFilter实现)BasicAuthenticationFilterimpl)AuthenticationEntryPointimpl)AccessDeniedHandlerimpl)这是我基于阅读大量博客和文章的当前设置:
public class ApiAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private ObjectMapper objectMapper;
private ApiAuthenticationFactory authenticationFactory;
private TokenService tokenService;
public ApiAuthenticationFilter(
AuthenticationManager authenticationManager,
TokenService tokenService,
ObjectMapper objectMapper,
ApiAuthenticationFactory authenticationFactory) {
super(authenticationManager);
this.tokenService = tokenService;
this.objectMapper = objectMapper;
this.authenticationFactory = authenticationFactory;
init();
}
private void init() {
setFilterProcessesUrl("/v1/auth/sign-in");
}
@Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
SignInRequest signInRequest = objectMapper.readValue(request.getInputStream(), SignInRequest.class);
Authentication authentication = authenticationFactory
.createAuthentication(signInRequest.getEmail(), signInRequest.getPassword());
// perform authentication and -- if successful -- populate granted authorities
return authenticationManager.authenticate(authentication);
} catch (IOException e) {
throw new BadCredentialsException("malformed sign-in request payload", e);
}
}
@Override
protected void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain,
Authentication authentication) {
// called if-and-only-if the attemptAuthentication method above is successful
ApiAuthentication apiAuthentication = (ApiAuthentication) authentication;
TokenPair tokenPair = tokenService.generateTokenPair(apiAuthentication);
response.setStatus(HttpServletResponse.SC_OK);
try {
response.getWriter().write(objectMapper.writeValueAsString(tokenPair));
} catch (IOException e) {
throw new ApiServiceException(e);
}
}
}
public class ApiAuthorizationFilter extends BasicAuthenticationFilter implements SecurityConstants {
private ApiAuthenticationFactory authenticationFactory;
private AuthenticationService authenticationService;
private String jwtSecret;
public ApiAuthorizationFilter(
AuthenticationManager authenticationManager,
ApiAuthenticationFactory authenticationFactory,
AuthenticationService authenticationService,
@Value("${myapp.jwt-secret}") String jwtSecret) {
super(authenticationManager);
this.authenticationFactory = authenticationFactory;
this.authenticationService = authenticationService;
this.jwtSecret = jwtSecret;
}
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws IOException, ServletException {
String authHeader = request.getHeader(AUTHORIZATION_HEADER);
// allow the request through if no valid auth header is set; spring security
// will throw access denied exceptions downstream if the request is for an
// authenticated url
if (authHeader == null || !authHeader.startsWith(BEARER_TOKEN_PREFIX)) {
filterChain.doFilter(request, response);
return;
}
// otherwise an auth header was specified so lets take a look at it and grant access based
// on what we find
try {
DecodedJWT decodedJWT = JwtUtils.verifyToken(authHeader.replace(BEARER_TOKEN_PREFIX, ""), jwtSecret);
String subject = decodedJWT.getSubject();
ApiAuthentication authentication = authenticationFactory.createAuthentication(subject, null);
// TODO: I believe I need to look up granted authorities here and set them
authenticationService.setCurrentAuthentication(authentication);
filterChain.doFilter(request, response);
} catch (JWTVerificationException jwtVerificationEx) {
throw new AccessDeniedException("access denied", jwtVerificationEx);
}
}
}
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfigV2 {
private boolean securityDebug;
private ObjectMapper objectMapper;
private ApiAuthenticationFactory authenticationFactory;
private TokenService tokenService;
private AuthenticationService authenticationService;
private String jwtSecret;
private ApiUnauthorizedHandler unauthorizedHandler;
private ApiSignInFailureHandler signInFailureHandler;
private BCryptPasswordEncoder passwordEncoder;
private RealmService realmService;
@Autowired
public SecurityConfigV2(
@Value("${spring.security.debug:false}") boolean securityDebug,
ObjectMapper objectMapper,
ApiAuthenticationFactory authenticationFactory,
TokenService tokenService,
AuthenticationService authenticationService,
@Value("${myapp.authentication.jwt-secret}") String jwtSecret,
ApiUnauthorizedHandler unauthorizedHandler,
ApiSignInFailureHandler signInFailureHandler,
BCryptPasswordEncoder passwordEncoder,
RealmService realmService) {
this.securityDebug = securityDebug;
this.objectMapper = objectMapper;
this.authenticationFactory = authenticationFactory;
this.tokenService = tokenService;
this.authenticationService = authenticationService;
this.jwtSecret = jwtSecret;
this.unauthorizedHandler = unauthorizedHandler;
this.signInFailureHandler = signInFailureHandler;
this.passwordEncoder = passwordEncoder;
this.realmService = realmService;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
// build authentication manager
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(realmService)
.passwordEncoder(passwordEncoder)
.and()
.build(); // <-- calling it once up here, to get an AuthenticationManager instance
// enable CSRF
// TODO: enable once you are ready to provide 'CSRF tokens'
// /sf/answers/5295270921/
httpSecurity.csrf().disable();
// add CORS filter
httpSecurity.cors();
// add anonoymous/permitted paths (that is: what paths are allowed to bypass authentication)
httpSecurity.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers(HttpMethod.GET, "/actuator/health").permitAll()
.antMatchers(HttpMethod.POST, "/v*/tokens/refresh").permitAll();
// restrict all other paths and set them to authenticated
httpSecurity.authorizeRequests().anyRequest().authenticated();
// add authn + authz filters -- using AuthenticationManager instance here
httpSecurity.addFilter(apiAuthenticationFilter(authenticationManager));
httpSecurity.addFilter(apiAuthorizationFilter(authenticationManager));
// configure exception-handling for authn and authz
httpSecurity.exceptionHandling().accessDeniedHandler(unauthorizedHandler);
httpSecurity.exceptionHandling().authenticationEntryPoint(signInFailureHandler);
// configure stateless http sessions (appropriate for RESTful web services)
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// and building it a 2nd time here, to complete the filter
// but I believe this is what causes the error
return httpSecurity.build();
}
public ApiAuthenticationFilter apiAuthenticationFilter(AuthenticationManager authenticationManager) {
ApiAuthenticationFilter authenticationFilter = new ApiAuthenticationFilter(
authenticationManager, tokenService, objectMapper, authenticationFactory);
return authenticationFilter;
}
public ApiAuthorizationFilter apiAuthorizationFilter(AuthenticationManager authenticationManager) {
ApiAuthorizationFilter authorizationFilter = new ApiAuthorizationFilter(
authenticationManager,
authenticationFactory,
authenticationService,
jwtSecret);
return authorizationFilter;
}
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.debug(securityDebug)
.ignoring()
.antMatchers("/css/**", "/js/**", "/img/**", "/lib/**", "/favicon.ico");
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
corsConfiguration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
UrlBasedCorsConfigurationSource corsConfigurationSource = new UrlBasedCorsConfigurationSource();
corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
return corsConfigurationSource;
}
}
Run Code Online (Sandbox Code Playgroud)
当我启动我的应用程序时,我得到:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name
'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration':
Unsatisfied dependency expressed through method 'setFilterChains' parameter 0; nested exception is
org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'filterChain' defined
in class path resource [myapp/ws/security/v2/SecurityConfigV2.class]:
Bean instantiation via factory method failed; nested exception is
org.springframework.beans.BeanInstantiationException:
Failed to instantiate [org.springframework.security.web.SecurityFilterChain]:
Factory method 'filterChain' threw exception;
nested exception is org.springframework.security.config.annotation.AlreadyBuiltException:
This object has already been built
Run Code Online (Sandbox Code Playgroud)
谷歌之神说这是因为我打了httpSecurity.build() 两次电话,这是不允许的。然而:
AuthenticationManager实例;和AuthenticationManager就是运行httpSecurity.build(); 但httpSecurity.build()有人能帮我冲过终点线吗?感谢您的任何帮助!
我们正在做类似的事情:
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(realmService)
.passwordEncoder(passwordEncoder);
...
httpSecurity.addFilter(apiAuthenticationFilter(ctx -> httpSecurity.getSharedObject(AuthenticationManager.class)));
...
}
public ApiAuthenticationFilter apiAuthenticationFilter(AuthenticationManagerResolver<?> authenticationManagerResolver) {
return new ApiAuthenticationFilter(
authenticationManagerResolver,
tokenService,
objectMapper,
authenticationFactory
);
}
Run Code Online (Sandbox Code Playgroud)
AuthenticationManager由于仅在运行时需要实例,因此足以在配置阶段将供应商传递给过滤器
UPD。
好吧,现在很清楚为什么您需要引用AuthenticationManager.
第一个选项:
如果ApiAuthorizationFilter您实际上不需要扩展BasicAuthenticationFilter- 只需让它spring-security完成它的工作并通过 启用基本身份验证httpSecurity.httpBasic()。因为ApiAuthenticationFilter可以传递AuthenticationManagerResolver或Supplier<AuthenticationManager>传递给它:
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(realmService)
.passwordEncoder(passwordEncoder);
...
httpSecurity.addFilter(apiAuthenticationFilter(() -> httpSecurity.getSharedObject(AuthenticationManager.class)));
...
}
public ApiAuthenticationFilter apiAuthenticationFilter(Supplier<AuthenticationManager> authenticationManagerSupplier) {
return new ApiAuthenticationFilter(
authenticationManagerSupplier,
tokenService,
objectMapper,
authenticationFactory
);
}
Run Code Online (Sandbox Code Playgroud)
public class ApiAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private Supplier<AuthenticationManager> authenticationManagerSupplier;
private ObjectMapper objectMapper;
private ApiAuthenticationFactory authenticationFactory;
private TokenService tokenService;
public ApiAuthenticationFilter(
Supplier<AuthenticationManager> authenticationManagerSupplier,
TokenService tokenService,
ObjectMapper objectMapper,
ApiAuthenticationFactory authenticationFactory) {
super();
this.tokenService = tokenService;
this.objectMapper = objectMapper;
this.authenticationFactory = authenticationFactory;
init();
}
private void init() {
setFilterProcessesUrl("/v1/auth/sign-in");
}
@Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
try {
SignInRequest signInRequest = objectMapper.readValue(request.getInputStream(), SignInRequest.class);
Authentication authentication = authenticationFactory
.createAuthentication(signInRequest.getEmail(), signInRequest.getPassword());
// perform authentication and -- if successful -- populate granted authorities
return getAuthenticationManager().authenticate(authentication);
} catch (IOException e) {
throw new BadCredentialsException("malformed sign-in request payload", e);
}
}
@Override
protected void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain,
Authentication authentication) {
// called if-and-only-if the attemptAuthentication method above is successful
ApiAuthentication apiAuthentication = (ApiAuthentication) authentication;
TokenPair tokenPair = tokenService.generateTokenPair(apiAuthentication);
response.setStatus(HttpServletResponse.SC_OK);
try {
response.getWriter().write(objectMapper.writeValueAsString(tokenPair));
} catch (IOException e) {
throw new ApiServiceException(e);
}
}
@Override
protected AuthenticationManager getAuthenticationManager() {
if (this.authenticationManager == null) {
this.authenticationManager = authenticationManagerSupplier.get();
}
return this.authenticationManager;
}
}
Run Code Online (Sandbox Code Playgroud)
第二个选项
编写您自己的 实现AbstractHttpConfigurer,应如下所示:
public class ApiSecurityBuilderConfigurer<H extends HttpSecurityBuilder<H>>
extends AbstractHttpConfigurer<ApiSecurityBuilderConfigurer<H>, H> {
private TokenService tokenService;
private ObjectMapper objectMapper;
private ApiAuthenticationFactory apiAuthenticationFactory;
public ApiSecurityBuilderConfigurer<H> tokenService(TokenService tokenService) {
this.tokenService = tokenService;
return this;
}
public ApiSecurityBuilderConfigurer<H> objectMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
return this;
}
public ApiSecurityBuilderConfigurer<H> apiAuthenticationFactory(ApiAuthenticationFactory ApiAuthenticationFactory) {
this.ApiAuthenticationFactory = ApiAuthenticationFactory;
return this;
}
@Override
public void configure(H builder) throws Exception {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
builder.addFilter(apiAuthenticationFilter(authenticationManager));
}
public ApiAuthenticationFilter apiAuthenticationFilter(AuthenticationManager authenticationManager) {
return new ApiAuthenticationFilter(authenticationManager, tokenService, objectMapper, authenticationFactory);
}
}
Run Code Online (Sandbox Code Playgroud)
@Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.apply(new ApiSecurityBuilderConfigurer())
.tokenService(tokenService)
.objectMapper(objectMapper)
.apiAuthenticationFactory(apiAuthenticationFactory);
...
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
854 次 |
| 最近记录: |