diff --git a/build.gradle b/build.gradle index 310bf4b..e69a044 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ plugins { ext { springBootVersion = '2.7.0' + springOauth2Version = '5.7.1' springLog4jVersion = '2.3.12.RELEASE' jacksonVersion = '2.13.0' itextVersion = '7.2.5' @@ -67,6 +68,8 @@ dependencies { implementation "com.warrenstrange:googleauth:1.5.0" implementation "com.google.zxing:core:3.4.0" implementation "com.google.zxing:javase:3.4.0" + implementation "org.springframework.security:spring-security-oauth2-resource-server:${springOauth2Version}" + implementation "org.springframework.security:spring-security-oauth2-jose:${springOauth2Version}" testImplementation "org.hibernate.validator:hibernate-validator:6.1.0.Final" testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}") { exclude group: "com.vaadin.external.google", module:"android-json" @@ -107,7 +110,7 @@ project.archivesBaseName = 'iwa' group = 'com.microfocus.example' version = '1.0' description = 'IWA (Insecure Web App) Pharmacy Direct - an insecure web application for use in Fortify demonstrations' -// A JDK 1.8 is needed if the WebInspect Runtime Agent is being used +// A JDK 1.8 is needed if the WebInspect Runtime Agent is being used? //sourceCompatibility = JavaVersion.VERSION_1_8 //targetCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_11 diff --git a/src/main/java/com/microfocus/example/api/controllers/ApiMessageController.java b/src/main/java/com/microfocus/example/api/controllers/ApiMessageController.java index be5ba2f..de0fde4 100644 --- a/src/main/java/com/microfocus/example/api/controllers/ApiMessageController.java +++ b/src/main/java/com/microfocus/example/api/controllers/ApiMessageController.java @@ -64,7 +64,7 @@ public class ApiMessageController { @Autowired private UserService userService; - @Operation(summary = "Finds messages by keyword(s)", description = "Keyword search by %keyword% format", tags = {"message"}, security = @SecurityRequirement(name = "JWT Authentication")) + @Operation(summary = "Finds messages by keyword(s)", description = "Keyword search by %keyword% format", tags = {"messages"}, security = @SecurityRequirement(name = "JWT Authentication")) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Success", content = @Content(array = @ArraySchema(schema = @Schema(implementation = MessageResponse.class)))), @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(schema = @Schema(implementation = ApiStatusResponse.class))), @@ -92,7 +92,7 @@ public ResponseEntity> getMessagesByKeywords( } } - @Operation(summary = "Find message by Id", description = "Find a message by UUID", tags = {"message"}, security = @SecurityRequirement(name = "JWT Authentication")) + @Operation(summary = "Find message by Id", description = "Find a message by UUID", tags = {"messages"}, security = @SecurityRequirement(name = "JWT Authentication")) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Success", content = @Content(schema = @Schema(implementation = MessageResponse.class))), @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(schema = @Schema(implementation = ApiStatusResponse.class))), @@ -166,7 +166,7 @@ public ResponseEntity deleteMessage( return new ResponseEntity<>(apiStatusResponse, HttpStatus.OK); } - @Operation(summary = "Get users unread message count", description = "Get a users unread message count by their UUID", tags = {"message"}, security = @SecurityRequirement(name = "JWT Authentication")) + @Operation(summary = "Get users unread message count", description = "Get a users unread message count by their UUID", tags = {"messages"}, security = @SecurityRequirement(name = "JWT Authentication")) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Success", content = @Content(schema = @Schema(implementation = MessageResponse.class))), @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content(schema = @Schema(implementation = ApiStatusResponse.class))), diff --git a/src/main/java/com/microfocus/example/config/OpenApi30Configuration.java b/src/main/java/com/microfocus/example/config/OpenApi30Configuration.java index 6a4f295..08ef055 100644 --- a/src/main/java/com/microfocus/example/config/OpenApi30Configuration.java +++ b/src/main/java/com/microfocus/example/config/OpenApi30Configuration.java @@ -32,9 +32,10 @@ Insecure Web App (IWA) @OpenAPIDefinition(info = @Info( title = "Insecure Web App (IWA) API", description = "This is the REST API for Insecure Web App (IWA) Pharmacy Direct. You can select a development or production server to test the API. " + - "Most operations require authentication via a user specific JWT token. To retrieve a JWT token for a user you can use the " + - " '/authentication/sign-in' operation below and then copy the value of the 'accessToken' field. This value can then be " + - "entered when you click on the 'Authorize' button or lock icons.", + "This API is protected using JWT Access tokens generated by Auth0 using Client Credentials Flow, although some operations are public, " + + "such as site (All) and products (Read) and reviews (Read).\n" + + "The Client Credentials Flow (defined in OAuth 2.0 RFC 6749, section 4.4) involves an application exchanging its application credentials," + + "such as client ID and client secret, for an access token. To use protected operations you will need to configure your own Auth0 API Application.", version = "v3"), servers = @Server( url = "{protocol}://{environment}", diff --git a/src/main/java/com/microfocus/example/config/WebSecurityConfiguration.java b/src/main/java/com/microfocus/example/config/WebSecurityConfiguration.java index 8627dd5..abeaeec 100644 --- a/src/main/java/com/microfocus/example/config/WebSecurityConfiguration.java +++ b/src/main/java/com/microfocus/example/config/WebSecurityConfiguration.java @@ -31,6 +31,7 @@ Insecure Web App (IWA) import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.Customizer; 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.configuration.EnableWebSecurity; @@ -38,8 +39,9 @@ Insecure Web App (IWA) import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistryImpl; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.firewall.DefaultHttpFirewall; import org.springframework.security.web.firewall.HttpFirewall; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; @@ -91,20 +93,23 @@ public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } + @Configuration @Order(1) public class ApiConfigurationAdapter extends WebSecurityConfigurerAdapter { + private JwtAuthenticationConverter jwtAuthenticationConverter() { + JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("scope"); + jwtGrantedAuthoritiesConverter.setAuthorityPrefix("SCOPE_"); + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter); + return jwtAuthenticationConverter; + } + @Override protected void configure(HttpSecurity httpSecurity) throws Exception { - /*http.cors().and().csrf().disable() - .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() - .authorizeRequests().antMatchers("/api/auth/**").permitAll() - .antMatchers("/api/test/**").permitAll() - .anyRequest().authenticated();*/ - httpSecurity.antMatcher("/api/**") .authorizeRequests() .antMatchers("/api/v3/site/**").permitAll() @@ -114,19 +119,20 @@ protected void configure(HttpSecurity httpSecurity) throws Exception { .antMatchers(HttpMethod.GET,"/api/v3/products/**").permitAll() .antMatchers(HttpMethod.GET, "/api/v3/reviews").permitAll() .antMatchers(HttpMethod.GET, "/api/v3/reviews/**").permitAll() - .antMatchers(HttpMethod.GET, "/api/**").authenticated() - .antMatchers(HttpMethod.DELETE, "/api/**").hasAnyRole("ADMIN", "API") - .antMatchers(HttpMethod.POST, "/api/**").hasAnyRole("ADMIN", "API") - .antMatchers(HttpMethod.PUT, "/api/**").hasAnyRole("ADMIN", "API") - .antMatchers(HttpMethod.PATCH, "/api/**").hasAnyRole("ADMIN", "API") + .antMatchers(HttpMethod.GET, "/api/**").hasAuthority("SCOPE_read:users") + .antMatchers(HttpMethod.DELETE, "/api/**").hasAuthority("SCOPE_delete:products") + .antMatchers(HttpMethod.POST, "/api/**").hasAuthority("SCOPE_add:products") + .antMatchers(HttpMethod.PUT, "/api/**").hasAuthority("SCOPE_update:products") + .antMatchers(HttpMethod.PATCH, "/api/**").hasAuthority("SCOPE_update:products") .and().exceptionHandling().authenticationEntryPoint(unauthorizedHandler) .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //.and().exceptionHandling().authenticationEntryPoint(basicAuthenticationEntryPoint) .and().exceptionHandling().accessDeniedHandler(apiAccessDeniedHandler) - .and().csrf().disable(); - - httpSecurity.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); + .and().csrf().disable().cors(Customizer.withDefaults()) + .oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthenticationConverter()); + //httpSecurity.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class); + } } diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 0427989..a6586fc 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -52,6 +52,12 @@ spring: jackson: serialization: WRITE_DATES_AS_TIMESTAMPS: false + security: + oauth2: + resourceserver: + jwt: + issuer-uri: https://dev-ahui5f878pgtbrpr.us.auth0.com/ + audiences: https://iwa-api.onfortify.com mail: default-encoding: UTF-8 host: localhost diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml index 911adb9..ee20001 100644 --- a/src/main/resources/application-test.yml +++ b/src/main/resources/application-test.yml @@ -51,6 +51,12 @@ spring: # favicon: # enabled: false throw-exception-if-no-handler-found: true + security: + oauth2: + resourceserver: + jwt: + issuer-uri: https://dev-ahui5f878pgtbrpr.us.auth0.com/ + audiences: https://iwa-api.onfortify.com jackson: serialization: WRITE_DATES_AS_TIMESTAMPS: false diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d8e9dc0..7d4e3f6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -50,6 +50,12 @@ spring: # favicon: # enabled: false throw-exception-if-no-handler-found: true + security: + oauth2: + resourceserver: + jwt: + issuer-uri: https://dev-ahui5f878pgtbrpr.us.auth0.com/ + audiences: https://iwa-api.onfortify.com jackson: serialization: WRITE_DATES_AS_TIMESTAMPS: false @@ -82,10 +88,10 @@ springdoc: display-request-duration: true operations-sorter: alpha tagsSorter: alpha - # groups-order: DESC disable-swagger-default-url: true paths-to-match: /api/** + writer-with-order-by-keys: true # group-configs: # - group: application # paths-to-match: /api/** @@ -99,6 +105,7 @@ logging: com.microfocus: INFO org.springframework.web: INFO org.springframework.security: INFO + org.springframework.security.oauth2: DEBUG app: name: IWA Pharmacy Direct