打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
用Spring Boot & Cloud,Angular2快速搭建微服务web应用

接下来我们来看看如何增加权限控制,即提供用户认证和鉴权的功能。首先有3个比较重要的架构设计选择:

  1. 使用Spring的OAuth 2.0,还是使用Spring Session。虽然Spring对OAuth 2.0的支持已经很完善了,简化了大量的配置和开发,但是OAuth 2.0本身还是比较复杂的,尤其是要使用JWT(JSON Web Tokens)和CORS的情况下。OAuth 2.0的应用场景,也包含了许多我们当前并不需要的功能。从简单够用的理念出发,我决定先选择使用Spring Session。
  2. 用户登陆和Session管理的功能放在哪里?用户登陆很自然的放在gateway里面会比较好,作为整个网站的入口。不过里面有一个问题是用户的信息放在数据库里面。如果登陆放在gateway项目里面,会与user-service有一些重复的代码。考虑到登陆和user-service实际上是不同的功能,重复的代码也就是一个User类,还有一个findByUsername方法,还是决定将登陆放在gateway。
  3. portal是继续放在node.js在用npm单独运行,还是放在gateway的resource里面,作为gateway运行时的一部分?如果放在gateway里面,则前端的开发不能完全脱离后端,在开发中会丧失一部分的灵活性(不过前端也不可能完全脱离后端)。如果在node.js里面运行,则后端需要考虑CORS的问题。浏览器在跨域访问的时候,还会在实际的HTTP请求之前,先插入一个preflight的请求,请求方法是OPTIONS。为了支持CORS,后端需要做很多配置,包括安全配置(允许OPTIONS请求),CORS过滤器(或者配置器)等。CORS的配置需要允许跨域访问,也带来一下安全隐患。并且在正式部署中,如果用Angular2 Cli工具打包portal,则这些配置可能都没有用处了。本着不过度设计的原则,我决定先选择将前端放在gateway里面。

那么下面的第一步就是把portal目录移到到gateway/main/java/resource/static目录下面。我尝试了在Windows下面使用Symlink,但是Eclipse当前版本(Neon)不支持Windows的Symlink,即不能正常解析里面的文件。如果用Eclipse自带的Linked Folder,Maven又不能正常拷贝里面的文件到target,所以只好老老实实的将portal的内容移动到static,并且删除portal。

Spring Boot提供的用户认证和鉴权功能,牵涉到了Spring Session,Spring Security的功能。

Spring Session

Spring Session提供了用户session管理的实现和接口。基本上Spring默认的实现可以满足大部分应用的需求。实现还提供了和HttpSession的集成,并且默认使用的是流行的Session管理内存No SQL数据库系统Redis。对于使用RESTful API的微服务,Spring Session通过在HTTP Header中添加session ID的方式来支持。在使用Spring Boot时,所有这些都基本上不需要开发人员编写代码,只需要在pom中增加依赖spring-boot-starter-redis和spring-session。具体请参考:http://projects.spring.io/spring-session/,以及http://docs.spring.io/spring-session/docs/current/reference/html5/

Spring Security

Spring Security实现了基于角色的访问控制,并且支持CORS,CSRF等。具体请参考:http://projects.spring.io/spring-security/,以及http://docs.spring.io/spring-security/site/docs/current/guides/html5/。不过即使是基于spring-boot-starter-security,也需要开发人员根据项目情况进行配置,编写配置代码。如果用户的信息在数据库中,则需要实现UserDetailsService的loadUserByUsername方法,供Spring Security调用,以取得登陆用户的密码,状态信息和角色信息。另外还要继承WebSecurityConfigurerAdapter类,并且至少重写参数为HttpSecurity的configure方法。

这里值得一提的是,Spring Security从版本3开始,支持BCrypt算法。该算法除了比MD5和SHA1强度更高,更不容易被暴力破解以外,另一个特点就是产生的最终密码包含了salt(盐)。为了防止拖库之后的彩虹表攻击,如果采用SHA1,一般还需要在数据库的用户表中增加一个salt字段,存放盐值。这样就比较麻烦,另外如果盐值不是随机的,或者生成的算法不好,也比较容易受到攻击。BCrypt解决了这个问题,该算法产生的最终密码包含了一个随机的盐值,验证的时候无需再提供单独的盐值。具体请参考:https://en.wikipedia.org/wiki/Bcrypt。要使用该算法,需要重写参数为AuthenticationManagerBuilder的configure方法。

