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:
-
a set of
@Configuration
classes nested within theSecurityConfiguration
class. Each@Configuration
class or construct is profile-constrained to match a section of the assignment. -
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). -
a
@Configuration
class that instantiates the correct AutoRentalService under the given profile/runtime context. -
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.
The meat of the assignment is focused in the following areas:
-
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.
-
the unit tests are well populated
-
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.
-
-
For the other server-side portions of the assignment:
-
there is a skeletal
IdentityController
that outlines portions of thewhoAmI
andauthorities
endpoints. -
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 withbootexec
.
-
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
.
<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.
<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.
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:
-
activate Spring Security
-
create multiple, custom authentication filter chains
-
enable open access to static resources
-
enable anonymous access to certain URIs
-
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.
- URIs
|
|
|
3.1.3. Requirements
-
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. -
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 classpathBy default, static content is served out of the classpath from several named locations including
classpath:/static/
,classpath:/public/
,classpath:/resources/
, andclasspath:/META-INF/resources/
-
Configure anonymous access for the following resources methods
-
static content below
/content
/content/**
glob indicates "anything below"/content
. -
HEAD
for all resourcesConfigure HttpSecurity to permit all HEAD
calls matching any URI — whether it exists or not. -
GET
for auto and autoRental resources. Leverage therequestMatcher()
to express the method and pattern.Configure HttpSecurity to permit all GET
calls matching URIs below autos and autoRentals. Leverage therequestMatcher()
to express the method and pattern. -
POST
for/api/autos/query
access
-
-
Configure authenticated access for the following resource operations
-
GET
calls for renter resources. No one can gain access to a Renter without being authenticated. -
non-safe calls (
POST
,PUT
, andDELETE
) for autos, renters, and autoRentals resources; exceptPOST /api/autos/query
.
Configure HttpSecurity to authenticate any request that was not yet explicitly permitted -
-
Create a unit integration test case that verifies (provided)
-
anonymous user access granted to static content
-
anonymous user access granted to a
HEAD
call to auto and renter resources -
anonymous user access granted to a
GET
call to auto and autoRental resources -
anonymous user access denial to a
GET
call to renter resources -
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.
-
-
Restrict this security configuration to the
anonymous-access
profile and activate that profile while testing this section.
|
3.1.4. Grading
Your solution will be evaluated on:
-
activate Spring Security
-
whether Spring security has been enabled
-
-
create multiple, custom authentication filter chains
-
whether access is granted or denied for different resource URIs and methods
-
-
enable open access to static resources
-
whether anonymous access is granted to static resources below
/content
-
-
enable anonymous access to certain URIs
-
whether anonymous access has been granted to dynamic resources for safe (
GET
) calls
-
-
enforce authenticated access to certain URIs
-
whether anonymous access is denied for dynamic resources for unsafe (
POST
,PUT
, andDELETE
) calls
-
3.1.5. Additional Details
-
No accounts are necessary for this portion of the assignment. All testing is performed using an anonymous caller.
-
Turning on TRACE for Spring Web Security may provide helpful narrative to actions taken
logging.level.org.springframework.security.web=TRACE
-
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:
-
add an authenticated identity to
RestTemplate
, orRestClient
client -
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.
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.
@Configuration(proxyBeanMethods = false)
@Profile({"authenticated-access", "userdetails"})
public class PartA2_AuthenticatedAccess {
3.2.3. Requirements
-
Configure the application to use a test username/password of
ted/secret
during testing with theauthenticated-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 -
Configure the application to support
BASIC
authentication.Configure HttpSecurity to enable HTTP BASIC authentication. -
Turn off
CSRF
protections.Configure HttpSecurity to disable CSRF processing. -
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. -
Add a new resource
api/whoAmI
(provided)-
supply two access methods:
GET
andPOST
. 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. -
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.
-
-
Create a set of unit integration tests that demonstrate the following (provided):
-
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. -
successful authentication using a valid username/password
-
successful identification of the authenticated caller identity using the
whoAmI
resource operations -
successful authenticated access to POST/create auto, renter, and autoRental resource operations
-
-
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 upcominguserdetails
) profile2 activating desired profiles -
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:
-
add an authenticated identity to
RestTemplate
orRestClient
client-
whether you have implemented stateless API authentication (
BASIC
) in the server -
whether you have successfully completed authentication using a Java client
-
whether you have correctly demonstrated and tested for authentication denial
-
whether you have demonstrated granted access to an unsafe methods for the auto, renter, and autoRental resources.
-
-
locate the current authenticated user identity
-
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:
-
build a
UserDetailsService
implementation to host user accounts and be used as a source for authenticating users -
build an injectable UserDetailsService
-
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.
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.
-
mary/secret
-
lou/secret
-
murry/secret
-
ted/secret
-
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.
3.3.3. Requirements
-
Create a
UserDetailsService
that is activated during theuserdetails
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).-
expose the
UserDetailsService
as a@Bean
that can be injected into other factories.@Bean public UserDetailsService userDetailsService(PasswordEncoder encoder, ...) {
-
populate it with the 5 users from an injected
Accounts
@ConfigurationProperty
beanThe rentalAccounts
bean is provided for you in theProvidedAuthorizationTestHelperConfiguration
in the support module. -
store passwords for users using a BCrypt hash algorithm.
Both the
BCryptPasswordEncoder
and theDelegatingPasswordEncoder
will encrypt with Bcrypt.
-
-
Create a unit integration test case that (provided):
-
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) profile2 activating desired profiles -
verifies successful authentication and identification of the authenticated username using the
whoAmI
resource -
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:
-
build a
UserDetailsService
implementation to host user accounts and be used as a source for authenticating users-
whether your solution can host credentials for multiple users
-
whether your tests correctly identify the authenticated caller for each of the users
-
whether your tests verify each authenticated user can create a Auto, Renter, and AutoRental
-
-
build an injectable UserDetailsService
-
whether the
UserDetailsService
was exposed using a@Bean
factory
-
-
encode passwords
-
whether the password encoding was explicitly set to create BCrypt hash
-
3.3.5. Additional Details
-
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. -
You may use a DelegatingPasswordEncoder to satisfy the BCrypt encoding requirements, but the value stored must be in BCrypt form.
-
The implementation choice for
PasswordEncoder
andUserDetailsService
is separate from one another and can be made in separate@Bean
factories.@Bean public PasswordEncoder passwordEncoder() {...} @Bean public UserDetailsService userDetailsService(PasswordEncoder encoder, ...) {...}
-
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. -
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:
-
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.
Figure 7. Authorities Test Resource
|
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
-
mary: ROLE_ADMIN, ROLE_MEMBER
-
lou: ROLE_MGR (no role member)
-
murry: ROLE_MEMBER, PROXY
-
ted: ROLE_MEMBER
-
sueann: ROLE_MEMBER
Your focus will be on adding authorities to each user populated in the UserDetailsService
.
4.1.3. Requirements
-
Create an
api/authorities
resource with aGET
method (provided):-
accepts an "authority" query parameter
-
returns (textual) "TRUE" or "FALSE" depending on whether the caller has the authority assigned
-
-
Create a
UserDetailsService
that is active during theauthorities
profile.-
populate it with the 5 users from an injected
Accounts
@ConfigurationProperty
bean. Include pre-assigned authorities.The rentalAccounts
bean is provided for you in theProvidedAuthorizationTestHelperConfiguration
in the support module.
-
-
Create one or more unit integration test cases that (provided):
-
activates the
authorities
profile -
verifies the unauthenticated caller has no authorities assigned
-
verifies each the authenticated callers have proper assigned authorities
-
4.1.4. Grading
Your solution will be evaluated on:
-
define role-based and permission-based authorities
-
whether you have assigned required authorities to users
-
whether you have verified an unauthenticated caller does not have identified authorities assigned
-
whether you have verified successful assignment of authorities for authenticated users as clients
-
4.1.5. Additional Details
-
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.
-
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:
-
implement URI path-based authorization constraints
-
implement annotation-based authorization constraints
-
implement role inheritance
-
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.
@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
-
Update the resources to store the identity of the caller with the resource created to identify the owner
-
Autos will store the username (as username) of the auto creator (provided)
-
Renters will store the username (as username) of the renter creator (provided)
-
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?" -
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.
-
-
Define access constraints for resources using path and expression-based authorizations. Specific authorization restriction details are in the next requirement.
-
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 -
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.
-
-
Restrict access according to the following
-
Autos (path-based authorizations)
-
continue to restrict non-safe methods to authenticated users (from Authenticated Access)
-
authenticated users may modify autos that match their login (provided)
-
authenticated users may delete autos that match their login or have the
MGR
role (provided) -
only authenticated users with the
ADMIN
role can delete all autos (new)
-
-
Renters (path-based authorizations)
-
continue to restrict non-safe methods to authenticated users (from Authenticated Access)
-
authenticated users may create a single Renter to represent themselves (provided)
-
authenticated users may modify a renter that matches their login (provided)
-
authenticated users may delete a renter that matches their login or have the
MGR
role (provided) -
only authenticated users with the
ADMIN
role can delete all renters (new)
-
-
AutoRentals (annotation-based authorizations) — through the use of
@PreAuthorize
and programmatic checks.-
authenticated users may create an AutoRental for an existing Renter matching their identity (new)
-
authenticated users may update an
AutoRental
for anAutoRental
they own (new)i.e. caller "sueann" can update (change time period) for an existing AutoRental associated with identity "sueann" -
authenticated users with
PROXY
authority may create and update an AutoRental for anyRenter
(new)i.e. caller "lou" and "murry" can create or update (change time period) any existing AutoRental for any Renter -
authenticated users may only delete a AutoRental for a Renter matching their username (new)
-
authenticated users with the
MGR
role may delete any AutoRental (new) -
authenticated users with the
ADMIN
role may delete all AutoRentals (new)
-
-
-
Form the following role inheritance
-
ROLE_ADMIN
inherits fromROLE_MGR
so that users withADMIN
role will also be able to performMGR
role operationsRegister a RoleHierarchy
relationship definition between inheriting roles.
-
-
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. -
Create unit integration test(s) to demonstrate the behavior defined above
4.2.4. Grading
Your solution will be evaluated on:
-
implement URI path-based authorization constraints
-
whether path-based authorization constraints were properly defined and used for Auto and Renter resource URIs
-
-
implement annotation-based authorization constraints
-
whether expression-based authorization constraints were properly defined for AutoRental service methods
-
-
implement role inheritance
-
whether users with the
ADMIN
role were allowed to invoke methods constrained to theMGR
role.
-
-
implement an AccessDeniedException controller advice to hide necessary stack trace information and provide useful error information to the caller
-
whether stack trace or other excessive information was hidden from the access denied caller response
-
4.2.5. Additional Details
-
Role inheritance can be defined using a
RoleHierarchy
bean. -
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.
-
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 throwAccessDeniedException
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:
-
generate a self-signed certificate for demonstration use
-
enable HTTPS/TLS within Spring Boot
-
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.
5.1.3. Requirements
-
Implement an HTTPS-only communication in the server when running with the
https
profile.-
package a demonstration certificate in the
src
tree -
supply necessary server-side properties in a properties file
With this in place and the application started with the
authorities
,authorization
, andhttps
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"}
-
-
Implement a unit integration test that will
-
activate the
authorities
,authorization
, andhttps
profiles -
establish an HTTPS connection with
RestTemplate
orWebClient
-
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:
-
generate a self-signed certificate for demonstration use
-
whether a demonstration PKI certificate was supplied for demonstrating the HTTPS capability of the application
-
-
enable HTTPS/TLS within Spring Boot
-
whether the integration test client was able to perform round-trip communication with the server
-
whether the communications used HTTPS protocol
-
5.1.5. Additional Details
-
There is no requirement to implement an HTTP to HTTPS redirect
-
See the
svc/svc-security/https-hello-example
for Maven andRestTemplate
setup example. -
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:
-
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.
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
-
implement the
getGetterMethod()
to locate and return thejava.lang.reflect.Method
for the requested (getter) method name.-
return the
Method
object if it exists -
use an
Optional<Method>
return type and return anOptional.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.
-
-
implement the
getValue()
method to return the value reported by invoking the getter method using reflection-
return the value returned
-
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
-
-
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:
-
obtain a method reference and invoke it using Java Reflection
-
whether you are able to return a reference to the specified getter method using reflection
-
whether you are able to obtain the current state of the property using the getter method reference using reflection
-
6.1.5. Additional Details
-
The JUnit test (
MyD1_ReflectionMethodTest
) is supplied and should not need to be modified beyond enabling it. -
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:
-
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.
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
-
implement the details for the
NullValidatorHandler
class that-
implements the
java.lang.reflect.InvocationHandler
interface -
has implementation properties
-
nullPropertyAssertion — implements the check (from the previous section)
-
target object that it is the proxy for
-
methodName it will operate against on the target object
-
nullProperties — propertyNames it will test for null (used also in the previous section)
-
nonNullProperties — propertyNames it will test for non-null (used also in the previous section)
-
-
has a static builder method called
newInstance
that accepts the above properties and returns ajava.lang.reflect.Proxy
implementing all interfaces of the targetIn 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. -
implements the
invoke()
method of theInvocationHandler
interface to validate the arguments passed to the method matching themethodName
. Checks properties matchingnullProperties
andnonNullProperties
.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 InstanceTest 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 PropertiesTest 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);
-
if the arguments are found to be in error — let the nullPropertyAssertion throw its exception
-
otherwise allow the call to continue to the target object/method with provided args
Validator limitsEach 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
-
-
-
Use the provided JUnit tests (
MyD2_DynamnicProxyNTest
) verify the functionality of the requirements above once you activate.-
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:
-
create a JDK Dynamic Proxy to implement adhoc interface(s) to form a proxy at runtime for implementing advice
-
whether you created a
java.lang.reflect.InvocationHandler
that would perform the validation on proxied targets -
whether you successfully created and returned a dynamic proxy from the
newInstance
factory method -
whether your returned proxy was able to successfully validate arguments passed to target
-
6.2.5. Additional Details
-
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.
-
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()
. -
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:
-
implement dynamically assigned behavior to methods using Spring Aspect-Oriented Programming (AOP) and AspectJ
-
identify method join points to inject using pointcut expressions
-
implement advice that executes before join points
-
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.
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
-
add AOP dependencies using the
spring-boot-starter-aop
module (provided) -
enable AspectJ auto proxy handling within your application
-
create a
ValidatorAspect
component. The shell of a class and@Bean
factory have been provided.-
inject a
NullPropertyAssertion
bean (implemented in the previous section) andList<MethodConstraints>
(populated from application-aop.yaml from the support module; provided)-
NullPropertyAssertion
@Bean
factory should already be in place from the previous section -
List<MethodConstraints>
@Bean
factory is provided for you in the starter module
-
-
annotate the Aspect class that will contain the pointcut(s) and advice with "@Aspect"
-
make the overall component conditional on the
aop
profileThis 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.
-
-
define a "pointcut" that will target the calls to the SecureAutosWrapper and SecureRentersWrapper services. This pointcut should both:
-
define a match pattern for the "join point"
Start with Lenient PatternStart with a lenient pattern for initial success and look to optimize with more precise matching over time.
-
-
define an "advice" method to execute before the "join point"
-
uses the previously defined "pointcut" to identify its "join point"
-
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. -
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);
-
-
Use the provided JUnit test cases to verify completion of the above requirements
-
the initial
MyD3a1_NoAspectSvcNTest
deactivates theaop
profile to demonstrate baseline behavior we want to change/not-change -
the second
MyD3a2_AspectSvcNTest
activates theaop
profile and asserts different results -
the third
MyD3b_AspectWebNTest
demonstrates that the aspect was added to the beans injected into the Autos and Renters controllers.If AspectSvcNTest
passes, andAspectWebNTest
fails, check that you are advising the secure wrapper and not the base service.
-
6.3.4. Grading
Your solution will be evaluated on:
-
implement dynamically assigned behavior to methods using Spring Aspect-Oriented Programming (AOP) and AspectJ
-
whether you activated aspects in your solution
-
whether you supplied an aspect as a component
-
-
identify method join points to inject using pointcut expressions
-
whether your aspect class contained a pointcut that correctly matched the target method join points
-
-
implement advice that executes before join points
-
whether your solution implements required validation before allowing target to execute
-
whether your solution will allow the target to execute if validation passes
-
whether your solution will prevent the target from executing and report the error if validation fails
-
-
implement parameter injection into advice
-
whether you have implemented typed or dynamic access to the arguments passed to the target method
-
6.3.5. Additional Details
-
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]
-
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. -
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
andRenter
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. -
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.