This is a single assignment that has been broken into incremental, compatible portions based on the completed API assignment. It should be turned in as a single tree of Maven modules that can build from the root level down.

1. Assignment Starter

There is a project within autorentals-starter/assignment3-autorentals-security/autorentals-security-svc that contains some ground work for the security portions of the assignment. It contains:

  1. a set of @Configuration classes nested within the SecurityConfiguration class. Each @Configuration class or construct is profile-constrained to match a section of the assignment.

  2. the shell of a secure AutoRentalsService wrapper

    It is your choice whether to use the "layered/wrapped" approach (where you implement separate/layered API and security modules) or "embedded/enhanced" approach (where you simply enhance the API solution with the security requirements).
  3. a @Configuration class that instantiates the correct AutoRentalService under the given profile/runtime context.

  4. base unit integration tests that align with the sections of the assignment and pull in base tests from the support module. Each test case activates one or more profiles identified by the assignment.

assignment3c autorentals starter
Figure 1. Security Assignment Starter

The meat of the assignment is focused in the following areas:

  1. configuring web security beans to meet the different security levels of the assignment. You will use the component-based approach. Each profile will start over. You will end up copy/pasting filtering rules forward to follow-on configurations of advancing profiles.

  2. the unit tests are well populated

    1. each of the tests have been @Disabled and rely on your test helper from the API tests to map RentalDTO references to your AutoRental DTO class.

  3. For the other server-side portions of the assignment:

    1. there is a skeletal IdentityController that outlines portions of the whoAmI and authorities endpoints.

    2. there is a skeletal SecureAutoRentalsServiceWrapper and @Configuration class that can be used to optionally wrap your assignment 2 implementation. The satisfactory alternative is to populate your existing assignment 2 implementations to meet the assignment 3 requirements. Neither path (layer versus enhance in-place) will get you more or less credit. Choose the path that makes the most sense to you.

      The layered approach provides an excuse to separate the solution into layers of components and provide practice doing so.
      The enhance-in-place approach is a more straight forward and realistic development because everything is in one place. It would be rare to split the implementation of a single component across a series of modules/JARs.
      If you layer your assignment3 over your assignment2 solution as separate modules, you will need to make sure that the dependencies on assignment2 are vanilla JARs and not Spring Boot executable JARs. Luckily the ejava-build-parent made that easy to do with classifying the executable JAR with bootexec.

2. Assignment Support

The assignment support module(s) in autorentals-support/autorentals-support-security again provide some examples and ground work for you to complete the assignment — by adding a dependency. I used a layered approach to secure Autos and Renters. This better highlighted what was needed for security because it removed most of the noise from the assignment 2 functional threads. It also demonstrated some weaving of components within the auto-configuration. Adding the dependency on the autorenters-support-security-svc adds this layer to the autorenters-support-api-svc components.

The following dependency can replace your current dependency on the autorenters-support-api-svc.

autorenters-support-security-svc Dependency
<dependency>
    <groupId>info.ejava.assignments.security.autorentals</groupId>
    <artifactId>autorenters-support-security-svc</artifactId>
    <version>${ejava.version}</version>
</dependency>

The support module comes with an extensive amount of tests that permit you to focus your attention on the security configuration and security implementation of the AutoRental service. The following test dependency can provide you with many test constructs.

autorenters-support-security-svc Dependency
<dependency>
    <groupId>info.ejava.assignments.security.autorentals</groupId>
    <artifactId>autorenters-support-security-svc</artifactId>
    <classifier>tests</classifier>
    <version>${ejava.version}</version>
    <scope>test</scope>
</dependency>

2.1. High Level View

Between the support (primary and test) modules and starter examples, most of your focus can be placed on completing the security configuration and service implementation to satisfy the security requirements.

The support module provides

  • Auto and Renter services that will operate within your application and will be secured by your security configuration. Necessary internal security checks are included within the Auto and Renter services, but your security configuration will use path-based security access to provide interface access control to these externally provided resources.

The support test modules provide

  • @TestConfiguration classes that supply the necessary beans for the tests to be completed.

  • Test cases that are written to be base classes of @SpringBootTest test cases supplied in your assignment. The starter provides most of what you will need for your security tests.

assignment3c autorentals highlevel
Figure 2. Security Assignment High Level View

Your main focus should be within the security configuration and AutoRentals service implementation classes and running the series of provided tests.

The individual sections of this assignment are associated with one or more Spring profiles. The profiles are activated by your @SpringBootTest test cases. The profiles will activate certain test configurations and security configurations your are constructing.

  • Run a test

  • Fix a test result with either a security configuration or service change

  • rinse and repeat

The tests are written to execute from the sub-class in your area. With adhoc navigation, sometimes the IDE can get lost — lose the context of the sub-class and provide errors as if there were only the base class. If that occurs — make a more direct IDE command to run the sub-class to clear the issue.

3. Assignment 3a: Security Authentication

3.1. Anonymous Access

3.1.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of configuring Spring Web Security for authentication requirements. You will:

  1. activate Spring Security

  2. create multiple, custom authentication filter chains

  3. enable open access to static resources

  4. enable anonymous access to certain URIs

  5. enforce authenticated access to certain URIs

3.1.2. Overview

In this portion of the assignment you will be activating and configuring the security configuration to require authentication to certain resource operations while enabling access to other resources operations.