gateway实现

UserDetailServiceImpl.java

  1. package com.healtrav.session;  
  2.   
  3. import org.springframework.beans.factory.annotation.Autowired;  
  4. import org.springframework.security.core.authority.AuthorityUtils;  
  5. import org.springframework.security.core.userdetails.User;  
  6. import org.springframework.security.core.userdetails.UserDetails;  
  7. import org.springframework.security.core.userdetails.UserDetailsService;  
  8. import org.springframework.security.core.userdetails.UsernameNotFoundException;  
  9. import org.springframework.stereotype.Service;  
  10.   
  11. @Service  
  12. public class UserDetailServiceImpl implements UserDetailsService {  
  13.   
  14.     private final UserRepository userRepo;  
  15.   
  16.     @Autowired  
  17.     public UserDetailServiceImpl(UserRepository userRepo) {  
  18.         this.userRepo = userRepo;  
  19.     }  
  20.   
  21.     @Override  
  22.     public UserDetails loadUserByUsername(String username)  
  23.             throws UsernameNotFoundException {  
  24.         return this.userRepo.findByUsername(username)  
  25.                 .map(user -> new User(  
  26.                         user.getUsername(),  
  27.                         user.getPassword(),  
  28.                         !user.getState().equals("expired"),  
  29.                         !user.getState().equals("locked"),  
  30.                         !user.getState().equals("credentialExpired"),  
  31.                         !user.getState().equals("disabled"),  
  32.                         AuthorityUtils.createAuthorityList(user.getRoles())))  
  33.                 .orElseThrow(() -> new UsernameNotFoundException(username));  
  34.     }  
  35.   
  36. }  

使用了Spring JPA的UserRepository,从MySQL数据库的user表,根据用户名读取用户。

UserRepository.java

  1. package com.healtrav.session;  
  2.   
  3. import java.util.Optional;  
  4.   
  5. import org.springframework.data.repository.Repository;  
  6. import org.springframework.data.repository.query.Param;  
  7.   
  8. public interface UserRepository extends Repository<User, Long> {  
  9.   
  10.     Optional<User> findByUsername(@Param("username") String username);  
  11. }  
与user-service里面的UserRepository不同,gateway里面实现是继承了Repository这个基本实现类,原因是不需要那么多方法。

WebSecurityConfigurer

  1. package com.healtrav.session;  
  2.   
  3. import org.springframework.beans.factory.annotation.Autowired;  
  4. import org.springframework.context.annotation.Bean;  
  5. import org.springframework.context.annotation.Configuration;  
  6. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;  
  7. import org.springframework.security.config.annotation.web.builders.HttpSecurity;  
  8. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;  
  9. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;  
  10. import org.springframework.security.crypto.password.PasswordEncoder;  
  11. import org.springframework.security.web.csrf.CookieCsrfTokenRepository;  
  12.   
  13. @Configuration  
  14. public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {  
  15.   
  16.     @Autowired  
  17.     UserDetailServiceImpl userService;  
  18.   
  19.     @Bean  
  20.     PasswordEncoder passwordEncoder() {  
  21.         return new BCryptPasswordEncoder();  
  22.     }  
  23.   
  24.     @Override  
  25.     protected void configure(AuthenticationManagerBuilder auth)  
  26.             throws Exception {  
  27.         auth.userDetailsService(userService)  
  28.                 .passwordEncoder(passwordEncoder());  
  29.     }  
  30.   
  31.     @Override  
  32.     protected void configure(HttpSecurity http) throws Exception {  
  33.         // @formatter:off  
  34.         http  
  35.             .httpBasic()  
  36.             .and()  
  37.             .logout()  
  38.             .and()  
  39.             .authorizeRequests()  
  40.                 .antMatchers(  
  41.                         "/*",  
  42.                         "/login",  
  43.                         "/app/**",  
  44.                         "/node_modules/**")  
  45.                     .permitAll()  
  46.                 .anyRequest()  
  47.                     .authenticated()  
  48.             .and().csrf().csrfTokenRepository(  
  49.                     CookieCsrfTokenRepository.withHttpOnlyFalse());  
  50.         // @formatter:on  
  51.     }  
  52.   
  53. }  
