Spring boot 3 multiple security filter chains doesn't work - spring-boot

I am using Spring Boot 3.0.1. In my WebSecurityConfig class, I want to filter 2 types of api urls.
So I have 2 SecurityFilterChains. This is what I want to achieve.
1.) Login api: This one, I want to permit this url and save the session id to the database using Spring Sessions.
2.) Other white apis: I want to permit some urls without any security/session checks
3.) Any other api calls need to have the x-auth-token
The following code has only 1 SecurityFilterChain and it works perfectly fine to satisfy all the above 1,2,3 points.
For 1, it will create the session id in the spring_session table with the login user as the principal_name.
For 2, it will also create another session id in spring_session table with "client" as the principal_name.
I do not want to create a session id for 2. I only want to create session id when I call the 1 (login api). So I believe that I have to write 2 Filter chains. First one only for login api and create session id, second one for all the white apis to go through with out security/session checks.
How do I write 2 security filter chains?
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfig {
#Autowired
private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private PasswordEncoder passwordEncoder;
#Bean
public AuthenticationManager authenticationManager(HttpSecurity http)
throws Exception {
var daoAC = new DaoAuthenticationConfigurer(userDetailsService);
daoAC.passwordEncoder(passwordEncoder);
var builder = http.getSharedObject(AuthenticationManagerBuilder.class);
builder.apply(daoAC);
return builder.build();
}
private static final String[] AUTH_WHITELIST = {
"/api/usermanager/auth/login",
"/api/usermanager/auth/app-login",
"/api/usermanager/auth/resetPassword",
"/api/usermanager/auth/health",
"/api/usermanager/back-office/login",
"/actuator/**",
"/get-user-names",
"/get-users",
"/get-user",
"/api/usermanager/users/activate",
"/actuator/**",
"/health/**",
"/api/usermanager/org",
"/api/usermanager/org/*/theme",
"/api/usermanager/image/org/*/all",
"/api/usermanager/image/org/*/logo.png"
};
#Bean
public SecurityFilterChain loginFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable().exceptionHandling()
.authenticationEntryPoint(restAuthenticationEntryPoint)
.and().securityContext((securityContext) -> securityContext.requireExplicitSave(false))
.cors()
.and()
.httpBasic()
.and()
.securityMatcher("/api/**")
.authorizeHttpRequests(
requests -> requests.
requestMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated()
).httpBasic(withDefaults())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.sessionFixation()
.migrateSession()
.maximumSessions(1)
.expiredUrl("/sessionExpired.html")
.maxSessionsPreventsLogin(false));
return http.build();
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers(HttpMethod.GET,
"/docs/**", "/resources/**", "/static/**", "/img/**");
}
#Bean
public AuthenticationFailureHandler myFailureHandler() {
return new CustomAuthenticationFailureHandler();
}
#Bean
public HttpSessionIdResolver httpSessionStrategy() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
#Bean
public HttpSessionIdResolver httpSessionIdResolver() {
return HeaderHttpSessionIdResolver.xAuthToken();
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
}

Create more SecurityFilterChain Bean and add #Order(1) and #Order(2) annotations.
Check the Spring docs: https://docs.spring.io/spring-security/reference/servlet/architecture.html#servlet-securityfilterchain

Related

Spring Boot 3 security cannot access H2 console - 403

