Swagger UI version 3 returning a 404 page but api-docs is working - spring-boot

I am trying to setup swagger-UI to test my springboot REST API rather than using postman however after going through a few tutorials and some questions i can not seem to get past the 404 error when trying to access the html page via my browser.
My dependencies :
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>3.0.0</version>
</dependency>
SpringConfig:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#Configuration
#EnableSwagger2
public class SpringFoxConfig {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
and my controller
#Controller
#CrossOrigin
#RequestMapping(path = "/api")
public class AppController {
#ResponseBody
#PostMapping(path ="/api")
public String home() {
return "Hello World";
}
#GetMapping(path = { "/api/Teacher/{id}"})
public TeacherPayload getTeacher(#PathVariable(required=false,name="id") String id) throws
Exception{
if (id != null) {
return teacher.getTeacher(id);
} else {
return teacher.getTeachers();
}
....
I changed my port number fro the default 8080 to 3005 but i do not think that should be the problem as i tried reverting back to 8080 to no avail.
Edit: My security config is as follows, note i permitted all paths to bypass the security whilst testing
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsServiceImpl jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// configure AuthenticationManager so that it knows from where to load
// user for matching credentials
// Use BCryptPasswordEncoder
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/authenticate");
web.ignoring().antMatchers("/recoverPortal");
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// We don't need CSRF for this example
httpSecurity.cors();
httpSecurity.csrf().disable()
// dont authenticate this particular request
.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "**").permitAll()
.antMatchers("/**").permitAll()
.antMatchers("/newUser").authenticated()
.antMatchers("/newUser").authenticated()
.antMatchers("/admin/**").hasAnyAuthority("USER_ADMIN")
.antMatchers("/resetPassword").authenticated()// all other requests need to be authenticated
.anyRequest().authenticated().and().
// make sure we use stateless session; session won't be used to
// store user's state.
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}

What endpoint are you hitting? It should be /swagger-ui/ at the end, not /swagger-ui.html. Also, I think you can omit the springfox-swagger-ui dependency. All I need in my setup is the springfox-boot-starter one.

Related

Spring auth server code grant returns 401 unauthorized for endpoint /oauth2/authorize via Postman

Using the spring auth server dependency 0.3.0:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>0.3.0</version>
</dependency>
I got these two config files:
#Configuration
#ComponentScan(basePackageClasses = AuthorizationServerConfig.class)
#Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {
private final RegisteredClientProvider registeredClientProvider;
#Autowired
public AuthorizationServerConfig(RegisteredClientProvider registeredClientProvider) {
this.registeredClientProvider = registeredClientProvider;
}
#Bean
#Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
#ConditionalOnMissingBean
public RegisteredClientRepository registeredClientRepository() {
RegisteredClient codeClient = RegisteredClient.withId(UUID.randomUUID().toString())
.clientId("code-auth-client")
.clientSecret("{noop}secret")
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("http://127.0.0.1:8080/redirect/")
.scope("read-access")
.build();
return new InMemoryRegisteredClientRepository(codeClient);
}
}
__
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
public class SpringSecurityConfiguration {
#Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
#Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests(authorizeRequests ->
authorizeRequests.anyRequest().authenticated()
)
.formLogin(withDefaults());
return http.build();
}
}
I use the folloing parameters to fetch the authorization code in order to trade it for the token itself:
Unfortunately the application responses with 401 unauthorized:
GET
http://localhost:9000/oauth2/authorize?response_type=code&state=&client_id=code-auth-client&scope=read-access&redirect_uri=http%3A%2F%2F127.0.0.1%3A8080%2Fredirect%2F 401
I tried to fix it in the SecurityFilterChain beans but I couldn't fix it so far. Basically I am using this example https://www.baeldung.com/spring-security-oauth-auth-server (without the clients tho).
EDIT: I've noticed that the parameter "grant_type=authorization_code" was missing. Appending that parameter did not work tho.
Removing #Import(OAuth2AuthorizationServerConfiguration.class) fixed the issue.

Mocking JWT token in #SpringBootTest with WebTestClient