该类设置了UserDetaiService和BCrypt,以及一些基本的权限。对于用Angula2来说,在根目录,app和node_modules下面,都有一下静态的文件,包括html,css和js文件,所以需要开放他们的权限。另外对于Angular2来说,需要设置CSRF token存储,否则浏览器没有办法取得正确的CSRF token,Spring Security会认为发生了CSRF攻击。另外我们使用了HTTP Basic的密码验证方法。这个听起来好像不是很安全,其实并没有降低安全的级别,只是用起来更加方便,即Angular2的登陆页面可以不用post,只需要用get,并且在HTTP Header里面加入登陆的用户名和密码。当然正式的产品需要使用HTTP S协议来保证安全。实际的安全性跟用表单post的形式没有区别。

LoginController.java

  1. package com.healtrav.session;  
  2.   
  3. import org.springframework.stereotype.Controller;  
  4. import org.springframework.web.bind.annotation.CrossOrigin;  
  5. import org.springframework.web.bind.annotation.RequestMapping;  
  6.   
  7. @Controller  
  8. public class LoginController {  
  9.   
  10.     @RequestMapping("/login")  
  11.     @CrossOrigin(origins = "*", maxAge = 3600)  
  12.     public String login() {  
  13.         return "forward:/";  
  14.     }  
  15.   
  16. }  
login成功会转到static目录下面。其实@CrossOrigin注解是不需要的,因为我们采用了将portal代码移入gateway的方法。

PrincipalController.java

  1. package com.healtrav.session;  
  2.   
  3. import java.security.Principal;  
  4.   
  5. import org.springframework.web.bind.annotation.RequestMapping;  
  6. import org.springframework.web.bind.annotation.RestController;  
  7.   
  8. @RestController  
  9. public class PrincipalController {  
  10.   
  11.     @RequestMapping("/user")  
  12.     Principal principal(Principal principal) {  
  13.         return principal;  
  14.     }  
  15.   
  16. }  
这是Spring的一个小技巧,用来获取当前用户信息,如果获取到,则说明已经登陆。

SessionPreFilter

  1. package com.healtrav.session;  
  2.   
  3. import javax.servlet.http.HttpSession;  
  4.   
  5. import org.springframework.beans.factory.annotation.Autowired;  
  6. import org.springframework.session.Session;  
  7. import org.springframework.session.SessionRepository;  
  8.   
  9. import com.netflix.zuul.ZuulFilter;  
  10. import com.netflix.zuul.context.RequestContext;  
  11.   
  12. public class SessionPreFilter extends ZuulFilter {  
  13.   
  14.     @Autowired  
  15.     private SessionRepository<?> repository;  
  16.   
  17.     @Override  
  18.     public String filterType() {  
  19.         return "pre";  
  20.     }  
  21.   
  22.     @Override  
  23.     public int filterOrder() {  
  24.         return 1;  
  25.     }  
  26.   
  27.     @Override  
  28.     public boolean shouldFilter() {  
  29.         return true;  
  30.     }  
  31.   
  32.     @Override  
  33.     public Object run() {  
  34.         RequestContext ctx = RequestContext.getCurrentContext();  
  35.         HttpSession httpSession = ctx.getRequest().getSession();  
  36.         Session session = repository.getSession(httpSession.getId());  
  37.         ctx.addZuulRequestHeader("Cookie", "SESSION=" + session.getId());  
  38.   
  39.         return null;  
  40.     }  
  41.   
  42. }  
通过Zuul Pre过滤器,将session的信息传递给微服务。这个比较关键,否则user-service拿不到用户的session,会认为是匿名用户而拒绝访问。另外在GatewayApplication上也需要加上注解@EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE),告诉Redis立即保存session。

application.properties

  1. zuul.routes.user-service.url=http://localhost:8081  
  2. ribbon.eureka.enabled=false  
  3. server.port=8080  
  4.   
  5. logging.level.org.springframework.security=DEBUG  
  6. security.sessions=ALWAYS  
  7.   
  8. # MySQL data source settings to user authentication  
  9. spring.datasource.url=jdbc:mysql://localhost:3306/healtrav  
  10. spring.datasource.username=root  
  11. spring.datasource.password=  
  12.   
  13. spring.datasource.initial-size=20  
  14. spring.datasource.max-idle=60  
  15. spring.datasource.max-wait=10000  
  16. spring.datasource.min-idle=10  
  17. spring.datasource.max-active=200  

gateway增加了secuiry.session=ALWAYS的配置表示总是创建session。

user-service实现