I'm in the process of re-learning Spring security in Spring Boot 3. Things changed a little and for some reason the same settings working for WebSecurityConfigurerAdapter's config method will not work in SecurityFilterChain.
HERE IS SOME CODE FROM PREVIOUS SETUPS- WORKING
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final AppUserService userService;
private final PasswordEncoder bCryptPasswordEncoder;
public SecurityConfig(AppUserService userService, PasswordEncoder bCryptPasswordEncoder) {
this.userService = userService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
private static final String[] SWAGGER = {
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/v3/api-docs/**",
"/swagger-ui/**"
};
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.cors(c -> {
CorsConfigurationSource cs = request -> {
CorsConfiguration cc = new CorsConfiguration();
cc.setAllowedOrigins(List.of("*"));
cc.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
cc.setAllowedHeaders(List.of("Origin", "Content-Type", "X-Auth-Token", "Access-Control-Expose-Header",
"Authorization"));
cc.addExposedHeader("Authorization");
cc.addExposedHeader("User-Name");
return cc;
};
c.configurationSource(cs);
});
http.headers().frameOptions().disable();
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.authorizeRequests().antMatchers("/login/**").permitAll();
http.authorizeRequests().antMatchers("/h2-console/**").permitAll();
http.authorizeRequests().antMatchers(SWAGGER).permitAll();
http.authorizeRequests().antMatchers("/users/password-reset-request").permitAll();
http.authorizeRequests().antMatchers("/users/password-change").permitAll();
http.authorizeRequests().antMatchers("/users/**").hasAnyAuthority("ADMIN");
http.authorizeRequests().antMatchers("/favorites/**").hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(GET).hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(POST).hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(PUT).hasAnyAuthority("USER");
http.authorizeRequests().antMatchers(DELETE).hasAnyAuthority("MODERATOR");
http.authorizeRequests().anyRequest().authenticated();
http.addFilter(new CustomAuthenticationFilter(authenticationManager()));
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected UserDetailsService userDetailsService() {
return userService;
}
}
NOW SINCE
WebSecurityConfigurerAdapter
is no longer available:
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final AuthenticationProvider authenticationProvider;
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
http.headers().frameOptions().disable();
http.sessionManagement().sessionCreationPolicy(STATELESS);
http.authorizeHttpRequests().requestMatchers("/h2-console/**").permitAll();
http.authorizeHttpRequests().requestMatchers("/auth/**").permitAll();
http.authorizeHttpRequests().anyRequest().authenticated();
http.authenticationProvider(authenticationProvider);
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Long story short- previously working
http.headers().frameOptions().disable();
http.authorizeRequests().antMatchers("/h2-console/**").permitAll();
Will not work as
http.headers().frameOptions().disable();
http.authorizeHttpRequests().requestMatchers("/h2-console/**").permitAll();
Application.properties for H2 database are exact same, copied. I didn't changed default url for H2. It seems its the Spring Security standing in the way.
Please advice what to do.
Do you have any knowlage if anything changed for H2 setup since previous Spring Boot?
EDIT: If I simply http.authorizeHttpRequests().anyRequest().permitAll();, the console will work. It must be security related
The H2ConsoleAutoConfiguration will register a Servlet for H2's Web Console, therefore, the servletPath property is needed in order to use the MvcRequestMatcher, like so:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) {
// ...
MvcRequestMatcher h2RequestMatcher = new MvcRequestMatcher(introspector, "/**");
h2RequestMatcher.setServletPath("/h2-console");
http.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(h2RequestMatcher).permitAll()
// ...
);
}
In summary, we are permitting every (/**) request under the h2-console servlet path.
Another option is to use PathRequest.toH2Console() as shown in the Spring Boot H2 Console's documentation, which in turn will create an AntPathRequestMatcher for you.
#Bean
public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) {
// ...
http.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(PathRequest.toH2Console()).permitAll()
// ...
);
}
This problem has also been answered in the project's repository

Spring Security pre authentication filter gets called every time

I have a Spring Boot app where I have custom pre authentication filter. I want to ignore security for health URL but I am not able to do it. Below is my configuration.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(1000)
public class UserSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> userDetailsService;
#Autowired
private IUserIdentityService iUserIdentityService;
#Value("${spring.profiles.active}")
private String profileType;
#Autowired
#Qualifier("publicEndpoints")
private Map<String, String> publicEndpoints;
#Autowired
private GenericDataService genericDataService;
#Bean(name = "preAuthProvider")
PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(userDetailsService);
return provider;
}
#Bean
AppPreAuthenticatedProcessingFilter appPreAuthenticatedProcessingFilter() throws Exception {
appPreAuthenticatedProcessingFilter filter = new appPreAuthenticatedProcessingFilter(iUserIdentityService, genericDataService);
filter.setAuthenticationManager(super.authenticationManagerBean());
filter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
filter.setCheckForPrincipalChanges(true);
return filter;
}
/**
* Uses JEE pre-authentication filter, that assumes that the user has been
* pre-authenticated into the container.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/health/e2e").permitAll()
.and()
.addFilter(appPreAuthenticatedProcessingFilter())
.authorizeRequests()
.anyRequest().authenticated()
.and()
.authenticationProvider(preauthAuthProvider())
.csrf()
.csrfTokenRepository(this.csrfTokenRepository())
.and()
.httpBasic().disable();
// Disabling the CSRF implementation, if "csrf.disabled" property set to "true"
// in System Properties.
if (!StringUtils.isEmpty(profileType) && profileType.equals("local")) {
http.csrf().disable();
}
}
/**
* Method to ignore web security for urls
*/
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("*/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**", "/health/e2e", "*/health/e2e", "**/health/e2e");
}
/**
* Method to to return CsrfTokenRepository
*/
private CsrfTokenRepository csrfTokenRepository() {
CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
tokenRepository.setCookiePath("/");
return tokenRepository;
}
}
Custom authentication filter looks like
#Slf4j
public class AppPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
private IUserIdentityService iUserIdentityService;
private GenericDataService genericDataService;
public AppPreAuthenticatedProcessingFilter(IUserIdentityService iUserIdentityService, GenericDataService genericDataService) {
this.iUserIdentityService = iUserIdentityService;
this.genericDataService = genericDataService;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
return iUserIdentityService.getUserName();
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return AppConst.DEFAULT_CREDENTIAL;
}
}
I am not sure why /health/e2e is secured?
P.S. I tried removing #Bean from pre auth filter but in that case, filter never gets called for any request.
The problem is two fold
Your security setup contains an error
The filter is added to the regular filter bean as well.
With your current security setup the AppPreAuthenticatedProcessingFilter is added only to the /health/e2d URL. Your attempt to fix something has actually broken things instead.
Your configuration should be something along the lines of
http.authorizeRequests().anyRequest().authenticated()
.and().httpBasic()
.and().authenticationProvider(preauthAuthProvider())
.csrf().csrfTokenRepository(this.csrfTokenRepository())
.and().addFilterBefore(appPreAuthenticatedProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
// in System Properties.
if (!StringUtils.isEmpty(profileType) && profileType.equals("local")) {
http.csrf().disable();
}
Spring Boot will by default register an javax.servlet.Filter in the normal filter chain, to disable this you need to add a FilterRegistrationBean to disable this.
#Bean
public FilterRegistrationBean<AppPreAuthenticatedProcessingFilter> preAuthenticationFilterRegistrationBean(AppPreAuthenticatedProcessingFilter filter) {
FilterRegistrationBean<AppPreAuthenticatedProcessingFilter> frb = new FilterRegistrationBean<>(filter);
frb.setEnabled(false);
return frb;
}

Spring Oauth 2 Facebook Authentication Redirects User To My Home Page

I am trying to redirect a user who have been authenticated to another page other than the home page. I am using spring boot 1.5.6 and Oauth 2. User is authenticated but was redirected to the home page. I don't understand why this is happening. Please, someone should help me. Some answers to related problem on stackoverflow and the internet didn't help me.
Here is my SecurityConfig file
#Configuration
#EnableGlobalAuthentication
#EnableOAuth2Client
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(2)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
protected final Log logger = LogFactory.getLog(getClass());
#Autowired
private OAuth2ClientContext oauth2ClientContext;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private GeneralConfig generalConfig;
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/user*")
.access("hasRole('CUSTOMER')")
.and()
.formLogin()
.loginPage("/loginUser")
.loginProcessingUrl("/user_login")
.failureUrl("/loginUser?error=loginError")
.defaultSuccessUrl("/customer/dashboard")
.and()
.logout()
.logoutUrl("/user_logout")
.logoutSuccessUrl("/loginUser").permitAll()
.deleteCookies("JSESSIONID")
.and()
.exceptionHandling()
.accessDeniedPage("/403")
.and()
.csrf().disable()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).
passwordEncoder(bCryptPasswordEncoder());
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws
Exception {
auth.userDetailsService(userDetailsService);
}
#Bean
public FilterRegistrationBeanoauth2ClientFilterRegistration
(OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new
OAuth2ClientAuthenticationProcessingFilter(path);
OAuth2RestTemplate template = new
OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
UserInfoTokenServices tokenServices = new
UserInfoTokenServices(client.getResource().getUserInfoUri(),
client.getClient().getClientId());
tokenServices.setRestTemplate(template);
filter.setTokenServices(tokenServices);
return filter;
}
private Filter ssoFilter() {
CompositeFilter filter = new CompositeFilter();
List<Filter> filters = new ArrayList<>();
filters.add(ssoFilter(facebook(), "/signin/facebook"));
filters.add(ssoFilter(google(), "/signin/google"));
filter.setFilters(filters);
return filter;
}
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#ConfigurationProperties("google")
public ClientResources google() {
return new ClientResources();
}
#Bean
#ConfigurationProperties("facebook")
public ClientResources facebook() {
return new ClientResources();
}
}
From the SecurityConfig I expect the user upon successful authentication to be redirected to customer/dashboard so that I can do further processing. I know the user is authenticated because I can access their data. It's not just redirecting to the right page
But instead it keep redirecting the user to the home page. What am I doing wrong? I also have another Security Config File for admin. I can provide it if required.
To change the default strategy, you have to set an AuthenticationSuccessHandler, see AbstractAuthenticationProcessingFilter#setAuthenticationSuccessHandler:
Sets the strategy used to handle a successful authentication. By default a SavedRequestAwareAuthenticationSuccessHandler is used.
Your modified code:
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(client.getResource().getUserInfoUri(),client.getClient().getClientId());
tokenServices.setRestTemplate(template);
filter.setTokenServices(tokenServices);
filter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/customer/dashboard")‌​;
return filter;
}