assignment3a autorentals security authn anon
Figure 3. Anonymous Access
URIs
  • /api/renters

  • /api/renters/{id}

  • /api/autos

  • /api/autos/{id}

  • /api/{your resource}

  • /api/{your resource}/id

  • /content/*

3.1.3. Requirements

  1. Turn off CSRF protections.

    Configure HttpSecurity to disable CSRF processing. This will prevent ambiguity between a CSRF or authorization rejection for non-safe HTTP methods.
  2. Add a static text file past_transactions.txt that will be made available below the /content/ URI. Place the following title in the first line so that the following will be made available from a web client.

    past_transactions.txt
    $ curl -X GET http://localhost:8080/content/past_transactions.txt
    Past AutoRentals
    Static Resources Served from classpath

    By default, static content is served out of the classpath from several named locations including classpath:/static/, classpath:/public/, classpath:/resources/, and classpath:/META-INF/resources/

  3. Configure anonymous access for the following resources methods

    1. static content below /content

      /content/** glob indicates "anything below" /content.
    2. HEAD for all resources

      Configure HttpSecurity to permit all HEAD calls matching any URI — whether it exists or not.
    3. GET for auto and autoRental resources. Leverage the requestMatcher() to express the method and pattern.

      Configure HttpSecurity to permit all GET calls matching URIs below autos and autoRentals. Leverage the requestMatcher() to express the method and pattern.
    4. POST for /api/autos/query access

  4. Configure authenticated access for the following resource operations

    1. GET calls for renter resources. No one can gain access to a Renter without being authenticated.

    2. non-safe calls (POST, PUT, and DELETE) for autos, renters, and autoRentals resources; except POST /api/autos/query.

    Configure HttpSecurity to authenticate any request that was not yet explicitly permitted
  5. Create a unit integration test case that verifies (provided)

    1. anonymous user access granted to static content

    2. anonymous user access granted to a HEAD call to auto and renter resources

    3. anonymous user access granted to a GET call to auto and autoRental resources

    4. anonymous user access denial to a GET call to renter resources

    5. anonymous user access denial to non-safe call to each resource type

      Denial must be because of authentication requirements and not because of a CSRF failure. Disable CSRF for all API security configurations.
      All tests will use an anonymous caller in this portion of the assignment. Authenticated access is the focus of a later portion of the assignment.
  6. Restrict this security configuration to the anonymous-access profile and activate that profile while testing this section.

@Configuration(proxyBeanMethods = false)
@Profile("anonymous-access") (1)
public class PartA1_AnonymousAccess {
1 restrict configuration changes to the anonymous-access profile
@SpringBootTest(classes= {...},
@ActiveProfiles({"test", "anonymous-access"}) (1)
public class MyA1_AnonymousAccessNTest extends A1_AnonymousAccessNTest {
1 activate the anonymous-access profile (with any other designed profiles) when executing tests

3.1.4. Grading

Your solution will be evaluated on:

  1. activate Spring Security

    1. whether Spring security has been enabled

  2. create multiple, custom authentication filter chains

    1. whether access is granted or denied for different resource URIs and methods

  3. enable open access to static resources

    1. whether anonymous access is granted to static resources below /content

  4. enable anonymous access to certain URIs

    1. whether anonymous access has been granted to dynamic resources for safe (GET) calls

  5. enforce authenticated access to certain URIs

    1. whether anonymous access is denied for dynamic resources for unsafe (POST, PUT, and DELETE) calls

3.1.5. Additional Details

  1. No accounts are necessary for this portion of the assignment. All testing is performed using an anonymous caller.

  2. Turning on TRACE for Spring Web Security may provide helpful narrative to actions taken

    logging.level.org.springframework.security.web=TRACE
  3. Setting a breakpoints at or near:

    • FilterChainProxy.doFilterInternal():214 will allow you to inspect the filterChains put in place by your definitions.

    • FilterChainProxy.VirtualFilterChain.doFilter():374 will allow you to inspect the work performed by each filter

    • AuthorizationFilter.doFilter():95, RequestMatcherDelegatingAuthorizationManager.check():74 will allow you to inspect authorization

3.2. Authenticated Access

3.2.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of authenticating a client and identifying the identity of a caller. You will:

  1. add an authenticated identity to RestTemplate, or RestClient client

  2. locate the current authenticated user identity

3.2.2. Overview

In this portion of the assignment you will be authenticating with the API (using RestTemplate, or RestClient) and tracking the caller’s identity within the application.

assignment3a autorentals security authn auth
Figure 4. Authenticated Access

You’re starting point should be an application with functional Autos, Renters, and AutoRentals API services. Autos and Renters were provided to you. You implemented AutoRentals in assignment2. All functional tests for AutoRentals are passing.

Your focus area is again in the SecurityConfiguration @Configuration class. This time, it is within the @Configuration that is active during the authenticated-access profile. The changes you made for the previous profile will not longer be active. Copy/paste anything that is again needed for these requirements.

Focus Area for this Portion of the Assignment
@Configuration(proxyBeanMethods = false)
@Profile({"authenticated-access", "userdetails"})
public class PartA2_AuthenticatedAccess {

3.2.3. Requirements

  1. Configure the application to use a test username/password of ted/secret during testing with the authenticated-access profile.

    Account properties should only be active when using the authenticated-access profile.

    spring.security.user.name: ...
    spring.security.user.password: ...

    Active profiles can be named for individual Test Cases.

    @SpringBootTest(...)
    @ActiveProfiles({"test", "authenticated-access"})

    Property values can be injected into the test configurations in order to supply known values.

    @Value("${spring.security.user.name:}") (1)
    private String username;
    @Value("${spring.security.user.password:}")
    private String password;
    1 value injected into property if defined — blank if not defined
  2. Configure the application to support BASIC authentication.

    Configure HttpSecurity to enable HTTP BASIC authentication.
  3. Turn off CSRF protections.

    Configure HttpSecurity to disable CSRF processing.
  4. Turn off sessions, and any other filters that prevent API interaction beyond authentication.

    Configure HttpSecurity to use Stateless session management and other properties as required.
  5. Add a new resource api/whoAmI (provided)

    1. supply two access methods: GET and POST. Configure security such that neither require authentication.

      Configure HttpSecurity to permit all method requests for /api/whoAmI. No matter which HttpMethod is used. Pay attention to the order of the authorize requests rules definition.
    2. both methods must determine the identity of the current caller and return that value to the caller. When called with no authenticated identity, the methods should should return a String value of “(null)” (open_paren + the_word_null + close_paren)

      You may inject or programmatically lookup the user details for the caller identity within the server-side controller method.
  6. Create a set of unit integration tests that demonstrate the following (provided):

    1. authentication denial when using a known username but bad password

      Any attempt to authenticate with a bad credential will result in a 401/UNAUTHORIZED error no matter if the resource call requires authentication or not.
      Credentials can be applied to RestTemplate using interceptors.
    2. successful authentication using a valid username/password

    3. successful identification of the authenticated caller identity using the whoAmI resource operations

    4. successful authenticated access to POST/create auto, renter, and autoRental resource operations

  7. Restrict this security configuration to the authenticated-access profile and activate that profile during testing this section.

    @Configuration(proxyBeanMethods = false)
    @Profile({"authenticated-access", "userdetails"}) (1)
    public class PartA2_AuthenticatedAccess {
    ===
    @SpringBootTest(classes={...},
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
    @ActiveProfiles({"test", "authenticated-access"}) (2)
    public class MyA2_AuthenticatedAccessNTest extends A2_AuthenticatedAccessNTest {
    1 activated with authenticated-access (or upcoming userdetails) profile
    2 activating desired profiles
  8. Establish a security configuration that is active for the nosecurity profile which allows for but does not require authentication to call each resource/method. This will be helpful to simplify some demonstration scenarios where security is not essential.

    @Configuration(proxyBeanMethods = false)
    @Profile("nosecurity") (1)
    public class PartA2b_NoSecurity {
    ===
    @SpringBootTest(classes= { ...
    @ActiveProfiles({"test", "nosecurity"}) (1)
    public class MyA2b_NoSecurityNTest extends A2b_NoSecurityNTest {

    The security profile will allow unauthenticated access to operations that are also free of CSRF checks.

    curl -X POST http://localhost:8080/api/autos -H 'Content-Type: application/json' -d '{ ... }'
    { ... }

3.2.4. Grading

Your solution will be evaluated on:

  1. add an authenticated identity to RestTemplate or RestClient client

    1. whether you have implemented stateless API authentication (BASIC) in the server

    2. whether you have successfully completed authentication using a Java client

    3. whether you have correctly demonstrated and tested for authentication denial

    4. whether you have demonstrated granted access to an unsafe methods for the auto, renter, and autoRental resources.

  2. locate the current authenticated user identity

    1. whether your server-side components are able to locate the identity of the current caller (authenticated or not).

3.3. User Details

3.3.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of assembling a UserDetailsService to track the credentials of all users is the service. You will:

  1. build a UserDetailsService implementation to host user accounts and be used as a source for authenticating users

  2. build an injectable UserDetailsService

  3. encode passwords

3.3.2. Overview

In this portion of the assignment, you will be starting with a security configuration with the authentication requirements of the previous section. The main difference here is that there will be multiple users and your server-side code needs to manage multiple accounts and permit them each to authenticate.

To accomplish this, you will be constructing an AuthenticationManager to provide the user credential authentication required by the policies in the SecurityFilterChain. Your supplied UserDetailService will be populated with at least 5 users when the application runs with the userdetails profile.

assignment3a autorentals security authn cors
Figure 5. User Details

The autorenters-support-security-svc module contains a YAML file activated with the userdetails profile. The YAML file expresses the following users with credentials. These are made available by injecting the Accounts @ConfigurationProperty bean.

  1. mary/secret

  2. lou/secret

  3. murry/secret

  4. ted/secret

  5. sueann/betty

Your main focus will be to provide a UserDetailsService that is populated from the supplied rentalAccounts. The account information will have only username and password.

assignment3c autorentals userdetails
Figure 6. UserDetailsService

3.3.3. Requirements

  1. Create a UserDetailsService that is activated during the userdetails profile.

    The InMemoryUserDetailsManager is fine for this requirement. You have the option of using other implementation types if you wish but they cannot require the presence of an external resource (i.e., JDBC option must use an in-memory database).
    1. expose the UserDetailsService as a @Bean that can be injected into other factories.

      @Bean
      public UserDetailsService userDetailsService(PasswordEncoder encoder, ...) {
    2. populate it with the 5 users from an injected Accounts @ConfigurationProperty bean

      The rentalAccounts bean is provided for you in the ProvidedAuthorizationTestHelperConfiguration in the support module.
    3. store passwords for users using a BCrypt hash algorithm.

      Both the BCryptPasswordEncoder and the DelegatingPasswordEncoder will encrypt with Bcrypt.

  2. Create a unit integration test case that (provided):

    1. activates the userdetails profile

      @Configuration(proxyBeanMethods = false)
      @Profile({"nosecurity","userdetails", "authorities", "authorization"})(1)
      public class PartA3_UserDetailsPart {
      ===
      @SpringBootTest(classes={...},
      @ActiveProfiles({"test","userdetails"}) (2)
      public class MyA3_UserDetailsNTest extends A3_UserDetailsNTest {
      1 activated with userdetails (or select other) profile
      2 activating desired profiles
    2. verifies successful authentication and identification of the authenticated username using the whoAmI resource

    3. verifies successful access to a one POST/create auto, renter, and autoRental resource operation for each user

      The test(s) within the support module provides much/all of this test coverage.

3.3.4. Grading

Your solution will be evaluated on:

  1. build a UserDetailsService implementation to host user accounts and be used as a source for authenticating users

    1. whether your solution can host credentials for multiple users

    2. whether your tests correctly identify the authenticated caller for each of the users

    3. whether your tests verify each authenticated user can create a Auto, Renter, and AutoRental

  2. build an injectable UserDetailsService

    1. whether the UserDetailsService was exposed using a @Bean factory

  3. encode passwords

    1. whether the password encoding was explicitly set to create BCrypt hash

3.3.5. Additional Details

  1. There is no explicit requirement that the UserDetailsService be implemented using a database. If you do use a database, use an in-memory RDBMS so that there are no external resources required.

  2. You may use a DelegatingPasswordEncoder to satisfy the BCrypt encoding requirements, but the value stored must be in BCrypt form.

  3. The implementation choice for PasswordEncoder and UserDetailsService is separate from one another and can be made in separate @Bean factories.

    @Bean
    public PasswordEncoder passwordEncoder() {...}
    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder, ...) {...}
  4. The commonality of tests differentiated by different account properties is made simpler with the use of JUnit @ParameterizedTest. However, by default method sources are required to be declared as Java static methods — unable to directly reference beans injected into non-static attributes. @TestInstance(TestInstance.Lifecycle.PER_CLASS) can be used to allow the method source to be declared a Java non-static method and directly reference the injected Spring context resources.

  5. You may annotate a @Test or @Nested testcase class with @DirtiesContext to indicate that the test makes changes that can impact other tests and the Spring context should be rebuilt after finishing.

4. Assignment 3b: Security Authorization

4.1. Authorities

4.1.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of authorities. You will:

  1. define role-based and permission-based authorities

4.1.2. Overview

In this portion of the assignment, you will start with the authorization and user details configuration of the previous section and enhance each user with authority information.

You will be assigning authorities to users and verifying them with a unit test. You will add an additional ("authorities") resource to help verify the assertions.

assignment3b autorentals security authz whoami
Figure 7. Authorities Test Resource
assignment3b autorentals security authz authts
Figure 8. Assignment Principles and Authorities

The autorenters-support-security-svc module contains a YAML file activated with the authorities and authorization profiles. The YAML file expresses the following users, credentials, and authorities

  1. mary: ROLE_ADMIN, ROLE_MEMBER

  2. lou: ROLE_MGR (no role member)

  3. murry: ROLE_MEMBER, PROXY

  4. ted: ROLE_MEMBER

  5. sueann: ROLE_MEMBER

Your focus will be on adding authorities to each user populated in the UserDetailsService.

assignment3c autorentals userdetails
Figure 9. UserDetailsService

4.1.3. Requirements

  1. Create an api/authorities resource with a GET method (provided):

    1. accepts an "authority" query parameter

    2. returns (textual) "TRUE" or "FALSE" depending on whether the caller has the authority assigned

  2. Create a UserDetailsService that is active during the authorities profile.

    1. populate it with the 5 users from an injected Accounts @ConfigurationProperty bean. Include pre-assigned authorities.

      The rentalAccounts bean is provided for you in the ProvidedAuthorizationTestHelperConfiguration in the support module.
  3. Create one or more unit integration test cases that (provided):

    1. activates the authorities profile

    2. verifies the unauthenticated caller has no authorities assigned

    3. verifies each the authenticated callers have proper assigned authorities

4.1.4. Grading

Your solution will be evaluated on:

  1. define role-based and permission-based authorities

    1. whether you have assigned required authorities to users

    2. whether you have verified an unauthenticated caller does not have identified authorities assigned

    3. whether you have verified successful assignment of authorities for authenticated users as clients

4.1.5. Additional Details

  1. There is no explicit requirement to use a database for the user details in this assignment. However, if you do use an database, please use an in-memory RDBMS instance so there are no external resources required.

  2. The repeated tests due to different account data can be simplified using a @ParameterizedTest. However, you will need to make use of @TestInstance(TestInstance.Lifecycle.PER_CLASS) in order to leverage the Spring context in the @MethodSource.

4.2. Authorization

4.2.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of implementing access control based on authorities assigned for URI paths and methods. You will:

  1. implement URI path-based authorization constraints

  2. implement annotation-based authorization constraints

  3. implement role inheritance

  4. implement an AccessDeniedException controller advice to hide necessary stack trace information and provide useful error information to the caller

4.2.2. Overview

In this portion of the assignment you will be restricting access to specific resource operations based on path and expression-based resource restrictions.

assignment3b autorentals security authz authzm
Figure 10. Authorization
Authorization Testing
@SpringBootTest(classes= {
        AutoRentalsSecurityApp.class,
        SecurityTestConfiguration.class},
        webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles({"test","authorities", "authorization"})
public class MyB2_AuthorizationNTest extends B2_AuthorizationNTest {

4.2.3. Requirements

  1. Update the resources to store the identity of the caller with the resource created to identify the owner

    1. Autos will store the username (as username) of the auto creator (provided)

    2. Renters will store the username (as username) of the renter creator (provided)

    3. AutoRentals should store the username (as username) of the renter it represents (new)

      The Renter creator or a user with the PROXY authority (murry) may create the AutoRental and have the username set to the Renter username. Therefore, there is a separation between "can they create?" and "who is the owner username?"
    4. the identity should not be included in the marshalled DTO returned to callers

      The DTO may have this field omitted or marked transient so that the element is never included.
      Identity for "can they create" comes from the SecurityContext. Identity for "who is the owner username" is derived from the renterId from AutoRentalDTO and RenterDTO from RentersService.
  2. Define access constraints for resources using path and expression-based authorizations. Specific authorization restriction details are in the next requirement.

    1. Use path-based authorizations for Auto and Renter resources, assigned to the URIs since you are not working with modifiable source code for these two resources

      Configure HttpSecurity to enforce the required roles for autos and renters calls
    2. Use expression-based authorizations for autoRentals resources, applied to the service methods

      Use annotations on AutoRental service methods to trigger authorization checks. Remember to enable method security for the prePostEnabled annotation form (now a default) for your application and tests.
  3. Restrict access according to the following

    1. Autos (path-based authorizations)

      1. continue to restrict non-safe methods to authenticated users (from Authenticated Access)

      2. authenticated users may modify autos that match their login (provided)

      3. authenticated users may delete autos that match their login or have the MGR role (provided)

      4. only authenticated users with the ADMIN role can delete all autos (new)

    2. Renters (path-based authorizations)

      1. continue to restrict non-safe methods to authenticated users (from Authenticated Access)

      2. authenticated users may create a single Renter to represent themselves (provided)

      3. authenticated users may modify a renter that matches their login (provided)

      4. authenticated users may delete a renter that matches their login or have the MGR role (provided)

      5. only authenticated users with the ADMIN role can delete all renters (new)

    3. AutoRentals (annotation-based authorizations) — through the use of @PreAuthorize and programmatic checks.

      1. authenticated users may create an AutoRental for an existing Renter matching their identity (new)

      2. authenticated users may update an AutoRental for an AutoRental they own (new)

        i.e. caller "sueann" can update (change time period) for an existing AutoRental associated with identity "sueann"
      3. authenticated users with PROXY authority may create and update an AutoRental for any Renter (new)

        i.e. caller "lou" and "murry" can create or update (change time period) any existing AutoRental for any Renter
      4. authenticated users may only delete a AutoRental for a Renter matching their username (new)

      5. authenticated users with the MGR role may delete any AutoRental (new)

      6. authenticated users with the ADMIN role may delete all AutoRentals (new)

  4. Form the following role inheritance

    1. ROLE_ADMIN inherits from ROLE_MGR so that users with ADMIN role will also be able to perform MGR role operations

      Register a RoleHierarchy relationship definition between inheriting roles.
  5. Implement a mechanism to hide stack trace or other details from the API caller response when an AccessDeniedException occurs. From this point forward — stack traces can be logged on the server-side but should not be exposed in the error payload.

  6. Create unit integration test(s) to demonstrate the behavior defined above

4.2.4. Grading

Your solution will be evaluated on:

  1. implement URI path-based authorization constraints

    1. whether path-based authorization constraints were properly defined and used for Auto and Renter resource URIs

  2. implement annotation-based authorization constraints

    1. whether expression-based authorization constraints were properly defined for AutoRental service methods

  3. implement role inheritance

    1. whether users with the ADMIN role were allowed to invoke methods constrained to the MGR role.

  4. implement an AccessDeniedException controller advice to hide necessary stack trace information and provide useful error information to the caller

    1. whether stack trace or other excessive information was hidden from the access denied caller response

4.2.5. Additional Details

  1. Role inheritance can be defined using a RoleHierarchy bean.

  2. With the requirements for fine-grain authority checks, the amount of additional declarative path-based and annotation-based access checking will still be present, but minimal. If a user and users with special authorizations can perform the same action, it will be understandable for declarative checks enforce authenticated and programmatic checks for special authority conditions.

  3. An optional AuthorizationHelper has been provided and demonstrated in the Auto and Renter security wrappers. It has get()/has() inspection methods that simply return information from the SecurityContext. It also has assert() methods that throw AccessDeniedException for anything that fails. You may use it or implement your own inspection/assertion mechanisms if it does not meet your needs.

5. Assignment 3c: HTTPS

5.1. HTTPS

5.1.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of protecting sensitive data exchanges with one-way HTTPS encryption. You will:

  1. generate a self-signed certificate for demonstration use

  2. enable HTTPS/TLS within Spring Boot

  3. implement an integration unit test using HTTPS

5.1.2. Overview

In this portion of the assignment you will be configuring your server to support HTTPS only when the https profile is active.

assignment3b autorentals security authz https
Figure 11. Authorization

5.1.3. Requirements

  1. Implement an HTTPS-only communication in the server when running with the https profile.

    1. package a demonstration certificate in the src tree

    2. supply necessary server-side properties in a properties file

      With this in place and the application started with the authorities, authorization, and https profiles active …​

      java -jar target/autorentals-security-svc-1.0-SNAPSHOT-bootexec.jar --spring.profiles.active=authorities,authorization,https

      should enable the following results

      curl -k -X GET https://localhost:8443/api/whoAmI -u "mary:secret" && echo
      mary
      $ curl -k -X DELETE https://localhost:8443/api/autos -u mary:secret
      $ curl -k -X DELETE https://localhost:8443/api/autos -u sueann:betty
      {"timestamp":"2022-09-25T00:51:41.200+00:00","status":403,"error":"Forbidden","path":"/api/autos"}
  2. Implement a unit integration test that will

    1. activate the authorities, authorization, and https profiles

    2. establish an HTTPS connection with RestTemplate or WebClient

    3. successfully invoke using HTTPS and evaluate the result

      The starter module provides a skeletal test for you to complete. The actual test performed by you can be any end-to-end communication with the server that uses HTTPS.

5.1.4. Grading

Your solution will be evaluated on:

  1. generate a self-signed certificate for demonstration use

    1. whether a demonstration PKI certificate was supplied for demonstrating the HTTPS capability of the application

  2. enable HTTPS/TLS within Spring Boot

    1. whether the integration test client was able to perform round-trip communication with the server

    2. whether the communications used HTTPS protocol

5.1.5. Additional Details

  1. There is no requirement to implement an HTTP to HTTPS redirect

  2. See the svc/svc-security/https-hello-example for Maven and RestTemplate setup example.

  3. Implement the end-to-end integration test with HTTP before switching to HTTPS to limit making too many concurrent changes.

6. Assignment 3d: AOP and Method Proxies

In this assignment, we are going to use some cross-cutting aspects of Spring AOP and dynamic capabilities of Java reflection to modify component behavior. As a specific example, you are going to modify Auto and Renter service behavior without changing the Autos/Renters source code. We are going to add a requirement that certain fields be null and non-null.

The first two sections (reflection and dynamic proxies) of the AOP assignment lead up to the final solution (aspects) in the third section.

No Autos/Renters Compilation Dependencies
The src/main portions of the assignment must have no compilation dependency on Autos and Renters. All compilation dependencies and most knowledge of Autos and Renters will be in the JUnit tests.

6.1. Reflection

6.1.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of using reflection to get and invoke a method proxy. You will:

  1. obtain a method reference and invoke it using Java Reflection

6.1.2. Overview

In this portion of the assignment you will implement a set of helper methods for a base class (NullPropertyAssertion) located in the autorenters-support-aop module — tasked with validating whether objects have nulls for an identified property. If the assertion fails — an exception will be thrown by the base class. Your derived class will assist in locating the method reference to the "getter" and invoking it to get the current property value.

assignment3c autorentals reflection
Figure 12. AOP and Method Proxies

You will find an implementation class shell, @Configuration class with @Bean factory, and JUnit test in the assignment 3 security "starter".

No Auto/Renter Compilation Dependency
Note that you see no mention of Auto or Renter in the above description/diagram. Everything will be accomplished using Java reflection.

You will need to create a dependency on the Spring Boot AOP starter, the ejava AOP support JAR, and AOP test JAR.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>info.ejava.assignments.aop.autorentals</groupId>
    <artifactId>autorentals homesales-support-aop</artifactId>
    <version>${ejava.version}</version>
</dependency>

<dependency>
    <groupId>info.ejava.assignments.aop.autorentals</groupId>
    <artifactId>autorentals-support-aop</artifactId>
    <classifier>tests</classifier>
    <version>${ejava.version}</version>
    <scope>test</scope>
</dependency>

6.1.3. Requirements

  1. implement the getGetterMethod() to locate and return the java.lang.reflect.Method for the requested (getter) method name.

    1. return the Method object if it exists

    2. use an Optional<Method> return type and return an Optional.empty() if it does not exist. It is not an error if the getter does not exist.

      It is not considered an error if the getterName requested does not exist. JUnit tests — which know the full context of the call — will decide if the result is correct or not.
  2. implement the getValue() method to return the value reported by invoking the getter method using reflection

    1. return the value returned

    2. report any exception thrown as a runtime exception

      Any exception calling an existing getter is unexpected and should be reported as a (subclass of) RuntimeException to the higher-level code to indicate this type of abnormality
  3. use the supplied JUnit unit tests to validate your solution. There is no Spring context required/used for this test.

    The tests will use non-AutoDTO/RenterDTO objects on purpose. The validator under test must work with any type of object passed in. Only "getter" method access will be supported for this capability. No "field" access will be performed.

6.1.4. Grading

Your solution will be evaluated on:

  1. obtain a method reference and invoke it using Java Reflection

    1. whether you are able to return a reference to the specified getter method using reflection

    2. whether you are able to obtain the current state of the property using the getter method reference using reflection

6.1.5. Additional Details

  1. The JUnit test (MyD1_ReflectionMethodTest) is supplied and should not need to be modified beyond enabling it.

  2. The tests will feed your solution with DTO instances in various valid and invalid states according to the isNull and notNull calls. There will also be some non-DTO classes used to verify the logic is suitable for generic parameter types.

6.2. Dynamic Proxies

6.2.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of implementing a dynamic proxy. You will:

  1. create a JDK Dynamic Proxy to implement adhoc interface(s) to form a proxy at runtime for implementing advice

6.2.2. Overview

In this portion of the assignment you will implement a dynamic proxy that will invoke the NullPropertyAssertion from the previous part of the assignment. The primary work will be in implementing an InvocationHandler implementation that will provide the implementation "advice" to the target object for selected methods. The advice will be a null check of specific properties of objects passed as parameters to the target object.

assignment3c autorentals dynamicproxy
Figure 13. Dynamic Proxy Advice

The constructed Proxy will be used as a stepping stone to better understand the Aspect solution you will implement in the next section. The handler/proxy will not be used in the final solution. It will be instantiated and tested within the JUnit test using an injected AutosService and RentersService from the Spring context.

No AutoDTO/RenterDTO Compilation Dependency
There will be no mention of or direct dependency on Autos or Renters in your solution. Everything will be accomplished using Java reflection.

6.2.3. Requirements

  1. implement the details for the NullValidatorHandler class that

    1. implements the java.lang.reflect.InvocationHandler interface

    2. has implementation properties

      1. nullPropertyAssertion — implements the check (from the previous section)

      2. target object that it is the proxy for

      3. methodName it will operate against on the target object

      4. nullProperties — propertyNames it will test for null (used also in the previous section)

      5. nonNullProperties — propertyNames it will test for non-null (used also in the previous section)

    3. has a static builder method called newInstance that accepts the above properties and returns a java.lang.reflect.Proxy implementing all interfaces of the target

      In order, for the tests to locate your factory method, it must have the following exact signature.

      //NullPropertyAssertion.class, Object.class, String.class, List.class, List.class
      public static <T> T newInstance(
              NullPropertyAssertion nullPropertyAssertion,
              T target,
              String methodName,
              List<String> nullProperties,
              List<String> nonNullProperties) {
      org.apache.commons.lang3.ClassUtils can be used to locate all interfaces of a class.
    4. implements the invoke() method of the InvocationHandler interface to validate the arguments passed to the method matching the methodName. Checks properties matching nullProperties and nonNullProperties.

      Example Validation Context
      • Example: renterService.createRenter(renterDTO)

        • target - renterService

        • method - createRenter

        • arg - renterDTO

          • id - property to evaluate

      • Example: autoService.getAuto(autoId)

        • target - autoService

        • method - getRenter

        • arg - autoId

      Test Method Invocations Matching methodName for this Instance

      Test only methods that match the configured method name for the proxy instance.

      private final String methodName;
      
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          if (this.methodName.equals(method.getName())) {
      Test Method Argument Properties not Target Properties

      Test the properties of each argument — not the properties of the target. Use null and non-null property lists provided during construction.

      nullPropertyAssertion.assertConditions(arg, isNullProperties, true);
      nullPropertyAssertion.assertConditions(arg, nonNullProperties, false);
      1. if the arguments are found to be in error — let the nullPropertyAssertion throw its exception

      2. otherwise allow the call to continue to the target object/method with provided args

        Validator limits
        Each proxy instance will only validate parameters of the named method but all parameters to that method. There is no way to distinguish between param1.propertyName and param2.propertyName with our design and will not need to
  2. Use the provided JUnit tests (MyD2_DynamnicProxyNTest) verify the functionality of the requirements above once you activate.

    1. Provide a NullPropertyAssertion component in the Spring context (e.g., @Bean factory)

      Make sure the @Bean factory is in the component scan path of your @SpringBootApplication application class.

6.2.4. Grading

Your solution will be evaluated on:

  1. create a JDK Dynamic Proxy to implement adhoc interface(s) to form a proxy at runtime for implementing advice

    1. whether you created a java.lang.reflect.InvocationHandler that would perform the validation on proxied targets

    2. whether you successfully created and returned a dynamic proxy from the newInstance factory method

    3. whether your returned proxy was able to successfully validate arguments passed to target

6.2.5. Additional Details

  1. The JUnit test case uses real AutoService and RenterService @Autowired from the Spring context, but only instantiates the proxy as a POJO within the test case.

  2. The JUnit tests make calls to the AutoService and RenterService, passing valid and invalid instances according to how your proxy was instantiated during the call to newInstance().

  3. The completed dynamic proxy will not be used beyond this section of the assignment. However, try to spot what the dynamic proxy has in common with the follow-on Aspect solution — since Spring interface Aspects are implemented using Dynamic Proxies.

6.3. Aspects

6.3.1. Purpose

In this portion of the assignment, you will demonstrate your knowledge of adding functionality to a point in code using an Aspect. You will:

  1. implement dynamically assigned behavior to methods using Spring Aspect-Oriented Programming (AOP) and AspectJ

  2. identify method join points to inject using pointcut expressions

  3. implement advice that executes before join points

  4. implement parameter injection into advice

6.3.2. Overview

In this portion of the assignment you will implement a ValidatorAspect that will "advise" service calls to provide secure Auto/Renter wrapper services.

The aspect will be part of your overall Spring context and will be able to change the behavior of the Autos and Renters used within your runtime application and tests when activated. The aspect will specifically reject any object passed to these services that violate defined create/update constraints. The aspect will be defined to match the targeted methods and arguments but will have no compilation dependency that is specific to Autos or Renters.

assignment3c autorentals aspect
Figure 14. Aspect Advice
No AutoDTO/RenterDTO Compilation Dependency
There will be no direct dependency on Autos or Renters. Everything will be achieved using AOP expressions and Java reflection constructs.

Your primary focus in the starter will be the following classes:

  • AOPConfiguration

  • ValidatorAspect

6.3.3. Requirements

  1. add AOP dependencies using the spring-boot-starter-aop module (provided)

  2. enable AspectJ auto proxy handling within your application

  3. create a ValidatorAspect component. The shell of a class and @Bean factory have been provided.

    1. inject a NullPropertyAssertion bean (implemented in the previous section) and List<MethodConstraints> (populated from application-aop.yaml from the support module; provided)

      1. NullPropertyAssertion @Bean factory should already be in place from the previous section

      2. List<MethodConstraints> @Bean factory is provided for you in the starter module

    2. annotate the Aspect class that will contain the pointcut(s) and advice with "@Aspect"

    3. make the overall component conditional on the aop profile

      This means the Auto and Renter services will act as delivered when the aop profile is not active and enforce the new constraints when the profile is active.
  4. define a "pointcut" that will target the calls to the SecureAutosWrapper and SecureRentersWrapper services. This pointcut should both:

    1. define a match pattern for the "join point"

      Start with Lenient Pattern
      Start with a lenient pattern for initial success and look to optimize with more precise matching over time.
  5. define an "advice" method to execute before the "join point"

    1. uses the previously defined "pointcut" to identify its "join point"

    2. uses typed or dynamic advice parameters

      Using typed advice parameters will require a close relationship between advice and service methods. Using dynamic advice parameters (JoinPoint) will allow a single advice method be used for all service methods. The former is more appropriate for targeted behavior. The latter is more appropriate to general purpose behavior.
    3. invokes the NullPropertyAssertion bean with the parameters passed to the method and asks to validate for isNull and notNull.

      nullPropertyAssertion.assertConditions(arg, conditions.getIsNull(), true);
      nullPropertyAssertion.assertConditions(arg, conditions.getNotNull(), false);
  6. Use the provided JUnit test cases to verify completion of the above requirements

    1. the initial MyD3a1_NoAspectSvcNTest deactivates the aop profile to demonstrate baseline behavior we want to change/not-change

    2. the second MyD3a2_AspectSvcNTest activates the aop profile and asserts different results

    3. the third MyD3b_AspectWebNTest demonstrates that the aspect was added to the beans injected into the Autos and Renters controllers.

      If AspectSvcNTest passes, and AspectWebNTest fails, check that you are advising the secure wrapper and not the base service.

6.3.4. Grading

Your solution will be evaluated on:

  1. implement dynamically assigned behavior to methods using Spring Aspect-Oriented Programming (AOP) and AspectJ

    1. whether you activated aspects in your solution

    2. whether you supplied an aspect as a component

  2. identify method join points to inject using pointcut expressions

    1. whether your aspect class contained a pointcut that correctly matched the target method join points

  3. implement advice that executes before join points

    1. whether your solution implements required validation before allowing target to execute

    2. whether your solution will allow the target to execute if validation passes

    3. whether your solution will prevent the target from executing and report the error if validation fails

  4. implement parameter injection into advice

    1. whether you have implemented typed or dynamic access to the arguments passed to the target method

6.3.5. Additional Details

  1. You are to base your AOP validation based on the data found within the injected List<MethodConstraints>. Each instance contains the name of the method and a list of property names that should be subject to isNull or notNull assertions.

    application-aop.yaml (in support)
    aop:
      validation:
        - methodName: createAuto
          isNull: [id, username]
          notNull: [make, model, passengers, dailyRate, fuelType ]
        - methodName: updateAuto
          isNull: [username]
    
        - methodName: createRenter
          isNull: [id, username]
          notNull: [make, firstName, lastName, dob, email]
        - methodName: updateRenter
          isNull: [username]
  2. The JUnit test case uses real, secured AutosService and RenterServices @Autowired from the Spring context augmented with aspects. Your aspect will be included when the aop profile is active.

  3. The JUnit tests will invoke the security service wrappers directly and through the controllers — calling with valid and invalid parameters according to the definition in the application-aop.yaml file.

    The Auto and Renter services may have some base validation built in and will not accept a non-null or null values. You cannot change that behavior and will not have to. You will validate the properties as defined, coming into the service.
  4. Ungraded activity — create a breakpoint in the Advice and Autos/RentersService(s) when executing the tests. Observe the call stack to see how you got to that point, where you are headed, and what else is in that call stack.