WebSecurityConfigurer.java

  1. package com.healtrav;  
  2.   
  3. import org.springframework.context.annotation.Configuration;  
  4. import org.springframework.http.HttpMethod;  
  5. import org.springframework.security.config.annotation.web.builders.HttpSecurity;  
  6. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;  
  7. import org.springframework.security.web.csrf.CookieCsrfTokenRepository;  
  8.   
  9. @Configuration  
  10. public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {  
  11.   
  12.     @Override  
  13.     protected void configure(HttpSecurity http) throws Exception {  
  14.         // @formatter:off  
  15.         http.httpBasic().disable()  
  16.                 .authorizeRequests()  
  17.                     .antMatchers(HttpMethod.POST, "/**").hasRole("ADMIN")  
  18.                     .anyRequest()  
  19.                     .authenticated()  
  20.                 .and()  
  21.                     .csrf().csrfTokenRepository(  
  22.                             CookieCsrfTokenRepository.withHttpOnlyFalse());  
  23.         // @formatter:on  
  24.     }  
  25.   
  26. }  

配置user-service所以的访问请求都需要ADMIN这个role。

application.properties

  1. # MySQL data source settings  
  2. spring.datasource.url=jdbc:mysql://localhost:3306/healtrav  
  3. spring.datasource.username=root  
  4. spring.datasource.password=  
  5.   
  6. spring.datasource.initial-size=20  
  7. spring.datasource.max-idle=60  
  8. spring.datasource.max-wait=10000  
  9. spring.datasource.min-idle=10  
  10. spring.datasource.max-active=200  
  11.   
  12. # auto create tables and data for database healtrav  
  13. spring.jpa.generate-ddl=true  
  14. spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect  
  15. spring.datasource.schema=..\..\..\db\schema.sql  
  16. spring.datasource.data=..\..\..\db\data.sql  
  17.   
  18. # show each sql for debug  
  19. spring.jpa.show-sql = true  
  20.   
  21. spring.application.name=user-service  
  22. server.port=8081  
  23. server.address: 127.0.0.1  
  24.   
  25. security.sessions: NEVER  
  26. logging.level.org.springframework.security: debug  
设置security.sessions=NEVER,即user-service永远也不会创建session,总是从redis那里根据session ID读取session。

Angular2实现

portal的页面也有了一些变化,增加了一个登陆信息的导航栏,登陆之后会显示用户名字(从gateway的PrincipalController获取登陆用户信息)。并且登陆之后,可以从User Management链接获取系统的用户列表。

user.service.ts

[javascript] view plain copy
print?
  1. import { Injectable } from '@angular/core';  
  2. import { Headers, Http, RequestOptions, Response } from '@angular/http';  
  3.   
  4. import { User } from './user';  
  5. import 'rxjs/add/operator/toPromise';  
  6.   
  7. @Injectable()  
  8. export class UserService {  
  9.   
  10.     constructor (  
  11.         private http: Http  
  12.     ) { }  
  13.   
  14.     private login_url = 'http://localhost:8080/login'  
  15.     private principal_url = 'http://localhost:8080/user'  
  16.     private users_url = 'http://localhost:8080/user-service/users'  
  17.   
  18.     principal: User = null;  
  19.   
  20.     getAllUsers(): Promise<User[]> {  
  21.         return this.http.get(this.users_url)  
  22.             .toPromise()  
  23.             .then(function(response: Response) {  
  24.                 return response.json()._embedded['users'];;  
  25.             })  
  26.             .catch(this.handleError);  
  27.     }  
  28.   
  29.     getPrincipal(): Promise<User> {  
  30.         return this.http.get(this.principal_url)  
  31.             .toPromise()  
  32.             .then(function(response: Response) {  
  33.                 return response.json().principal;  
  34.             })  
  35.             .catch(this.handleError);  
  36.     }  
  37.   
  38.     login(username: string, password: string): Promise<string> {  
  39.         //let headers = new Headers({ authorization: 'Basic ' + btoa(user.username + ':' + user.password) });  
  40.         let auth = 'Basic ' + btoa(username + ':' + password);  
  41.         let headers = new Headers();  
  42.         headers.append('Authorization', auth);  
  43.         return this.http.get(this.login_url, { headers: headers })  
  44.             .toPromise()  
  45.             .then(function(response: Response) {  
  46.                 return 'success';  
  47.             })  
  48.             .catch(this.handleError);  
  49.     }  
  50.   
  51.     private handleError (error: any) {  
  52.         let msg = (error.message) ? error.message :  
  53.             error.status ? `${error.status} - ${error.statusText}` : 'unknown error';  
  54.         console.error(msg); // log to console instead  
  55.         this.principal = null;  
  56.         return Promise.reject(msg);  
  57.     }  
  58. }  
