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 homesales-starter/assignment3-homesales-security/homesales-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 HomeSalesService 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 HomeSaleService 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 may use the deprecated
WebSecurityConfigurerAdapter
or its component-based replacement. Each profile will start over. The starter is configured for the new component-based approach. 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 HomeSaleDTO references to your HomeSale DTO class.
-
-
For the other server-side portions of the assignment
-
there is a skeletal
IdentityController
that lays out portions of thewhoAmI
andauthorities
endpoints. -
there is a skeletal
SecureHomeSalesServiceWrapper
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. 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 homebuyers-support/homebuyers-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 Homes and Buyers.
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 homebuyers-support-security-svc
adds this layer to the homebuyers-support-api-svc
components.
The following dependency can replace your current dependency on the homebuyers-support-api-svc
.
<dependency>
<groupId>info.ejava.assignments.security.homesales</groupId>
<artifactId>homebuyers-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 HomeSale service. The following test dependency can provide you with many test constructs.
<dependency>
<groupId>info.ejava.assignments.security.homesales</groupId>
<artifactId>homebuyers-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
-
Home and Buyer services that will operate within your application and will be secured by your security configuration. Necessary internal security checks are included within the Home and Buyer 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 HomeSale 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.
3.1.3. Requirements
-
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 HomeSales
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
Configure HttpSecurity to ignore all calls below /content/
. Leverage theantMatchers()
to express the pattern./content/**
blob indicates "anything below"/content
. -
HEAD
for all resources -
GET
for home and homeSale resources..Configure HttpSecurity to permit all HEAD
calls matching any URI — whether it exists or not. Leverage theantMatcher()
to express the method and pattern.Configure HttpSecurity to permit all GET
calls matching URIs below homes and homeSales. Leverage theantMatcher()
to express the method and pattern.
-
-
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. -
Configure authenticated access for the following resource operations
-
GET
calls for buyer resources. No one can gain access to a Buyer without being authenticated. -
non-safe calls (
POST
,PUT
, andDELETE
) for homes, buyers, and homeSales resourcesConfigure 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 home and buyer resources -
anonymous user access granted to a
GET
call to home and homeSale resources -
anonymous user access denial to a
GET
call to buyer 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.
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
orWebClient
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 WebClient
) and tracking the caller’s identity within the application.
You’re starting point should be an application with functional Homes, Buyers, and HomeSales API services. Homes and Buyers were provided to you. You implemented HomeSales in assignment2. All functional tests for HomeSales are passing.
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
-
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 + 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 home, buyer, and homeSale 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
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/homes -H 'Content-Type: application/json' -d '{ ... }' { ... }
3.2.4. Grading
Your solution will be evaluated on:
-
add an authenticated identity to
RestTemplate
orWebClient
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 home, buyer, and homeSale 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 homebuyers-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
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 Accounts
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
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 home, buyer, and homeSale 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 Home, Buyer, and HomeSale
-
-
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 6. Authorities Test Resource
|
Figure 7. Assignment Principles and Authorities
|
The homebuyers-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
4.1.3. Requirements
-
Create an
api/authorities
resource with aGET
method-
accepts an "authority" query parameter
-
returns (textual) "TRUE" or "FALSE" depending on whether the caller has the authority assigned
-
-
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.
4.2.3. Requirements
-
Update the resources to store the identity of the caller with the resource created to identify the owner
-
Homes will store the username (as username) of the home creator (provided)
-
Buyers will store the username (as username) of the buyer creator (provided)
-
HomeSales should store the username (as username) of the home it represents (new)
The Home creator may create the HomeSale or a user with the PROXY permission (murry) may create the HomeSale for the creator of the Home. -
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.
-
-
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 Home and Buyer 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 homes and buyers calls -
Use expression-based authorizations for homeSales resources, applied to the service methods
Use annotations on HomeSale service methods to trigger authorization checks. Remember to enable global method security for the prePostEnabled
annotation form for your application and tests.
-
-
Restrict access according to the following
-
Homes (path-based authorizations)
-
continue to restrict non-safe methods to authenticated users (from Authenticated Access)
-
authenticated users may modify homes that match their login (provided)
-
authenticated users may delete homes that match their login or have the
MGR
role (provided) -
only authenticated users with the
ADMIN
role can delete all homes (new)
-
-
Buyers (path-based authorizations)
-
continue to restrict non-safe methods to authenticated users (from Authenticated Access)
-
authenticated users may create a single Buyer to represent themselves (provided)
-
authenticated users may modify a buyer that matches their login (provided)
-
authenticated users may delete a buyer that matches their login or have the
MGR
role (provided) -
only authenticated users with the
ADMIN
role can delete all buyers (new)
-
-
HomeSales (annotation-based authorizations) — through the use of
@PreAuthorize
and programmatic checks.-
authenticated users may create a HomeSale for an existing Home they created (new)
-
authenticated users with the
MEMBER
role may update (purchase) aHomeSale
with aBuyer
they created (new)i.e. caller "sueann" can update (purchase) existing HomeSale (owned by ted
) with Buyer representing "sueann" -
authenticated users with the
MGR
role orPROXY
permission may update (purchase) a HomeSale with anyBuyer
(new)i.e. caller "lou" and "murry" can update (purchase) any existing HomeSale for any Buyer -
authenticated users may only delete a HomeSale for a Home matching their username (new)
-
authenticated users with the
MGR
role may delete any HomeSale (new) -
authenticated users with the
ADMIN
role may delete all HomeSales (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 Home and Buyer resource URIs
-
-
implement annotation-based authorization constraints
-
whether expression-based authorization constraints were properly defined for HomeSale 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.
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 a Maven Failsafe integration 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/homesales-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/homes -u mary:secret $ curl -k -X DELETE https://localhost:8443/api/homes -u sueann:betty {"timestamp":"2022-09-25T00:51:41.200+00:00","status":403,"error":"Forbidden","path":"/api/homes"}
-
-
Implement an IT/Failsafe integration test that will
-
start the server with the
authorities
,authorization
, andhttps
profiles activeThe starter module has the Maven pom.xml plugin basics for this. -
configure and start JUnit with an integration test
The starter module has the Maven pom.xml plugin basics for this. -
establish an HTTPS connection with
RestTemplate
orWebClient
The starter module provides a @TestConfiguration
class that will instantiate a RestTemplate capable of using HTTPS and the test activates the "its" Spring profile. You need to supply the details of the HTTPS client properties within that (missing) properties file. -
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 Home and Buyer service behavior without changing the Homes/Buyers 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 Homes/Buyers Compilation Dependencies
The src/main portions of the assignment must have no compilation dependency on Homes and Buyers.
All compilation dependencies and most knowledge of Homes and Buyers 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 obtain 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 homebuyers-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 obtain 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 Home/Buyer Compilation Dependency
Note that you see no mention of Home or Buyer 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.homesales</groupId>
<artifactId>homesales-support-aop</artifactId>
<version>${ejava.version}</version>
</dependency>
<dependency>
<groupId>info.ejava.assignments.aop.homesales</groupId>
<artifactId>homesales-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-HomeDTO/BuyerDTO objects on purpose. The validator under test must work with any type of object passed in. Only "getter" 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 state 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 HomesService and BuyersService from the Spring context.
No HomeDTO/BuyerDTO Compilation Dependency
There will be no mention of or direct dependency on Homes or Buyers 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 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 previous section)
-
nonNullProperties — propertyNames it will test for non-null (used also in 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
.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.
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 HomeService and BuyerService @Autowired from the Spring context, but only instantiates the proxy as a POJO within the test case.
-
The JUnit tests make calls to the HomeService and BuyerService, 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 provided secure Home/Buyer wrapper services.
The aspect will be part of your overall Spring context and will be able to change the behavior of the Homes and Buyers 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 Homes or Buyers.
No HomeDTO/BuyerDTO Compilation Dependency
There will be no direct dependency on Homes or Buyers.
Everything will be accomplished using AOP expressions and Java reflection constructs.
|
6.3.3. Requirements
-
add AOP dependencies using the
spring-boot-starter-aop
module -
enable AspectJ auto proxy handling within your application
-
create a
ValidatorAspect
component-
inject a
NullPropertyAssertion
bean andList<MethodConstraints>
(populated from application-aop.yaml from the support module) -
make the class an Aspect
-
make the overall component conditional on the
aop
profileThis means the Home and Buyer 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 SecureHomesWrapper and SecureBuyersWrapper services. This pointcut should both:
-
define a match pattern for the "join point"
-
-
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 Homes and Buyers 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: createHome isNull: [id, username] notNull: [value, yearBuilt, bedRooms ] - methodName: updateHome isNull: [username] - methodName: createBuyer isNull: [id, username] notNull: [firstName, lastName, email] - methodName: updateBuyer isNull: [username]
-
The JUnit test case uses real, secured HomesService and BuyerServices @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.Note: The
Home
andBuyer
services has some base validation built in and will not accept a non-null ID during the create calls. You cannot change that behavior and will not have to. -
Ungraded activity — create a breakpoint in the Advice and Homes/BuyersService(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.