I have the following controller class in a Spring Boot project:
#GetMapping
public ResponseEntity<UserResponse> getUser(#AuthenticationPrincipal CustomUserDetails userDetails) {
try {
final UserResponse userData = userService.getUser(userDetails.getId());
return ResponseEntity.ok(userData);
} catch (UserNotFoundException e) {
log.error("User with id {} not found", userDetails.getId());
return ResponseEntity.notFound().build();
}
}
This resource is only accessible if the client sends a JWT token with Authorization: Bearer <token>. The CustomUserDetails are provided by a CustomUserDetailsService after having parsed the JWT token via a JwtRequestFilter.
Now I'd like to write a #SpringBootTest which uses a real HTTP client calling this resource. My first idea was to use a MockMvc object but then I read about the WebTestClient provided by Spring.
However I don't yet understand how I would be able to mock the JWT token. This is my initial attempt:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWebTestClient
public class UserControllerIT {
#Autowired
private ApplicationContext context;
private WebTestClient webTestClient;
#MockBean
private UserRepo userRepo;
#BeforeEach
public void setup() {
webTestClient = WebTestClient
.bindToApplicationContext(context)
.apply(springSecurity())
.configureClient()
.build();
}
#Test
#WithMockUser
public void someTest() {
final User user = createUser("foo#bar.com", "my-password");
when(userRepo.findById(anyLong())).thenReturn(Optional.of(user));
webTestClient
.mutateWith(mockJwt())
.get()
.uri("/user")
.header(ACCEPT, APPLICATION_JSON_VALUE)
.exchange()
.expectStatus().is2xxSuccessful();
}
This test fails with the following exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'webHandler' available
However I'm not sure if my approach makes sense. Is WebTestClient the "correct" way? Or do I need to use a WebClient?
My security configuration:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final JwtRequestFilter jwtRequestFilter;
public SecurityConfiguration(JwtRequestFilter jwtRequestFilter) {
this.jwtRequestFilter = jwtRequestFilter;
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors().and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.mvcMatcher("/services/**").authorizeRequests()
.mvcMatchers(PUBLIC_RESOURCES).permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
WebTestClient is the recommended replacement for TestRestTemplate.
A deep-dive into the Spring Security source at https://github.com/spring-projects/spring-security, shows some examples of use of WebTestClient with JWT. E.g.: ServerOAuth2ResourceServerApplicationITests
Given that you have a service JwtTokenProvider that is responsible for generating JWT-tokens, a test may look like below. Or, if possible, you may use constant tokens like in ServerOAuth2ResourceServerApplicationITests.
package no.yourcompany.yourapp.yourpackage;
import no.yourcompany.yourapp.configuration.JwtTokenProvider;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.test.web.reactive.server.WebTestClient;
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
#AutoConfigureWebTestClient
public class YourIntegrationTest {
#Autowired
WebTestClient webTestClient;
#Autowired
JwtTokenProvider jwtTokenProvider;
#Test
public void postTest_withValidToken_receiveOk() {
var tokenString = jwtTokenProvider.createToken(
new UsernamePasswordAuthenticationToken("test-user", "P4ssword"));
webTestClient
.post().uri("/test")
.headers(http -> http.setBearerAuth(tokenString))
.exchange()
.expectStatus().isOk();
}
}
For WebTestClient, add this to POM
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
<scope>test</scope>
</dependency>

Spring Security Always returning 403 forbidden, Access denied

I want to enable admin to access admin page and do admin stuff, but when I try to do that by setting that the url with /admin/** can only be accessed by user with role admin, it returns 403 Forbidden, access denied. But the user has authorities set to ROLE_ADMIN I checked. What am I doing wrong?
My Controller for user login
#RestController
public class UserController {
#Autowired
AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private AuthorityService authorityService;
#Autowired
private UserAuthorityService userAuthorityService;
#Autowired
TokenUtils tokenUtils;
#Autowired
private UserService userService;
#RequestMapping(value = "/api/login", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> login(#RequestBody LoginDTO loginDTO) {
try {
// System.out.println(loginDTO.getUsername() + " " + loginDTO.getPassword());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
loginDTO.getUsername(), loginDTO.getPassword());
Authentication authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails details = userDetailsService.loadUserByUsername(loginDTO.getUsername());
return new ResponseEntity<String>(tokenUtils.generateToken(details), HttpStatus.OK);
} catch (Exception ex) {
return new ResponseEntity<String>("Invalid login", HttpStatus.BAD_REQUEST);
}
}
#RequestMapping(value = "/api/register", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> register(#RequestBody RegisterDTO registerDTO) {
try {
System.out.println(registerDTO);
User user = userService.findUserByUsername(registerDTO.getUsername());
// // Check if user with that username exists
if(user != null){
// User with that username is found
return new ResponseEntity<String>("User with that username exists", HttpStatus.BAD_REQUEST);
}
// We need to save the user so his ID is generated
User newUser = userService.saveUser(new User(registerDTO));
UserAuthority userAuthority = userAuthorityService.save(new UserAuthority(newUser, authorityService.findOneByName("User")));
Set<UserAuthority> authorities = new HashSet<>();
authorities.add(userAuthority);
newUser.setUserAuthorities(authorities);
User savedUser = userService.save(newUser);
return new ResponseEntity<String>("You have registered successfully with username " + savedUser.getUsername(), HttpStatus.OK);
} catch (Exception ex) {
return new ResponseEntity<String>("Invalid register", HttpStatus.BAD_REQUEST);
}
}
}
I can say that I test my app with postman and login and registration are working fine. When the user is logged in I can the token with the correct data and users authorities, but why when I try to access /admin/building/add url it is returning 403 error?
My Controller for adding building for admin page:
#RestController
public class BuildingController {
#Autowired
private BuildingService buildingService;
#RequestMapping(value = "/admin/building/add", method = RequestMethod.POST, produces = "text/html")
public ResponseEntity<String> addBuilding(#RequestBody BuildingDTO buildingDTO) {
try{
Building newBuilding = new Building(buildingDTO);
return new ResponseEntity<String>(newBuilding.getName(), HttpStatus.OK);
}catch (Exception ex) {
return new ResponseEntity<String>("Data was not valid", HttpStatus.BAD_REQUEST);
}
}
}
My SecurityConfiguration.java
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configureAuthentication(
AuthenticationManagerBuilder authenticationManagerBuilder)
throws Exception {
authenticationManagerBuilder
.userDetailsService(this.userDetailsService).passwordEncoder(
passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public AuthenticationTokenFilter authenticationTokenFilterBean()
throws Exception {
AuthenticationTokenFilter authenticationTokenFilter = new AuthenticationTokenFilter();
authenticationTokenFilter
.setAuthenticationManager(authenticationManagerBean());
return authenticationTokenFilter;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/index.html", "/view/**", "/app/**", "/", "/api/login", "/api/register").permitAll()
// defined Admin only API area
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest()
.authenticated()
.and().csrf().disable();
//if we use AngularJS on client side
// .and().csrf().csrfTokenRepository(csrfTokenRepository());
//add filter for adding CSRF token in the request
httpSecurity.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
// Custom JWT based authentication
httpSecurity.addFilterBefore(authenticationTokenFilterBean(),
UsernamePasswordAuthenticationFilter.class);
}
/**
* If we use AngularJS as a client application, it will send CSRF token using
* name X-XSRF token. We have to tell Spring to expect this name instead of
* X-CSRF-TOKEN (which is default one)
* #return
*/
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
I should mention that I am using Angularjs for frontend, but even so I can login and the correct authorities are displayed for that user. But for some reason I can not access the admin page, even if I login as admin.
Also I tried .hasAuthority("ROLE_ADMIN") and .hasRole("ROLE_ADMIN")(which displays an error for ROLE_) and so I changed it to .hasRole("ADMIN") but it is still not working.
In the database the role for admin is saved as ROLE_ADMIN.
Try like this :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static String REALM="MY_TEST_REALM";
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("bill").password("abc123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("tom").password("abc123").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/**").hasRole("ADMIN")
.and().httpBasic().realmName(REALM).authenticationEntryPoint(getBasicAuthEntryPoint())
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);//We don't need sessions to be created.
}
#Bean
public CustomBasicAuthenticationEntryPoint getBasicAuthEntryPoint(){
return new CustomBasicAuthenticationEntryPoint();
}
/* To allow Pre-flight [OPTIONS] request from browser */
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
}
For a complet configuration example : Secure Spring REST API using Basic Authentication
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();// We don't need sessions to be created.
}
}
This did it for me. Now I am able to submit my post requests successfully
Try this in SecurityConfig:
.antMatchers("/api/admin").access("hasRole('ADMIN')")
.antMatchers("/api/user").access("hasRole('ADMIN') or hasRole('USER')")

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()

