Spring-Boot / Spring Security with AngularJS : Part II (Dynamic resource load from Angular) - 2020
This post is a continuation from the previous post: Spring-Boot / Spring Security with AngularJS - Part I.
Let's create a file, src/main/resources/static/js/hello.js for our "hello" application. The <script/> at the bottom of our index.html will finds it.
It's a minimal form of Angular JS application, and it looks like this:
angular.module('hello', []) .controller('home', function($scope) { $scope.greeting = {id: 'xxx', content: 'Hello World!'} })
The name of the application is "hello" and it has an empty (omitted) "config" and a "controller" called "home".
The "home" controller will be called when we load the "index.html" because as shown below we have decorated the content <div> with ng-controller="home" in our "index.html":
<div ng-controller="home" ng-cloak class="ng-cloak"> <p>The ID is {{greeting.id}}</p> <p>The content is {{greeting.content}}</p> </div>
Note that $scope is injected into the controller function (Angular does dependency injection by naming convention, and recognizes the names of our function parameters). The $scope is then used inside the function to set up content and behavior for the UI elements that this controller is responsible for.
The greeting is rendered by Angular in the HTML using the placeholders, {{greeting.id}} and {{greeting.content}}.
Notice that our index.html can now recognize the greeting and it's id and content thanks to the $scope injection.
To make our code more clear, let's make two modifications to our codes.
Personally, I do not completely agree with this modification since I've been using Angular the way before this changes. But let's modify our codes as recommended by spring.io.
index.html
From:
<body ng-app="hello"> <div class="container"> <h1>Greeting</h1> <div ng-controller="home" ng-cloak class="ng-cloak"> <p>The ID is {{greeting.id}}</p> <p>The content is {{greeting.content}}</p> </div> </div> <script src="js/angular-bootstrap.js" type="text/javascript"></script> <script src="js/hello.js"></script> </body>
To:
<body ng-app="hello"> <div class="container"> <h1>Greeting</h1> <div ng-controller="home as home" ng-cloak class="ng-cloak"> <p>The ID is {{home.greeting.id}}</p> <p>The content is {{home.greeting.content}}</p> </div> </div> <script src="js/angular-bootstrap.js" type="text/javascript"></script> <script src="js/hello.js"></script> </body>
Note that we explicitly declared a namespace for the controller, and use the controller instance itself, rather than using the implicit $scope when we bind in the UI.
Another file to change is hello.js
From:
angular.module('hello', []) .controller('home', function($scope) { $scope.greeting = {id: 'xxx', content: 'Hello World!'} })
To:
angular.module('hello', []) .controller('home', function() { this.greeting = {id: 'xxx', content: 'Hello World!'} })
As we can see from the client code above, we also changed the binding the greeting to the controller (using this) instead of to $scope.
Let's look into our hello.js code:
angular.module('hello', []) .controller('home', function($scope) { $scope.greeting = {id: 'xxx', content: 'Hello World!'} })
As we can see, the greeting is hard coded. However, we expect content to come from a backend server, so we need to create an HTTP endpoint that we can use to grab a greeting.
In our application class, src/main/java/com/example/DemoApplication.java), add the @RestController annotation and define a new @RequestMapping:
From:
package com.example; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
To:
package com.example; import java.util.HashMap; import java.util.Map; import java.util.UUID; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @SpringBootApplication @RestController public class DemoApplication { @RequestMapping("/resource") public Map<String,Object> home() { Map<String,Object> model = new HashMap<String,Object>(); model.put("id", UUID.randomUUID().toString()); model.put("content", "Hello World"); return model; } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
Note that we added the @RestController annotation and define a new @RequestMapping.
Let's run the app:
$ mvn spring-boot:run
Then, we'll get the following response:
If we try to check the response using curl, we will see that it is secure by default:
$ curl localhost:8080/resource {"timestamp":1456870221658,"status":401,"error":"Unauthorized","message":"Full authentication is required to access this resource","path":"/resource"}
Let's modify the home controller (hello.js) to load the protected resource using XMLHttpRequest (XHR).
From:
angular.module('hello', []) .controller('home', function() { this.greeting = {id: 'xxx', content: 'Hello World!'} })
To:
angular.module('hello', []) .controller('home', function($http) { var self = this; $http.get('/resource/').success(function(data) { self.greeting = data; }) });
We injected an $http service, which is provided by Angular as a core feature, and used it to GET our resource. Angular passes us the JSON from the response body back to a callback function on success.
Run the application again (or just reload the home page in the browser):
$ mvn spring-boot:run
We can see the dynamic message with its unique ID.
Even though the resource is protected and we can't curl it directly, the browser was able to access the content.
Here is the table showing the requests and responses:
Verb | Path | Status | Response |
---|---|---|---|
GET | / | 401 | Browser prompts for authentication |
GET | / | 200 | index.html |
GET | /css/angular-bootstrap.css | 200 | Twitter bootstrap CSS |
GET | /js/angular-bootstrap.js | 200 | Bootstrap and Angular JS |
GET | /js/hello.js | 200 | Application logic |
GET | /resource | 200 | JSON greeting |
Note that:
- We may not see the 401 because the browser treats the home page load as a single interaction.
- We may see 2 requests for "/resource" because there is a CORS negotiation.
Look more closely at the requests and we can see that all of them have an "Authorization" header, something like the one that's being highlighted:
In the next post in this series, we will extend the application to use form-based.
Continued to Spring-Boot / Spring Security with AngularJS : Part III (Form-based Authentication) .
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization