├── .gitignore ├── README.adoc ├── authserver ├── pom.xml └── src │ ├── main │ ├── java │ │ └── demo │ │ │ └── AuthserverApplication.java │ └── resources │ │ ├── application.properties │ │ └── keystore.jks │ └── test │ └── java │ └── demo │ └── ApplicationTests.java ├── docker-compose.yml ├── eureka ├── pom.xml └── src │ └── main │ ├── java │ └── demo │ │ └── EurekaApplication.java │ └── resources │ └── application.properties ├── pom.xml ├── resource ├── pom.xml └── src │ ├── main │ ├── java │ │ └── demo │ │ │ └── ResourceApplication.java │ └── resources │ │ └── application.properties │ └── test │ └── groovy │ └── demo │ └── ApplicationTests.groovy └── ui ├── pom.xml └── src ├── main ├── java │ └── demo │ │ └── UiApplication.java ├── resources │ ├── application.yml │ └── static │ │ ├── home.html │ │ ├── index.html │ │ └── js │ │ └── hello.js └── wro │ ├── main.less │ ├── wro.properties │ └── wro.xml └── test └── java └── demo └── ApplicationTests.java /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | #* 3 | *# 4 | .#* 5 | .classpath 6 | .project 7 | .settings/ 8 | .springBeans 9 | target/ 10 | bin/ 11 | _site/ 12 | .idea 13 | *.iml 14 | .factorypath 15 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | [[_sso_with_oauth2_angular_js_and_spring_security_part_v]] 2 | = Single Sign On with OAuth2 3 | 4 | In this section we continue <<_the_api_gateway_pattern_angular_js_and_spring_security_part_iv,our discussion>> of how to use http://projects.spring.io/spring-security[Spring Security] with http://angularjs.org[Angular JS] in a "single page application". Here we show how to use http://projects.spring.io/spring-security-oauth/[Spring Security OAuth] together with http://projects.spring.io/spring-cloud/[Spring Cloud] to extend our API Gateway to do Single Sign On and OAuth2 token authentication to backend resources. This is the fifth in a series of sections, and you can catch up on the basic building blocks of the application or build it from scratch by reading the <<_spring_and_angular_js_a_secure_single_page_application,first section>>, or you can just go straight to the https://github.com/dsyer/spring-security-angular/tree/master/oauth2[source code in Github]. In the <<_the_api_gateway_pattern_angular_js_and_spring_security_part_iv,last section>> we built a small distributed application that used https://github.com/spring-projects/spring-session/[Spring Session] to authenticate the backend resources and http://projects.spring.io/spring-cloud/[Spring Cloud] to implement an embedded API Gateway in the UI server. In this section we extract the authentication responsibilities to a separate server to make our UI server the first of potentially many Single Sign On applications to the authorization server. This is a common pattern in many applications these days, both in the enterprise and in social startups. We will use an OAuth2 server as the authenticator, so that we can also use it to grant tokens for the backend resource server. Spring Cloud will automatically relay the access token to our backend, and enable us to further simplify the implementation of both the UI and resource servers. 5 | 6 | ____ 7 | Reminder: if you are working through this section with the sample application, be sure to clear your browser cache of cookies and HTTP Basic credentials. In Chrome the best way to do that for a single server is to open a new incognito window. 8 | ____ 9 | 10 | == Creating an OAuth2 Authorization Server 11 | 12 | Our first step is to create a new server to handle authentication and token management. Following the steps in <<_spring_and_angular_js_a_secure_single_page_application,Part I>> we can begin with https://start.spring.io[Spring Boot Initializr]. E.g. using curl on a UN*X like system: 13 | 14 | [source] 15 | ---- 16 | $ curl https://start.spring.io/starter.tgz -d style=web \ 17 | -d style=security -d name=authserver | tar -xzvf - 18 | ---- 19 | 20 | You can then import that project (it's a normal Maven Java project by default) into your favourite IDE, or just work with the files and "mvn" on the command line. 21 | 22 | === Adding the OAuth2 Dependencies 23 | 24 | We need to add the http://projects.spring.io/spring-security-oauth[Spring OAuth] dependencies, so in our https://github.com/dsyer/spring-security-angular/blob/master/oauth2/authserver/pom.xml[POM] we add: 25 | 26 | .pom.xml 27 | [source,xml] 28 | ---- 29 | 30 | org.springframework.security.oauth 31 | spring-security-oauth2 32 | 2.0.5.RELEASE 33 | 34 | ---- 35 | 36 | The authorization server is pretty easy to implement. A minimal version looks like this: 37 | 38 | .AuthserverApplication.java 39 | [source,java] 40 | ---- 41 | @SpringBootApplication 42 | @EnableAuthorizationServer 43 | public class AuthserverApplication extends WebMvcConfigurerAdapter { 44 | 45 | public static void main(String[] args) { 46 | SpringApplication.run(AuthserverApplication.class, args); 47 | } 48 | 49 | } 50 | ---- 51 | 52 | We only have to do 1 more thing (after adding `@EnableAuthorizationServer`): 53 | 54 | .application.properties 55 | [source,properties] 56 | --- 57 | ... 58 | security.oauth2.client.clientId: acme 59 | security.oauth2.client.clientSecret: acmesecret 60 | security.oauth2.client.authorized-grant-types: authorization_code,refresh_token,password 61 | security.oauth2.client.scopes: openid 62 | --- 63 | 64 | This registers a client "acme" with a secret and some authorized grant types including "authorization_code". 65 | 66 | Now let's get it running on port 9999, with a predictable password for testing: 67 | 68 | .application.properties 69 | [source,properties] 70 | ---- 71 | server.port=9999 72 | security.user.password=password 73 | server.contextPath=/uaa 74 | ... 75 | ---- 76 | 77 | We also set the context path so that it doesn't use the default ("/") because otherwise you can get cookies for other servers on localhost being sent to the wrong server. So get the server running and we can make sure it is working: 78 | 79 | [source] 80 | ---- 81 | $ mvn spring-boot:run 82 | ---- 83 | 84 | or start the `main()` method in your IDE. 85 | 86 | === Testing the Authorization Server 87 | 88 | Our server is using the Spring Boot default security settings, so like the server in <<_spring_and_angular_js_a_secure_single_page_application,Part I>> it will be protected by HTTP Basic authentication. To initiate an https://tools.ietf.org/html/rfc6749#section-1.3.1[authorization code token grant] you visit the authorization endpoint, e.g. at http://localhost:9999/uaa/oauth/authorize?response_type=code&client_id=acme&redirect_uri=http://example.com[http://localhost:9999/uaa/oauth/authorize?response_type=code&client_id=acme&redirect_uri=http://example.com] once you have authenticated you will get a redirect to example.com with an authorization code attached, e.g. http://example.com/?code=jYWioI[http://example.com/?code=jYWioI]. 89 | 90 | NOTE: for the purposes of this sample application we have created a client "acme" with no registered redirect, which is what enables us to get a redirect the example.com. In a production application you should always register a redirect (and use HTTPS). 91 | 92 | The code can be exchanged for an access token using the "acme" client credentials on the token endpoint: 93 | 94 | [source] 95 | ---- 96 | $ curl acme:acmesecret@localhost:9999/uaa/oauth/token \ 97 | -d grant_type=authorization_code -d client_id=acme \ 98 | -d redirect_uri=http://example.com -d code=jYWioI 99 | {"access_token":"2219199c-966e-4466-8b7e-12bb9038c9bb","token_type":"bearer","refresh_token":"d193caf4-5643-4988-9a4a-1c03c9d657aa","expires_in":43199,"scope":"openid"} 100 | ---- 101 | 102 | The access token is a UUID ("2219199c…"), backed by an in-memory token store in the server. We also got a refresh token that we can use to get a new access token when the current one expires. 103 | 104 | NOTE: since we allowed "password" grants for the "acme" client we can also get a token directly from the token endpoint using curl and user credentials instead of an authorization code. This is not suitable for a browser based client, but it's useful for testing. 105 | 106 | If you followed the link above you would have seen the whitelabel UI provided by Spring OAuth. To start with we will use this and we can come back later to beef it up like we did in <<_the_login_page_angular_js_and_spring_security_part_ii,Part II>> for the self-contained server. 107 | 108 | [[changing-the-resource-server]] 109 | == Changing the Resource Server 110 | 111 | If we follow on from <<_the_api_gateway_pattern_angular_js_and_spring_security_part_iv,Part IV>>, our resource server is using https://github.com/spring-projects/spring-session/[Spring Session] for authentication, so we can take that out and replace it with Spring OAuth. We also need to remove the Spring Session and Redis dependencies, so replace this: 112 | 113 | .pom.xml 114 | [source,xml] 115 | ---- 116 | 117 | org.springframework.session 118 | spring-session 119 | 120 | 121 | org.springframework.boot 122 | spring-boot-starter-redis 123 | 124 | ---- 125 | 126 | with this: 127 | 128 | .pom.xml 129 | [source,xml] 130 | ---- 131 | 132 | org.springframework.security.oauth 133 | spring-security-oauth2 134 | 135 | ---- 136 | 137 | and then remove the session `Filter` from the https://github.com/dsyer/spring-security-angular/blob/master/vanilla-oauth2/resource/src/main/groovy/demo/ResourceApplication.groovy[main application class], replacing it with the convenient `@EnableResourceServer` annotation (from Spring Security OAuth2): 138 | 139 | .ResourceApplication.groovy 140 | [source,java] 141 | ---- 142 | @SpringBootApplication 143 | @RestController 144 | @EnableResourceServer 145 | class ResourceApplication { 146 | 147 | @RequestMapping('/') 148 | def home() { 149 | [id: UUID.randomUUID().toString(), content: 'Hello World'] 150 | } 151 | 152 | static void main(String[] args) { 153 | SpringApplication.run ResourceApplication, args 154 | } 155 | } 156 | 157 | ---- 158 | 159 | With that one change the app is ready to challenge for an access token instead of HTTP Basic, but we need a config change to actually finish the process. We are going to add a small amount of external configuration (in "application.properties") to allow the resource server to decode the tokens it is given and authenticate a user: 160 | 161 | .application.properties 162 | [source,properties] 163 | ---- 164 | ... 165 | security.oauth2.resource.userInfoUri: http://localhost:9999/uaa/user 166 | ---- 167 | 168 | This tells the server that it can use the token to access a "/user" endpoint and use that to derive authentication information (it's a bit like the https://developers.facebook.com/docs/graph-api/reference/v2.2/user/?locale=en_GB["/me" endpoint] in the Facebook API). Effectively it provides a way for the resource server to decode the token, as expressed by the `ResourceServerTokenServices` interface in Spring OAuth2. 169 | 170 | Run the application and hit the home page with a command line client: 171 | 172 | [source] 173 | ---- 174 | $ curl -v localhost:9000 175 | > GET / HTTP/1.1 176 | > User-Agent: curl/7.35.0 177 | > Host: localhost:9000 178 | > Accept: */* 179 | > 180 | < HTTP/1.1 401 Unauthorized 181 | ... 182 | < WWW-Authenticate: Bearer realm="null", error="unauthorized", error_description="An Authentication object was not found in the SecurityContext" 183 | < Content-Type: application/json;charset=UTF-8 184 | {"error":"unauthorized","error_description":"An Authentication object was not found in the SecurityContext"} 185 | ---- 186 | 187 | and you will see a 401 with a "WWW-Authenticate" header indicating that it wants a bearer token. 188 | 189 | NOTE: the `userInfoUri` is by far not the only way of hooking a resource server up with a way to decode tokens. In fact it's sort of a lowest common denominator (and not part of the spec), but quite often available from OAuth2 providers (like Facebook, Cloud Foundry, Github), and other choices are available. For instance you can encode the user authentication in the token itself (e.g. with http://jwt.io/[JWT]), or use a shared backend store. There is also a `/token_info` endpoint in CloudFoundry, which provides more detailed information than the user info endpoint, but which requires more thorough authentication. Different options (naturally) provide different benefits and trade-offs, but a full discussion of those is outside the scope of this section. 190 | 191 | == Implementing the User Endpoint 192 | 193 | On the authorization server we can easily add that endpoint 194 | 195 | .AuthserverApplication.java 196 | [source,java] 197 | ---- 198 | @SpringBootApplication 199 | @RestController 200 | @EnableAuthorizationServer 201 | @EnableResourceServer 202 | public class AuthserverApplication { 203 | 204 | @RequestMapping("/user") 205 | public Principal user(Principal user) { 206 | return user; 207 | } 208 | 209 | ... 210 | 211 | } 212 | ---- 213 | 214 | We added a `@RequestMapping` the same as the UI server in <<_the_login_page_angular_js_and_spring_security_part_ii,Part II>>, and also the `@EnableResourceServer` annotation from Spring OAuth, which by default secures everything in an authorization server except the "/oauth/*" endpoints. 215 | 216 | With that endpoint in place we can test it and the greeting resource, since they both now accept bearer tokens that were created by the authorization server: 217 | 218 | [source] 219 | ---- 220 | $ TOKEN=2219199c-966e-4466-8b7e-12bb9038c9bb 221 | $ curl -H "Authorization: Bearer $TOKEN" localhost:9000 222 | {"id":"03af8be3-2fc3-4d75-acf7-c484d9cf32b1","content":"Hello World"} 223 | $ curl -H "Authorization: Bearer $TOKEN" localhost:9999/uaa/user 224 | {"details":...,"principal":{"username":"user",...},"name":"user"} 225 | ---- 226 | 227 | (substitute the value of the access token that you obtain from your own authorization server to get that working yourself). 228 | 229 | == The UI Server 230 | 231 | The final piece of this application we need to complete is the UI server, extracting the authentication part and delegating to the authorization server. So, as with link:#changing-the-resource-server[the resource server], we first need to remove the Spring Session and Redis dependencies and replace them with Spring OAuth2. 232 | 233 | Once that is done we can remove the session filter and the "/user" endpoint as well, and set up the application to redirect to the authorization server (using the `@EnableOAuth2Sso` annotation): 234 | 235 | .UiApplication.java 236 | [source,java] 237 | ---- 238 | @SpringBootApplication 239 | @EnableZuulProxy 240 | @EnableOAuth2Sso 241 | public class UiApplication { 242 | 243 | public static void main(String[] args) { 244 | SpringApplication.run(UiApplication.class, args); 245 | } 246 | 247 | ... 248 | 249 | } 250 | ---- 251 | 252 | Recall from <<_the_api_gateway_pattern_angular_js_and_spring_security_part_iv,Part IV>> that the UI server, by virtue of the `@EnableZuulProxy`, acts an API Gateway and we can declare the route mappings in YAML. So the "/user" endpoint can be proxied to the authorization server: 253 | 254 | .application.yml 255 | [source,yaml] 256 | ---- 257 | zuul: 258 | routes: 259 | resource: 260 | path: /resource/** 261 | url: http://localhost:9000 262 | user: 263 | path: /user/** 264 | url: http://localhost:9999/uaa/user 265 | ---- 266 | 267 | Lastly, we need to change the application to a `WebSecurityConfigurerAdapter` since now it is going to be used to modify the defaults in the SSO filter chain set up by `@EnableOAuth2Sso`: 268 | 269 | .SecurityConfiguration.java 270 | [source,java,indent=0] 271 | ---- 272 | @SpringBootApplication 273 | @EnableZuulProxy 274 | @EnableOAuth2Sso 275 | public class UiApplication extends WebSecurityConfigurerAdapter { 276 | @Override 277 | public void configure(HttpSecurity http) throws Exception { 278 | http.authorizeRequests().antMatchers("/index.html", "/home.html", "/") 279 | .permitAll().anyRequest().authenticated().and().csrf() 280 | .csrfTokenRepository(csrfTokenRepository()).and() 281 | .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class); 282 | } 283 | 284 | ... // the csrf*() methods are the same as the old WebSecurityConfigurerAdapter 285 | } 286 | ---- 287 | 288 | The main changes (apart from the base class name) are that the matchers 289 | go into their own method, and there is no need for `formLogin()` any more. 290 | 291 | There are also some mandatory external configuration properties for the 292 | `@EnableOAuth2Sso` annotation to be able to contact and authenticate with 293 | thr right authorization server. So we need this in `application.yml`: 294 | 295 | .application.yml 296 | [source,yaml] 297 | ---- 298 | security: 299 | ... 300 | oauth2: 301 | client: 302 | accessTokenUri: http://localhost:9999/uaa/oauth/token 303 | userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize 304 | clientId: acme 305 | clientSecret: acmesecret 306 | resource: 307 | userInfoUri: http://localhost:9999/uaa/user 308 | ---- 309 | 310 | The bulk of that is about the OAuth2 client ("acme") and the 311 | authorization server locations. There is also a `userInfoUri` (just 312 | like in the resource server) so that the user can be authenticated in 313 | the UI app itself. 314 | 315 | === In the Client 316 | 317 | There are some minor tweaks to the UI application on the front end that we still need to make to trigger the redirect to the authorization server. The first is in the navigation bar in "index.html" where the "login" link changes from an Angular route: 318 | 319 | .index.html 320 | [source,html] 321 | ---- 322 |
323 | 328 |
329 | ---- 330 | 331 | to a plain HTML link 332 | 333 | .index.html 334 | [source,html] 335 | ---- 336 |
337 | 342 |
343 | ---- 344 | 345 | The "/login" endpoint that this goes to is handled by Spring Security and if the user is not authenticated it will result in a redirect to the authorization server. 346 | 347 | We can also remove the definition of the `login()` function in the "navigation" controller, and the "/login" route from the Angular configuration, which simplifies the implementation a bit: 348 | 349 | .hello.js 350 | [source,javascript] 351 | ---- 352 | angular.module('hello', [ 'ngRoute' ]).config(function($routeProvider) { 353 | 354 | $routeProvider.when('/', { 355 | templateUrl : 'home.html', 356 | controller : 'home' 357 | }).otherwise('/'); 358 | 359 | }). // ... 360 | .controller('navigation', 361 | 362 | function($rootScope, $scope, $http, $location, $route) { 363 | 364 | $http.get('user').success(function(data) { 365 | if (data.name) { 366 | $rootScope.authenticated = true; 367 | } else { 368 | $rootScope.authenticated = false; 369 | } 370 | }).error(function() { 371 | $rootScope.authenticated = false; 372 | }); 373 | 374 | $scope.credentials = {}; 375 | 376 | $scope.logout = function() { 377 | $http.post('logout', {}).success(function() { 378 | $rootScope.authenticated = false; 379 | $location.path("/"); 380 | }).error(function(data) { 381 | $rootScope.authenticated = false; 382 | }); 383 | } 384 | 385 | }); 386 | ---- 387 | 388 | == How Does it Work? 389 | 390 | Run all the servers together now, and visit the UI in a browser at http://localhost:8080[http://localhost:8080]. Click on the "login" link and you will be redirected to the authorization server to authenticate (HTTP Basic popup) and approve the token grant (whitelabel HTML), before being redirected to the home page in the UI with the greeting fetched from the OAuth2 resource server using the same token as we authenticated the UI with. 391 | 392 | The interactions between the browser and the backend can be seen in your browser if you use some developer tools (usually F12 opens this up, works in Chrome by default, may require a plugin in Firefox). Here's a summary: 393 | 394 | |=== 395 | |Verb |Path |Status |Response 396 | 397 | |GET |/ |200 |index.html 398 | |GET |/css/angular-bootstrap.css |200 |Twitter bootstrap CSS 399 | |GET |/js/angular-bootstrap.js |200 |Bootstrap and Angular JS 400 | |GET |/js/hello.js |200 |Application logic 401 | |GET |/home.html |200 |HTML partial for home page 402 | |GET |/user |302 |Redirect to login page 403 | |GET |/login |302 |Redirect to auth server 404 | |GET |(uaa)/oauth/authorize |401 |(ignored) 405 | |GET |/resource |302 |Redirect to login page 406 | |GET |/login |302 |Redirect to auth server 407 | |GET |(uaa)/oauth/authorize |401 |(ignored) 408 | |GET |/login |302 |Redirect to auth server 409 | |GET |(uaa)/oauth/authorize |200 |HTTP Basic auth happens here 410 | |POST |(uaa)/oauth/authorize |302 |User approves grant, redirect to /login 411 | |GET |/login |302 |Redirect to home page 412 | |GET |/user |200 |(Proxied) JSON authenticated user 413 | |GET |/home.html |200 |HTML partial for home page 414 | |GET |/resource |200 |(Proxied) JSON greeting 415 | |=== 416 | 417 | The requests prefixed with (uaa) are to the authorization server. The responses that are marked "ignored" are responses received by Angular in an XHR call, and since we aren't processing that data they are dropped on the floor. We do look for an authenticated user in the case of the "/user" resource, but since it isn't there in the first call, that response is dropped. 418 | 419 | In the "/trace" endpoint of the UI (scroll down to the bottom) you will see the proxied backend requests to "/user" and "/resource", with `remote:true` and the bearer token instead of the cookie (as it would have been in <<_the_api_gateway_pattern_angular_js_and_spring_security_part_iv,Part IV>>) being used for authentication. Spring Cloud Security has taken care of this for us: by recognising that we has `@EnableOAuth2Sso` and `@EnableZuulProxy` it has figured out that (by default) we want to relay the token to the proxied backends. 420 | 421 | NOTE: As in previous sections, try to use a different browser for "/trace" so that there is no chance of authentication crossover (e.g. use Firefox if you used Chrome for testing the UI). 422 | 423 | == The Logout Experience 424 | 425 | If you click on the "logout" link you will see that the home page changes (the greeting is no longer displayed) so the user is no longer authenticated with the UI server. Click back on "login" though and you actually _don't_ need to go back through the authentication and approval cycle in the authorization server (because you haven't logged out of that). Opinions will be divided as to whether that is a desirable user experience, and it's a notoriously tricky problem (Single Sign Out: http://www.sciencedirect.com/science/article/pii/S2214212614000179[Science Direct article] and https://wiki.shibboleth.net/confluence/display/SHIB2/SLOIssues[Shibboleth docs]). The ideal user experience might not be technically feasible, and you also have to be suspicious sometimes that users really want what they say they want. "I want 'logout' to log me out" sounds simple enough, but the obvious response is, "Logged out of what? Do you want to be logged out of _all_ the systems controlled by this SSO server, or just the one that you clicked the 'logout' link in?" We don't have room to discuss this topic more broadly here but it does deserve more attention. If you are interested then there is some discussion of the principles and some (fairly unappetising) ideas about implementations in the http://openid.net/connect/[Open ID Connect] specification. 426 | 427 | == Conclusion 428 | 429 | This is almost the end of our shallow tour through the Spring Security and Angular JS stack. We have a nice architecture now with clear responsibilities in three separate components, UI/API Gateway, resource server and authorization server/token granter. The amount of non-business code in all layers is now minimal, and it's easy to see where to extend and improve the implementation with more business logic. The next steps will be to tidy up the UI in our authorization server, and probably add some more tests, including tests on the JavaScript client. Another interesting task is to extract all the boiler plate code and put it in a library (e.g. "spring-security-angular") containing Spring Security and Spring Session autoconfiguration and some webjars resources for the navigation controller in the Angular piece. Having read the sections in thir series, anyone who was hoping to learn the inner workings of either Angular JS or Spring Security will probably be disappointed, but if you wanted to see how they can work well together and how a little bit of configuration can go a long way, then hopefully you will have had a good experience. http://projects.spring.io/spring-cloud/[Spring Cloud] is new and these samples required snapshots when they were written, but there are release candidates available and a GA release coming soon, so check it out and send some feedback https://github.com/spring-cloud[via Github] or https://gitter.im/spring-cloud/spring-cloud[gitter.im]. 430 | 431 | The <<_multiple_ui_applications_and_a_gateway_single_page_application_with_spring_and_angular_js_part_vi,next section>> in the series is about access decisions (beyond authentication) and employs multiple UI applications behind the same proxy. 432 | 433 | == Addendum: Bootstrap UI and JWT Tokens for the Authorization Server 434 | 435 | You will find another version of this application in the https://github.com/dsyer/spring-security-angular/tree/master/oauth2[source code in Github] which has a pretty login page and user approval page implemented similarly to the way we did the login page in <<_the_login_page_angular_js_and_spring_security_part_ii,Part II>>. It also uses http://jwt.io/[JWT] to encode the tokens, so instead of using the "/user" endpoint, the resource server can pull enough information out of the token itself to do a simple authentication. The browser client still uses it, proxied through the UI server, so that it can determine if a user is authenticated (it doesn't need to do that very often, compared to the likely number of calls to a resource server in a real application). -------------------------------------------------------------------------------- /authserver/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | jar 6 | 7 | org.demo 8 | cloud-sso 9 | 0.0.1-SNAPSHOT 10 | 11 | authserver 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-web 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-security 20 | 21 | 22 | org.springframework.security.oauth 23 | spring-security-oauth2 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-test 28 | test 29 | 30 | 31 | org.apache.httpcomponents 32 | httpclient 33 | test 34 | 35 | 36 | 37 | demo.AuthserverApplication 38 | 1.7.6 39 | 40 | 41 | 42 | 43 | ${project.basedir}/src/main/resources 44 | 45 | 46 | ${project.build.directory}/generated-resources 47 | 48 | 49 | 50 | 51 | org.springframework.boot 52 | spring-boot-maven-plugin 53 | 54 | 55 | maven-resources-plugin 56 | 57 | 58 | 60 | copy-resources 61 | validate 62 | 63 | copy-resources 64 | 65 | 66 | ${basedir}/target/wro 67 | 68 | 69 | src/main/wro 70 | true 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /authserver/src/main/java/demo/AuthserverApplication.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import java.security.Principal; 4 | 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer; 8 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @SpringBootApplication 13 | @RestController 14 | @EnableResourceServer 15 | @EnableAuthorizationServer 16 | public class AuthserverApplication { 17 | 18 | @RequestMapping("/user") 19 | public Principal user(Principal user) { 20 | return user; 21 | } 22 | 23 | public static void main(String[] args) { 24 | SpringApplication.run(AuthserverApplication.class, args); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /authserver/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port: 9999 2 | server.contextPath: /uaa 3 | security.user.password: password 4 | security.sessions: if_required 5 | security.oauth2.client.clientId: acme 6 | security.oauth2.client.clientSecret: acmesecret 7 | security.oauth2.client.authorized-grant-types: authorization_code,refresh_token,password 8 | security.oauth2.client.scope: openid 9 | # logging.level.org.springframework.security: DEBUG -------------------------------------------------------------------------------- /authserver/src/main/resources/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshlong-attic/cloud-native-sso-workspace/114ac26f2509764570f0a6f0caa598dbe3d9bb69/authserver/src/main/resources/keystore.jks -------------------------------------------------------------------------------- /authserver/src/test/java/demo/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.boot.test.IntegrationTest; 10 | import org.springframework.boot.test.SpringApplicationConfiguration; 11 | import org.springframework.boot.test.TestRestTemplate; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | import org.springframework.test.context.web.WebAppConfiguration; 16 | import org.springframework.web.client.RestTemplate; 17 | 18 | @RunWith(SpringJUnit4ClassRunner.class) 19 | @SpringApplicationConfiguration(classes = AuthserverApplication.class) 20 | @WebAppConfiguration 21 | @IntegrationTest("server.port:0") 22 | public class ApplicationTests { 23 | 24 | @Value("${local.server.port}") 25 | private int port; 26 | 27 | private RestTemplate template = new TestRestTemplate(); 28 | 29 | @Test 30 | public void homePageProtected() { 31 | ResponseEntity response = template.getForEntity("http://localhost:" 32 | + port + "/uaa/", String.class); 33 | assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); 34 | String auth = response.getHeaders().getFirst("WWW-Authenticate"); 35 | assertTrue("Wrong header: " + auth, auth.startsWith("Bearer realm=\"")); 36 | } 37 | 38 | @Test 39 | public void userEndpointProtected() { 40 | ResponseEntity response = template.getForEntity("http://localhost:" 41 | + port + "/uaa/user", String.class); 42 | assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); 43 | String auth = response.getHeaders().getFirst("WWW-Authenticate"); 44 | assertTrue("Wrong header: " + auth, auth.startsWith("Bearer realm=\"")); 45 | } 46 | 47 | @Test 48 | public void authorizationRedirects() { 49 | ResponseEntity response = template.getForEntity("http://localhost:" 50 | + port + "/uaa/oauth/authorize", String.class); 51 | assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()); 52 | String auth = response.getHeaders().getFirst("WWW-Authenticate"); 53 | assertTrue("Wrong header: " + auth, auth.startsWith("Basic realm=\"")); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | redis: 2 | image: redis 3 | ports: 4 | - "6379:6379" 5 | -------------------------------------------------------------------------------- /eureka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | jar 6 | 7 | org.demo 8 | cloud-sso 9 | 0.0.1-SNAPSHOT 10 | 11 | eureka 12 | 13 | 14 | org.springframework.cloud 15 | spring-cloud-starter-eureka-server 16 | 17 | 18 | org.springframework.boot 19 | spring-boot-starter-test 20 | test 21 | 22 | 23 | 24 | UTF-8 25 | 1.7 26 | 27 | 28 | 29 | 30 | org.springframework.cloud 31 | spring-cloud-starter-parent 32 | Brixton.M3 33 | pom 34 | import 35 | 36 | 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-maven-plugin 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /eureka/src/main/java/demo/EurekaApplication.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 7 | 8 | @SpringBootApplication 9 | @EnableEurekaServer 10 | public class EurekaApplication { 11 | 12 | public static void main(String args[]) { 13 | SpringApplication.run(EurekaApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /eureka/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8761 2 | 3 | eureka.client.register-with-eureka=false 4 | eureka.client.fetch-registry=false 5 | #eureka.client.enabled=false -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | org.springframework.boot 6 | spring-boot-starter-parent 7 | 1.3.1.RELEASE 8 | 9 | 10 | org.demo 11 | cloud-sso 12 | 0.0.1-SNAPSHOT 13 | pom 14 | 15 | authserver 16 | ui 17 | resource 18 | eureka 19 | 20 | 21 | 22 | 23 | org.springframework.cloud 24 | spring-cloud-starter-parent 25 | Brixton.M3 26 | pom 27 | import 28 | 29 | 30 | 31 | 32 | UTF-8 33 | 1.8 34 | 35 | 36 | 37 | spring-snapshots 38 | Spring Snapshots 39 | http://repo.spring.io/libs-snapshot-local 40 | 41 | true 42 | 43 | 44 | 45 | spring-milestones 46 | Spring Milestones 47 | http://repo.spring.io/libs-milestone-local 48 | 49 | false 50 | 51 | 52 | 53 | spring-releases 54 | Spring Releases 55 | http://repo.spring.io/libs-release-local 56 | 57 | false 58 | 59 | 60 | 61 | 62 | 63 | spring-snapshots 64 | Spring Snapshots 65 | http://repo.spring.io/libs-snapshot-local 66 | 67 | true 68 | 69 | 70 | 71 | spring-milestones 72 | Spring Milestones 73 | http://repo.spring.io/libs-milestone-local 74 | 75 | false 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /resource/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | jar 7 | 8 | org.demo 9 | cloud-sso 10 | 0.0.1-SNAPSHOT 11 | 12 | resource 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-web 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-starter-security 21 | 22 | 23 | org.springframework.cloud 24 | spring-cloud-starter-eureka 25 | 26 | 27 | org.springframework.security.oauth 28 | spring-security-oauth2 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-test 33 | test 34 | 35 | 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-maven-plugin 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /resource/src/main/java/demo/ResourceApplication.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 6 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | import java.util.UUID; 13 | 14 | @SpringBootApplication 15 | @RestController 16 | @EnableResourceServer 17 | @EnableDiscoveryClient 18 | public class ResourceApplication { 19 | 20 | @RequestMapping("/") 21 | public Map home() { 22 | Map content = new HashMap<>(); 23 | content.put("id", UUID.randomUUID().toString()); 24 | content.put("content", "Hello, world!"); 25 | return content; 26 | } 27 | 28 | public static void main(String[] args) { 29 | SpringApplication.run(ResourceApplication.class, args); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /resource/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=9000 2 | security.oauth2.resource.userInfoUri=http://localhost:9999/uaa/user 3 | spring.application.name=resource -------------------------------------------------------------------------------- /resource/src/test/groovy/demo/ApplicationTests.groovy: -------------------------------------------------------------------------------- 1 | package demo 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | import org.springframework.test.context.web.WebAppConfiguration; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.boot.test.IntegrationTest; 11 | import org.springframework.boot.test.SpringApplicationConfiguration 12 | import org.springframework.boot.test.TestRestTemplate; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner 16 | import org.springframework.web.client.RestTemplate; 17 | 18 | @RunWith(SpringJUnit4ClassRunner) 19 | @SpringApplicationConfiguration(classes = ResourceApplication) 20 | @WebAppConfiguration 21 | @IntegrationTest('server.port:0') 22 | public class ApplicationTests { 23 | 24 | @Value('${local.server.port}') 25 | private int port; 26 | 27 | private RestTemplate template = new TestRestTemplate(); 28 | 29 | @Test 30 | public void resourceLoads() { 31 | ResponseEntity response = template.getForEntity("http://localhost:${port}/", String.class) 32 | assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode()) 33 | String auth = response.getHeaders().getFirst("WWW-Authenticate") 34 | assertTrue("Wrong location: " + auth , auth.startsWith("Bearer")) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ui/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | jar 7 | 8 | org.demo 9 | cloud-sso 10 | 0.0.1-SNAPSHOT 11 | 12 | ui 13 | 14 | 15 | org.springframework.cloud 16 | spring-cloud-starter-eureka 17 | 18 | 19 | org.springframework.cloud 20 | spring-cloud-starter-zuul 21 | 22 | 23 | org.springframework.cloud 24 | spring-cloud-starter-oauth2 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-security 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | org.springframework.security.oauth 36 | spring-security-oauth2 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-starter-test 41 | test 42 | 43 | 44 | org.apache.httpcomponents 45 | httpclient 46 | runtime 47 | 48 | 49 | 50 | 1.7.6 51 | 52 | 53 | 54 | 55 | ${project.basedir}/src/main/resources 56 | 57 | 58 | ${project.build.directory}/generated-resources 59 | 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-maven-plugin 65 | 66 | 67 | maven-resources-plugin 68 | 69 | 70 | 71 | copy-resources 72 | validate 73 | 74 | copy-resources 75 | 76 | 77 | ${basedir}/target/wro 78 | 79 | 80 | src/main/wro 81 | true 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | ro.isdc.wro4j 90 | wro4j-maven-plugin 91 | ${wro4j.version} 92 | 93 | 94 | generate-resources 95 | 96 | run 97 | 98 | 99 | 100 | 101 | ro.isdc.wro.maven.plugin.manager.factory.ConfigurableWroManagerFactory 102 | ${project.build.directory}/generated-resources/static/css 103 | ${project.build.directory}/generated-resources/static/js 104 | ${project.build.directory}/wro/wro.xml 105 | ${basedir}/src/main/wro/wro.properties 106 | ${basedir}/src/main/wro 107 | 108 | 109 | 110 | org.webjars 111 | jquery 112 | 2.1.1 113 | 114 | 115 | org.webjars 116 | angularjs 117 | 1.3.8 118 | 119 | 120 | org.webjars 121 | bootstrap 122 | 3.2.0 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /ui/src/main/java/demo/UiApplication.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.Filter; 6 | import javax.servlet.FilterChain; 7 | import javax.servlet.ServletException; 8 | import javax.servlet.http.Cookie; 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | import org.springframework.boot.SpringApplication; 13 | import org.springframework.boot.autoconfigure.SpringBootApplication; 14 | import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso; 15 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 16 | import org.springframework.cloud.netflix.zuul.EnableZuulProxy; 17 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 18 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 19 | import org.springframework.security.web.csrf.CsrfFilter; 20 | import org.springframework.security.web.csrf.CsrfToken; 21 | import org.springframework.security.web.csrf.CsrfTokenRepository; 22 | import org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository; 23 | import org.springframework.web.filter.OncePerRequestFilter; 24 | import org.springframework.web.util.WebUtils; 25 | 26 | @SpringBootApplication 27 | @EnableZuulProxy 28 | @EnableOAuth2Sso 29 | @EnableDiscoveryClient 30 | public class UiApplication extends WebSecurityConfigurerAdapter { 31 | 32 | public static void main(String[] args) { 33 | SpringApplication.run(UiApplication.class, args); 34 | } 35 | 36 | @Override 37 | public void configure(HttpSecurity http) throws Exception { 38 | http.antMatcher("/**").authorizeRequests() 39 | .antMatchers("/index.html", "/home.html", "/").permitAll().anyRequest() 40 | .authenticated().and().csrf().csrfTokenRepository(csrfTokenRepository()) 41 | .and().addFilterAfter(csrfHeaderFilter(), CsrfFilter.class); 42 | } 43 | 44 | private Filter csrfHeaderFilter() { 45 | return new OncePerRequestFilter() { 46 | @Override 47 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 48 | throws ServletException, IOException { 49 | 50 | CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); 51 | if (csrf != null) { 52 | Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN"); 53 | String token = csrf.getToken(); 54 | if (cookie == null || token != null && !token.equals(cookie.getValue())) { 55 | cookie = new Cookie("XSRF-TOKEN", token); 56 | cookie.setPath("/"); 57 | response.addCookie(cookie); 58 | } 59 | } 60 | filterChain.doFilter(request, response); 61 | } 62 | }; 63 | } 64 | 65 | private CsrfTokenRepository csrfTokenRepository() { 66 | HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository(); 67 | repository.setHeaderName("X-XSRF-TOKEN"); 68 | return repository; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /ui/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | debug: true 2 | 3 | 4 | 5 | spring: 6 | aop: 7 | proxy-target-class: true 8 | application: 9 | name: ui 10 | 11 | security: 12 | user: 13 | password: password 14 | oauth2: 15 | client: 16 | accessTokenUri: http://localhost:9999/uaa/oauth/token 17 | userAuthorizationUri: http://localhost:9999/uaa/oauth/authorize 18 | clientId: acme 19 | clientSecret: acmesecret 20 | resource: 21 | userInfoUri: http://localhost:9999/uaa/user 22 | 23 | zuul: 24 | routes: 25 | # resource: 26 | # path: /resource/** 27 | # url: http://localhost:9000 28 | user: 29 | path: /user/** 30 | url: http://localhost:9999/uaa/user 31 | 32 | logging: 33 | level: 34 | org.springframework.security: DEBUG 35 | org.springframework.web: DEBUG 36 | server: 37 | port: 8080 38 | -------------------------------------------------------------------------------- /ui/src/main/resources/static/home.html: -------------------------------------------------------------------------------- 1 |

Greeting

2 |
3 |

The ID is {{greeting.id}}

4 |

The content is {{greeting.content}}

5 |
6 |
7 |

Login to see your greeting

8 |
9 | -------------------------------------------------------------------------------- /ui/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello AngularJS 5 | 8 | 13 | 14 | 15 | 16 |
17 | 22 |
23 |
24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ui/src/main/resources/static/js/hello.js: -------------------------------------------------------------------------------- 1 | angular.module('hello', [ 'ngRoute' ]).config(function($routeProvider) { 2 | 3 | $routeProvider.when('/', { 4 | templateUrl : 'home.html', 5 | controller : 'home' 6 | }).otherwise('/'); 7 | 8 | }).controller('navigation', 9 | 10 | function($rootScope, $scope, $http, $location, $route) { 11 | 12 | $scope.tab = function(route) { 13 | return $route.current && route === $route.current.controller; 14 | }; 15 | 16 | $http.get('user').success(function(data) { 17 | if (data.name) { 18 | $rootScope.authenticated = true; 19 | } else { 20 | $rootScope.authenticated = false; 21 | } 22 | }).error(function() { 23 | $rootScope.authenticated = false; 24 | }); 25 | 26 | $scope.credentials = {}; 27 | 28 | $scope.logout = function() { 29 | $http.post('logout', {}).success(function() { 30 | $rootScope.authenticated = false; 31 | $location.path("/"); 32 | }).error(function(data) { 33 | console.log("Logout failed") 34 | $rootScope.authenticated = false; 35 | }); 36 | } 37 | 38 | }).controller('home', function($scope, $http) { 39 | $http.get('resource/').success(function(data) { 40 | $scope.greeting = data; 41 | }) 42 | }); 43 | -------------------------------------------------------------------------------- /ui/src/main/wro/main.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joshlong-attic/cloud-native-sso-workspace/114ac26f2509764570f0a6f0caa598dbe3d9bb69/ui/src/main/wro/main.less -------------------------------------------------------------------------------- /ui/src/main/wro/wro.properties: -------------------------------------------------------------------------------- 1 | #List of preProcessors 2 | preProcessors=lessCssImport 3 | #List of postProcessors 4 | postProcessors=less4j,jsMin -------------------------------------------------------------------------------- /ui/src/main/wro/wro.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | webjar:bootstrap/3.2.0/less/bootstrap.less 4 | file:@project.basedir@/src/main/wro/main.less 5 | webjar:jquery/2.1.1/jquery.js 6 | webjar:angularjs/1.3.8/angular.js 7 | webjar:angularjs/1.3.8/angular-route.js 8 | 9 | -------------------------------------------------------------------------------- /ui/src/test/java/demo/ApplicationTests.java: -------------------------------------------------------------------------------- 1 | package demo; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.boot.test.IntegrationTest; 10 | import org.springframework.boot.test.SpringApplicationConfiguration; 11 | import org.springframework.boot.test.TestRestTemplate; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | import org.springframework.test.context.web.WebAppConfiguration; 16 | import org.springframework.web.client.RestTemplate; 17 | 18 | @RunWith(SpringJUnit4ClassRunner.class) 19 | @SpringApplicationConfiguration(classes = UiApplication.class) 20 | @WebAppConfiguration 21 | @IntegrationTest("server.port:0") 22 | public class ApplicationTests { 23 | 24 | @Value("${local.server.port}") 25 | private int port; 26 | 27 | @Value("${security.oauth2.client.userAuthorizationUri}") 28 | private String authorizeUri; 29 | 30 | private RestTemplate template = new TestRestTemplate(); 31 | 32 | @Test 33 | public void homePageLoads() { 34 | ResponseEntity response = template.getForEntity("http://localhost:" 35 | + port + "/", String.class); 36 | assertEquals(HttpStatus.OK, response.getStatusCode()); 37 | } 38 | 39 | @Test 40 | public void userEndpointProtected() { 41 | ResponseEntity response = template.getForEntity("http://localhost:" 42 | + port + "/user", String.class); 43 | assertEquals(HttpStatus.FOUND, response.getStatusCode()); 44 | } 45 | 46 | @Test 47 | public void resourceEndpointProtected() { 48 | ResponseEntity response = template.getForEntity("http://localhost:" 49 | + port + "/resource", String.class); 50 | assertEquals(HttpStatus.FOUND, response.getStatusCode()); 51 | } 52 | 53 | @Test 54 | public void loginRedirects() { 55 | ResponseEntity response = template.getForEntity("http://localhost:" 56 | + port + "/login", String.class); 57 | assertEquals(HttpStatus.FOUND, response.getStatusCode()); 58 | String location = response.getHeaders().getFirst("Location"); 59 | assertTrue("Wrong location: " + location , location.startsWith(authorizeUri)); 60 | } 61 | 62 | } 63 | --------------------------------------------------------------------------------