How to get Spring Security to respond to Pre-Flight CORS request with OAUTH2

I am not a Java programmer by profession. I am working on building an app for our lab. It started out as a Spring MVC app with JSP files. It has now migrated to a Spring REST API that uses OAUTH2 as a standalone authentication and authorization server. I am using a MySQL database to store my data and users. I can successfully get an access_token issued if I use Postman to access the server. However if I use the Angular 2 client I setup I cannot get past the pre-flight OPTIONS request sent by the client to the server. I have tried several different ways to configure a CORS filter, but I cannot get that filter to get implemented and I always get a 401 code returned for the pre-flight request. I have tried implementing the suggested solution in the Spring blog https://spring.io/blog/2015/06/08/cors-support-in-spring-framework but that hasn't worked yet.
In my pom.xml file I am using these versions of Spring
Spring Framework - 4.3.2.RELEASE
Spring Security - 4.1.2.RELEASE
Spring Security OAUTH2 - 2.0.10.RELEASE
I also included Spring Boot 1.4.0 when trying to use FilterRegistrationBean
Here is my code as it is right now.
WebConfig Class:
#EnableWebMvc
#Configuration
#ComponentScan(basePackages={"eng.lab"})
#Import({ApplicationContext.class})
#PropertySource("classpath:application.properties")
public class WebConfig extends WebMvcConfigurerAdapter{
/* #Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}*/
}
SecurityConfig Class:
#Configuration
#EnableWebSecurity
#PropertySource("classpath:application.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(
"select username,password, enabled from user where username=?")
.authoritiesByUsernameQuery(
"select u.username, r.role from User u, Role r where r.id=u.roleId and u.userName=?");
}
#Autowired
private ClientDetailsService clientDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
//.requestMatchers(CorsUtils::isCorsRequest).permitAll()
.antMatchers("/oauth/token").permitAll();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
}
MethodSecurityConfig Class:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#SuppressWarnings("unused")
#Autowired
private SecurityConfig securityConfig;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
WebAppInitializer Class:
public class WebAppInitializer implements WebApplicationInitializer{
#Override
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(WebConfig.class);
container.addListener(new ContextLoaderListener(rootContext));
DelegatingFilterProxy filter = new DelegatingFilterProxy("springSecurityFilterChain");
DispatcherServlet dispatcherServlet = new DispatcherServlet(rootContext);
ServletRegistration.Dynamic registration = container.addServlet("dispatcherServlet", dispatcherServlet);
container.addFilter("springSecurityFilterChain", filter).addMappingForUrlPatterns(null, false, "/*");
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
SimpleCorsFilter Class:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {
public SimpleCorsFilter() {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
If I include a second WebSecurityConfig class I do get a 403 code instead.
MyWebSecurity Class:
#Configuration
#EnableWebSecurity
#Order(-1)
public class MyWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll();
}
}
Any suggestions as to how to get past the pre-flight issue beyond waiting for the DATAREST-573 bug to be fixed?
UPDATE: I tried Benjamin's suggestion, but not sure I implemented the solution properly. I have edited my WebConfig class as folows but the pre-flight still fails. The filter still isn't getting applied.
package eng.lab.config;
import javax.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#EnableWebMvc
#Configuration
#ComponentScan(basePackages={"eng.lab"})
#Import({ApplicationContext.class})
#PropertySource("classpath:application.properties")
public class WebConfig extends WebMvcConfigurerAdapter{
//more custome rule beans
#Bean
public FilterRegistrationBean simpleCORSFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new SimpleCorsFilter());
registration.addUrlPatterns("/*");
//registration.addInitParameter("paramName", "paramValue");
registration.setName("simpleCorsFilter");
registration.setOrder(0);
return registration;
}
#Bean(name = "simpleCorsFilter")
public Filter simpleCorsFilter() {
return new SimpleCorsFilter();
}
/* #Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}*/
}
Your CORS filter is not registered. As this is a standard javax.servlet.Filter instance, you need to register it against a FilterRegistrationBean or declare it in your web.xml file.
You can refer to this SO question for more details: How to add a filter class in Spring Boot?

Resources