multiple oauth2 services and configurations

I have an existing Spring application which is using configuration A extending
ResourceServerConfigureAdapter to secure APIs against an internal oauth service A.
I am trying to add another configuration B extending WebSecurityConfigurerAdapter which authenticates against an external oauth provider.
The aim is to continue with B determine authentication for /api/ related endpoints while A determines overall login to the web application.
Following is the existing code using ResourceServerConfigureAdapter:-
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Value("${oauth.clientId}")
private String clientId;
#Value("${oauth.clientSecret}")
private String clientSecret;
#Autowired
private RestTemplate restTemplate;
#Bean
public RemoteTokenServices remoteTokenServices() {
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setRestTemplate(restTemplate);
remoteTokenServices.setClientId(clientId);
remoteTokenServices.setClientSecret(clientSecret);
remoteTokenServices.setCheckTokenEndpointUrl("srvc://myservice/api/v2/oauth/check_token");
return remoteTokenServices;
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(null);
resources.tokenServices(remoteTokenServices());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.anonymous()
.and().authorizeRequests()
.antMatchers("/api/secured/**").authenticated()
.antMatchers("/api/**").permitAll();
}}
Following is the code using WebSecurityConfigurerAdapter:-
#SpringBootApplication
#EnableOAuth2Client
#EnableMBeanExport(registration = RegistrationPolicy.IGNORE_EXISTING)
public class DemoService extends WebSecurityConfigurerAdapter {
#Autowired
OAuth2ClientContext oauth2ClientContext;
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http.antMatcher("/**").authorizeRequests().antMatchers("/", "/login**", "/webjars/**").permitAll().anyRequest()
.authenticated().and().exceptionHandling()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/")).and().logout()
.logoutSuccessUrl("/").permitAll().and().csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).and()
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
// #formatter:on
}
public static void main(String[] args) {
SpringApplication.run(DemoService.class, args);
}
#Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter googleFilter = new OAuth2ClientAuthenticationProcessingFilter(
"/login/google");
OAuth2RestTemplate googleTemplate = new OAuth2RestTemplate(google(), oauth2ClientContext);
googleFilter.setRestTemplate(googleTemplate);
UserInfoTokenServices tokenServices = new UserInfoTokenServices(googleResource().getUserInfoUri(),
google().getClientId());
tokenServices.setRestTemplate(googleTemplate);
googleFilter.setTokenServices(
new UserInfoTokenServices(googleResource().getUserInfoUri(), google().getClientId()));
return googleFilter;
}
#Bean
#ConfigurationProperties("google.client")
public AuthorizationCodeResourceDetails google() {
return new AuthorizationCodeResourceDetails();
}
#Bean
#ConfigurationProperties("google.resource")
public ResourceServerProperties googleResource() {
return new ResourceServerProperties();
}
}
Both of them individually run fine but put together in the same project, problems start showing up. Everything compiles and runs fine but when I hit localhost:8080/ following happens -
The page loads fine but when I hit localhost:8080/login/google, it shows me a whitelabel error page like following
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Thu Apr 06 13:22:27 IST 2017
There was an unexpected error (type=Not Found, status=404).
Not Found
I try to read a bit about ResourceServerConfigureAdapter vs WebSecurityConfigurerAdapter and I could only understand that there is some kind of filter-order that determines the priority of each configurer. But that hasn't helped me fix the issue. Any pointers?
Update:
There's another adapter for Swagger integration that is also part of the project.
#EnableSwagger2
#Configuration
public class SwaggerConfiguration extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addRedirectViewController("/docs", "/swagger-ui.html");
registry.addRedirectViewController("/docs/", "/swagger-ui.html");
registry.addRedirectViewController("/docs.json", "/v2/api-docs");
}
#Bean
public Docket swaggerSpringMvcPlugin() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(new ApiInfoBuilder()
.title("Spring Boot Service")
.description("Sample project documentation")
.contact("a#b.com")
.version("1.0")
.license("Apache")
.build())
.forCodeGeneration(true)
.ignoredParameterTypes(Principal.class)
.useDefaultResponseMessages(false)
.select()
.paths(documentedPaths())
.build();
}
private Predicate<String> documentedPaths() {
return or(
regex("/api.*"));
}
}
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
The OAuth2ClientAuthenticationProcessingFilter must after OAuth2ClientContextFilter, OAuth2ClientAuthenticationProcessingFilter will throw a redirect exception when the request is wrong(no code, etc...),
and the OAuth2ClientContextFilter will catch it and redirect to the userAuthorizationUri;
The BasicAuthenticationFilter is before OAuth2ClientContextFilter normal, , so you should change the order:
#Autowired
private OAuth2ClientContextFilter oAuth2ClientContextFilter;
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(oAuth2ClientContextFilter, ExceptionTranslationFilter.class)
.addFilterAfter(ssoFilter(), OAuth2ClientContextFilter.class);
}
UPDATE:
There is another place need to be updated, if you have multi chains, you should define the request match, the default value is '/**', and the default order of ResourceServerConfiguration is 3, the default order of WebSecurityConfigurerAdapter is 100, ResourceServerConfiguration has high priority.
// only handle the request start with `/api`
http.requestMatcher(new AntPathRequestMatcher("/api/**"))
http.anonymous()
.and().authorizeRequests()
.antMatchers("/api/secured/**").authenticated()
.antMatchers("/api/**").permitAll();
If you put the WebSecurityConfigurerAdapter before ResourceServerConfiguration by change the order, you should also config the WebSecurityConfigurerAdapter not to handler /api/**
// skip the request start with '/api'
http.requestMatcher(new RegexRequestMatcher("^(?!/api).*$", null))
.authorizeRequests().antMatchers("/", "/login/**", "/webjars/**").permitAll().anyRequest()
Update2:
.authorizeRequests().antMatchers("/", "/login**")
This matcher doesn't match /login/google, please update to /login/**, so it should be
.authorizeRequests().antMatchers("/", "/login/**").permitAll()

Spring - Add a check for 3rd parameter during authentication

At a glance, I have API Back-end App written in Spring Boot which uses JWT for secured data transmission. I want to add 3rd parameter for authorization, so I should have login, password and storeID parameters. I am inspired by this answer How implement Spring security when login page having more field apart from user name and password? but when I followed proposed solution my 3rd parameter in not used. My impression is that I am missing something important in Security Config. Could you please point to my mistake?
SecurityConfig
#SuppressWarnings("SpringJavaAutowiringInspection")
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint unauthorizedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationDetailsSource<HttpServletRequest, ?> webAuthenticationDetailsSourceImpl;
#Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
.authenticationProvider(myAuthProvider());
}
#Bean
public CustomUserDetailsAuthenticationProvider myAuthProvider() throws Exception {
CustomUserDetailsAuthenticationProvider provider = new CustomUserDetailsAuthenticationProvider();
provider.setPasswordEncoder(passwordEncoder());
provider.setUserDetailsService(userDetailsService);
return provider;
}
#Bean
public UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter() throws Exception {
UsernamePasswordAuthenticationFilter usernamePasswordAuthenticationFilter = new UsernamePasswordAuthenticationFilter();
usernamePasswordAuthenticationFilter.setAuthenticationManager(authenticationManager());
usernamePasswordAuthenticationFilter.setAuthenticationDetailsSource(webAuthenticationDetailsSourceImpl);
return usernamePasswordAuthenticationFilter;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// allow anonymous resource requests
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/favicon.ico",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated();
// Custom JWT based security filter
httpSecurity
.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// disable page caching
httpSecurity.headers().cacheControl();
}
}
I was under impression I can check against storeID field in WebAuthenticationDetailsSourceImpl, but looks like it has never been executed because I don't see anything related in log.
WebAuthenticationDetailsSourceImpl:
#Component
public class WebAuthenticationDetailsSourceImpl implements AuthenticationDetailsSource<HttpServletRequest, JwtAuthenticationRequest> {
#Override
public JwtAuthenticationRequest buildDetails(HttpServletRequest context) {
System.out.println("___#####_____");
System.out.println(context);
System.out.println("___#####_____");
return new JwtAuthenticationRequest();
}
}
cuz you don't insert "your" usernamePasswordAuthenticationFilter that set webAuthenticationDetailsSourceImpl to Spring Security's authentication filter chain.
perhaps current your authentication filter chain is
~
JwtAuthenticationTokenFilter
(Spring Security's original)UsernamePasswordAuthenticationFilter
~
hence,if you want to retrieve your additional parameter in "your" usernamePasswordAuthenticationFilter add this filter too like a JwtAuthenticationTokenFilter
but , if you want to simply retrieve parameter at JwtAuthenticationTokenFilter
use setAuthenticationDetailsSource at there
#Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
JwtAuthenticationTokenFilter authenticationTokenFilter = new JwtAuthenticationTokenFilter();
authenticationTokenFilter.setAuthenticationManager(authenticationManagerBean());
authenticationTokenFilter.setAuthenticationDetailsSource(webAuthenticationDetailsSourceImpl);
return authenticationTokenFilter;
}

Resources