注意登陆是通过增加了一个HTTP Authorization Header。并且通过principal: User是否为空,来判断是否登陆成功。

app.component.ts

[javascript] view plain copy
print?
  1. import { Component } from '@angular/core';  
  2. import { Router } from '@angular/router';  
  3.   
  4. import { User } from './shared/user';  
  5. import { UserService } from './shared/user.service';  
  6.   
  7. @Component({  
  8.     selector: 'healtrav-app',  
  9.     templateUrl: 'app/app.component.html'  
  10. })  
  11. export class AppComponent {  
  12.   
  13.     username: string = null;  
  14.     password: string = null;  
  15.   
  16.     constructor(  
  17.         private router: Router,  
  18.         private userService: UserService,  
  19.     ) { }  
  20.   
  21.     login() {  
  22.         this.userService.login(this.username, this.password).then(  
  23.             result => {  
  24.                 console.log(result);  
  25.                 this.userService.getPrincipal().then(  
  26.                     principal => {  
  27.                         this.userService.principal = principal;  
  28.                         console.log(this.userService.principal);  
  29.                     },  
  30.                     error => console.error(error)  
  31.                 )  
  32.                 this.router.navigate(['']);  
  33.             },  
  34.             error => console.error(error)  
  35.         );  
  36.     }  
  37. }  
登陆后立刻调用userService的getPrincipal方法获取登陆用户信息,来判断是否登陆成功。

app.module.ts

[javascript] view plain copy
print?
  1. import { NgModule, Injectable } from '@angular/core';  
  2. import { BrowserModule } from '@angular/platform-browser';  
  3. import { HttpModule, BrowserXhr } from '@angular/http';  
  4. import { FormsModule } from '@angular/forms';  
  5.   
  6. import { AppComponent } from './app.component';  
  7. import { routing, appRoutingProviders } from './app.routing';  
  8.   
  9. import { UserService } from './shared/user.service';  
  10. import { HomeModule } from './home/home.module';  
  11. import { UserManagementModule } from './user-management/user-management.module';  
  12.   
  13. @Injectable()  
  14. export class CorsBrowserXhr extends BrowserXhr {  
  15.     constructor() {  
  16.         super();  
  17.     }  
  18.   
  19.     build(): any {  
  20.         let xhr:XMLHttpRequest = super.build();  
  21.         xhr.withCredentials = true;  
  22.         return <any>(xhr);  
  23.     }  
  24. }  
  25.   
  26. @NgModule({  
  27.     imports: [  
  28.         BrowserModule,  
  29.         HttpModule,  
  30.         FormsModule,  
  31.         routing,  
  32.         HomeModule,  
  33.         UserManagementModule  
  34.     ],  
  35.     declarations: [  
  36.         AppComponent  
  37.     ],  
  38.     providers: [  
  39.         { provide: BrowserXhr, useClass:CorsBrowserXhr },  
  40.         appRoutingProviders,  
  41.         UserService  
  42.     ],  
  43.     bootstrap: [ AppComponent ]  
  44. })  
  45. export class AppModule { }  

CorsBrowserXhr类通过覆盖默认的浏览器Xhr请求,全局的设置了withCredentials为true,即告诉Angular2,每个XHR请求都带上cookie信息,放到请求的HTTP Header。这个比较关键。因为登陆时的CSRF和Session信息,都是通过gateway的HTTP响应的Set-Cookie头,存入浏览器的。如果没有这个配置,浏览器不会将这两个cookie,放置到XHR请求头。不过这个类的名字起的不恰当,应该叫CredentialBrowserXhr之类的。

验证

在使用浏览器验证之前,我们可以先用curl工具看看服务器是否配置成功。前半部分命令和响应如下:
  1. $ curl -v -i http://localhost:8080/login -u cuiwader:1  
  2. * timeout on name lookup is not supported  
  3. *   Trying ::1...  
  4.   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current  
  5.                                  Dload  Upload   Total   Spent    Left  Speed  
  6.   0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Connected to localhost (::1) port 8080 (#0)  
  7. * Server auth using Basic with user 'cuiwader'  
  8. > GET /login HTTP/1.1  
  9. > Host: localhost:8080  
  10. > Authorization: Basic Y3Vpd2FkZXI6MQ==  
  11. > User-Agent: curl/7.48.0  
  12. > Accept: */*  
  13. >  
  14. < HTTP/1.1 200  
  15. < Set-Cookie: XSRF-TOKEN=54797b38-7fac-4942-8057-8989617f140b;path=/  
  16. < X-Application-Context: application:8080  
  17. < Last-Modified: Sun, 16 Oct 2016 15:24:12 GMT  
  18. < Accept-Ranges: bytes  
  19. < X-Content-Type-Options: nosniff  
  20. < X-XSS-Protection: 1; mode=block  
  21. < Cache-Control: no-cache, no-store, max-age=0, must-revalidate  
  22. < Pragma: no-cache  
  23. < Expires: 0  
  24. < X-Frame-Options: DENY  
  25. < Set-Cookie: SESSION=3353e5c5-ccc8-46b1-944b-4031a268c8a8;path=/;HttpOnly  

可以看到curl发送的请求包含了一个Authorization头,内容是Base64编码的用户名:密码。服务器返回了Set-Cookie响应头,设置XSRF-TOKEN和SESSION。Session的范围是/,并且是HttpOnly。
之后可以用curl验证是否登陆成功:
  1. $ curl -v -i http://localhost:8080/user -H "Cookie: XSRF-TOKEN=54797b38-7fac-4942-8057-8989617f140b; SESSION=3353e5c5-ccc8-46b1-944b-4031a268c8a8"  
  2. *   Trying ::1...  
  3.   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current  
  4.                                  Dload  Upload   Total   Spent    Left  Speed  
  5.   0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*                                                                            
  6. > GET /user HTTP/1.1  
  7. > Host: localhost:8080  
  8. > User-Agent: curl/7.48.0  
  9. > Accept: */*  
  10. > Cookie: XSRF-TOKEN=54797b38-7fac-4942-8057-8989617f140b; SESSION=3353e5c5-ccc8                                                                          
  11. >  
  12. < HTTP/1.1 200  
  13. < X-Application-Context: application:8080  
  14. < X-Content-Type-Options: nosniff  
  15. < X-XSS-Protection: 1; mode=block  
  16. < Cache-Control: no-cache, no-store, max-age=0, must-revalidate  
  17. < Pragma: no-cache  
  18. < Expires: 0  
  19. < X-Frame-Options: DENY  
  20. < Content-Type: application/json;charset=UTF-8  
  21. < Transfer-Encoding: chunked  
  22. < Date: Sun, 16 Oct 2016 17:04:55 GMT  
  23. <  
  24. { [362 bytes data]  
  25. 100   355    0   355    0     0  11451      0 --:--:-- --:--:-- --:--:-- 11451HT                                                                           
  26. X-Application-Context: application:8080  
  27. X-Content-Type-Options: nosniff  
  28. X-XSS-Protection: 1; mode=block  
  29. Cache-Control: no-cache, no-store, max-age=0, must-revalidate  
  30. Pragma: no-cache  
  31. Expires: 0  
  32. X-Frame-Options: DENY  
  33. Content-Type: application/json;charset=UTF-8  
  34. Transfer-Encoding: chunked  
  35. Date: Sun, 16 Oct 2016 17:04:55 GMT  
  36.   
  37. {"details":{"remoteAddress":"0:0:0:0:0:0:0:1","sessionId":null},"authorities":[{"authority":"ADMIN, USER"}],"authenticated":true,"principal":{"password":  
  1. null,"username":"cuiwader","authorities":[{"authority":"ADMIN, USER"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true,  
  1. "enabled":true},"credentials":null,"name":"cuiwader"}                                                                     
  1. * Connection #0 to host localhost left intact  

看到这些内容说明服务器正常,可以用浏览器打开页面了。


上一章:用Spring Boot & Cloud,Angular2快速搭建微服务web应用 - 增加代理服务器


本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Remoting and web services using Spring
spring security 3.1配置过程从简单到复杂详细配置
Spring Boot gradle
spring security3 自定义过滤链
Spring Security(2):过滤器链(filter chain)的介绍
使用 Spring Security 构建一个 HTTP 基本认证示例
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服