【问题标题】:Minimal setup for a secured microservice environment安全微服务环境的最小设置
【发布时间】:2018-12-14 09:14:04
【问题描述】:

我想使用 Gradle、Spring Boot2、Zuul、JWT、带有 REST-Api 和自制身份验证服务器的微服务设置 SSO 微服务环境。 当身份验证服务器、网关和微服务充当 OAuth 客户端和资源服务器时,它们的注释是什么?什么是最小设置?

【问题讨论】:

    标签: spring-boot jwt single-sign-on api-gateway


    【解决方案1】:

    对于 gradle 配置,我建议使用网站 http://start.spring.io/ 和授权服务器。配置将是这样的:

    buildscript {
        ext {
            springBootVersion = '2.0.3.RELEASE'
        }
        repositories {
            mavenCentral()
        }
        dependencies {
            classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
        }
    }
    
    apply plugin: 'java'
    apply plugin: 'eclipse'
    apply plugin: 'org.springframework.boot'
    apply plugin: 'io.spring.dependency-management'
    
    group = 'com.example'
    version = '0.0.1-SNAPSHOT'
    sourceCompatibility = 1.8
    
    repositories {
        mavenCentral()
    }
    
    
    ext {
        springCloudVersion = 'Finchley.RELEASE'
    }
    
    dependencies {
        compile('org.springframework.boot:spring-boot-starter-web')
        compile('org.springframework.cloud:spring-cloud-starter-oauth2')
        compile('org.springframework.cloud:spring-cloud-starter-security')
        testCompile('org.springframework.boot:spring-boot-starter-test')
    }
    
    dependencyManagement {
        imports {
            mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
        }
    }
    

    那么认证服务器将是这样的:

    @Configuration
    @EnableAuthorizationServer
    public class SecurityOAuth2AutorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        private final AccountUserDetailsService accountUserDetailsService;
        private final UserDetailsService authenticationManager;
        private final PasswordEncoder passwordEncoder;
    
        public SecurityOAuth2AutorizationServerConfig(UserDetailsService accountUserDetailsService,
                                                      AuthenticationManager authenticationManager,
                                                      PasswordEncoder passwordEncoder) {
            this.accountUserDetailsService = accountUserDetailsService;
            this.authenticationManager = authenticationManager;
            this.passwordEncoder = passwordEncoder;
        }
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints.approvalStoreDisabled()
                    .authenticationManager(authenticationManager)
                    .tokenStore(tokenStore())
                    .accessTokenConverter(accessTokenConverter())
                    .userDetailsService(accountUserDetailsService)
                    .reuseRefreshTokens(false);
        }
    
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
            oauthServer.tokenKeyAccess("permitAll()")
                    .passwordEncoder(passwordEncoder)
                    .checkTokenAccess("isAuthenticated()")
                    .allowFormAuthenticationForClients();
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    .withClient("client")
                    .secret(passwordEncoder.encode("secret"))
                    .authorizedGrantTypes("authorization_code", "refresh_token", "password").scopes("openid")
                    .authorities("ROLE_USER", "ROLE_EMPLOYEE")
                    .scopes("read", "write", "trust", "openid")
                    .resourceIds("oauth2-resource")
                    .autoApprove(true)
                    .accessTokenValiditySeconds(5)
                    .refreshTokenValiditySeconds(60*60*8);
        }
    
        @Bean
        public TokenStore tokenStore() {
            return new JwtTokenStore(accessTokenConverter());
        }
    
        @Bean
        public JwtAccessTokenConverter accessTokenConverter() {
            ....
        }
    }
    

    sso 的登录页面配置如下:

    @Configuration
    @Order(SecurityProperties.DEFAULT_FILTER_ORDER)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .formLogin()
                    .permitAll()
                    .and()
                    .authorizeRequests().anyRequest().authenticated();
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        public UserDetailsService accountUserDetailsService() {
            InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
            inMemoryUserDetailsManager.createUser(new User("user", "secret",
                    Collections.singleton(new SimpleGrantedAuthority("USER"))));
    
            return inMemoryUserDetailsManager;
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    

    在您的 sso 应用程序中,您可以进行如下配置:

     @EnableOAuth2Sso
    @EnableZuulProxy
    @SpringBootApplication
    public class SsoDemoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SsoDemoApplication.class, args);
        }
    
    
        @Bean
        @Primary
        public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ClientContext context,
                                                     OAuth2ProtectedResourceDetails authorizationCodeResourceDetails) {
            return new OAuth2RestTemplate(authorizationCodeResourceDetails, context);
        }
    
    }
    

    在您的 application.yml 中:

     security:
          oauth2:
            client:
             clientId: client
             clientSecret: secret
             accessTokenUri: http://localhost:9090/auth/oauth/token
             userAuthorizationUri: http://localhost:9090/auth/oauth/authorize
             auto-approve-scopes: '.*'
             registered-redirect-uri: http://localhost:9090/auth/singin
             clientAuthenticationScheme: form
            resource:
              jwt:
                key-value: -----BEGIN PUBLIC KEY-----
          ......
                           -----END PUBLIC KEY-----
    
    server:
      use-forward-headers: true
    
    zuul:
      sensitiveHeaders:
      ignoredServices: '*'
      ignoreSecurityHeaders: false
      addHostHeader: true
      routes:
        your-service: /your-service/**
    
    proxy:
      auth:
        routes:
          spent-budget-service: oauth2
    

    通过这种方式,您可以使用身份验证服务器在 sso 中配置客户端应用程序,@EnableOAuth2Sso 将为您做任何事情,它也像客户端应用程序一样,如果您的应用程序未经过身份验证,您的 sso 将在登录页面上重定向您您的身份验证服务器并将为您刷新您的令牌 zuul 令牌中继它也可用作此用例中的功能,我使用 eureka 作为发现服务注册表。配置 OAuth2RestTemplate 非常重要,因为 spring 将使用此 bean 为您自动刷新令牌,否则一旦令牌过期,您将无法自动刷新令牌。

    你所有的资源服务器都是这样的:

    @EnableResourceServer
    @SpringBootApplication
    public class AccountServiceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(AccountServiceApplication.class, args);
        }
    }
    

    在您的 application.yml 中:

     security:
      oauth2:
        resource:
          jwt:
            key-value: -----BEGIN PUBLIC KEY-----
       .....
                       -----END PUBLIC KEY----
    

    -

    当然,这是非常简单的配置,但 id 足以启动

    更新:

    不要忘记网关、sso 和任何其他资源服务器上应用程序 yml 中的资源配置,否则将无法针对您的身份验证服务器验证令牌。

    如果是普通的 oauth2 令牌,您可以使用

    security.oauth2.resource.token-info-uri: your/auth/server:yourport/oauth/check_token 
    

    security.oauth2.resource.user-info-uri: yourAccountDEtailsRndpoint/userInfo.json
    security.oauth2.resource.preferTokenInfo: false
    

    在 preferTokenInfo: false 的情况下,您在身份验证服务器上的典型帐户数据端点。

    @RestController
    @RequestMapping("/account")
    class UserRestFullEndPoint {
    
        @GetMapping("/userInfo")
        public Principal userInfo(Principal principal){
            return principal;
        }
    }
    

    如果使用 token-info-uri 配置,UserInfoRestTemplateFactory 将在春季自动提供,唯一要记住的是配置 Oauth2RestTemplate,否则您的令牌将不再刷新

    更新 2

    如果是非 JWT 令牌,则需要添加缺少的配置 您的身份验证服务器上的 @EnableResourceServer。

    通过这种方式,您的用户信息 uri 将返回主体对象,例如 json。听到的问题是,您的端点在任何情况下都将返回 null,因此您的服务收到 401。发生这种情况是因为您的身份验证服务器只能返回令牌,而不能公开其他不是框架端点的服务以提供令牌。由于您需要返回用户信息,因此您需要一种公开资源的方法,因此您需要像资源服务器一样公开身份验证服务器。在 jwt 的情况下它是无用的,因为令牌验证和用户信息将由令牌本身提供。用户信息由 jwt 提供,由 jwt key 验证。

    恢复您的身份验证服务器将如下所示:

    @SpringBootApplication
    public class AuthserverApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(AuthserverApplication.class, args);
        }
    }
    
    @RestController
    class UserInfo {
    
        @GetMapping("/account/user-info")
        public Principal principal(Principal principal){
            System.out.println(principal);
            return principal;
        }
    
    }
    
    @Controller
    class Login{
    
        @GetMapping(value = "/login", produces = "application/json")
        public String login(){
            return "login";
        }
    }
    
    @Configuration
    @EnableAuthorizationServer
    @EnableResourceServer
    class SecurityOAuth2AutorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints.authenticationManager(authenticationManager)
                    .approvalStoreDisabled()
                    .reuseRefreshTokens(false)
                    .userDetailsService(accountUserDetailsService());
        }
    
        @Bean
        public UserDetailsService accountUserDetailsService() {
            InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
            inMemoryUserDetailsManager.createUser(new User("user", passwordEncoder.encode("secret"),
                    Collections.singleton(new SimpleGrantedAuthority("USER"))));
    
            return inMemoryUserDetailsManager;
        }
    
        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
            oauthServer.tokenKeyAccess("permitAll()")
                    .passwordEncoder(passwordEncoder)
                    .checkTokenAccess("isAuthenticated()")
                    .allowFormAuthenticationForClients();
        }
    
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    .withClient("client")
                    .secret(passwordEncoder.encode("secret"))
                    .authorizedGrantTypes("client_credentials", "password", "authorization_code", "refresh_token", "implicit")
                    .authorities("ROLE_USER", "ROLE_EMPLOYEE")
                    .scopes("read", "write", "trust", "openid")
                    .autoApprove(true)
                    .refreshTokenValiditySeconds(20000000)
                    .accessTokenValiditySeconds(20000000);
        }
    
    }
    
    @Configuration
    @Order(SecurityProperties.DEFAULT_FILTER_ORDER)
    class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable().httpBasic().disable()
                    .formLogin().loginPage("/login").loginProcessingUrl("/login")
                    .permitAll()
                    .and()
                    .requestMatchers().antMatchers("/account/userInfo", "/login", "/oauth/authorize", "/oauth/confirm_access")
                    .and()
                    .authorizeRequests().anyRequest().authenticated();
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    

    登录页面:

    <html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
    
        <link rel="stylesheet" th:href="@{/webjars/bootstrap/3.3.7-1/css/bootstrap.css}"/>
        <link rel="stylesheet" th:href="@{/webjars/bootstrap/3.3.7-1/css/bootstrap-theme.css}"/>
    
        <title>Log In</title>
    </head>
    <body>
    <div class="container">
        <form role="form" action="login" method="post">
            <div class="row">
                <div class="form-group">
                    <div class="col-md-6 col-lg-6 col-md-offset-2 col-lg-offset-">
                        <label for="username">Username:</label>
                        <input type="text" class="form-control" id="username" name="username"/>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="form-group">
                    <div class="col-md-6 col-lg-6 col-md-offset-2 col-lg-offset-2">
                        <label for="password">Password:</label>
                        <input type="password" class="form-control" id="password" name="password"/>
                    </div>
                </div>
            </div>
            <div class="row">
                <div class="col-md-offset-2 col-lg-offset-2 col-lg-12">
                    <button type="submit" class="btn btn-primary">Submit</button>
                </div>
            </div>
        </form>
    </div>
    
    <script th:src="@{/webjars/jquery/3.2.0/jquery.min.js}"></script>
    <script th:src="@{/webjars/bootstrap/3.3.7-1/js/bootstrap.js}" ></script>
    </body>
    </html>
    

    application.yml:

    server:
      use-forward-headers: true
      port: 9090
      servlet:
        context-path: /auth
    
    
    management.endpoints.web.exposure.include: "*"
    
    spring:
      application:
        name: authentication-server
    

    您的 sso 服务器如下所示:

    @EnableZuulProxy
    @SpringBootApplication
    public class SsoApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SsoApplication.class, args);
        }
    
        @Bean
        public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ClientContext context,
                                                     OAuth2ProtectedResourceDetails authorizationCodeResourceDetails) {
            return new OAuth2RestTemplate(authorizationCodeResourceDetails, context);
        }
    }
    
    
    
    @Configuration
    @EnableOAuth2Sso
    class OAuth2SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable().cors().and().httpBasic().disable()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                    .and()
                    .authorizeRequests().anyRequest().authenticated();
        }
    }
    

    http://localhost:8080/index.html 上的简单页面:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    It Works by an SSO
    
    <script src="https://code.jquery.com/jquery-3.3.1.min.js"
            integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
            crossorigin="anonymous"></script>
    
    <script>
        $.ajax({
            url: "/hello-service/hello",
            success: function (data, status) {
                window.alert("The returned data" + data);
            }
        })
    
    </script>
    </body>
    </html>
    

    application.yml:

    security:
      oauth2:
        client:
         clientId: client
         clientSecret: secret
         accessTokenUri: http://localhost:9090/auth/oauth/token
         userAuthorizationUri: http://localhost:9090/auth/oauth/authorize
         auto-approve-scopes: '.*'
         registered-redirect-uri: http://localhost:9090/auth/login
         clientAuthenticationScheme: form
        resource:
          user-info-uri: http://localhost:9090/auth/account/user-info
          prefer-token-info: false
    
    
    management.endpoints.web.exposure.include: "*"
    server:
      use-forward-headers: true
      port: 8080
    
    zuul:
      sensitiveHeaders:
      ignoredServices: '*'
      ignoreSecurityHeaders: false
      addHostHeader: true
      routes:
        hello-service:
          serviceId: hello-service
          path: /hello-service/**
          url: http://localhost:4040/
    
    proxy:
      auth:
        routes:
          hello-service: oauth2
    

    您的 hello 服务(资源服务器)将是:

    @SpringBootApplication
    public class ResourceServerApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ResourceServerApplication.class, args);
        }
    
    }
    
    @RestController
    class HelloService {
    
        @GetMapping("/hello")
        public ResponseEntity hello(){
            return ResponseEntity.ok("Hello World!!!");
        }
    }
    
    
    
    @Configuration
    @EnableResourceServer
    class SecurityOAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.csrf().disable().authorizeRequests().anyRequest().authenticated()
                    .and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
        }
    
    }
    

    希望对你有用

    【讨论】:

      【解决方案2】:

      感谢您的快速答复。我有一个问题使它工作。也许你还有另一个好主意。 我无权访问“http://localhost:9977/service/home”上的资源。 我的服务器和 Zuul 代码:

      @EnableOAuth2Sso
      @EnableZuulProxy
      @SpringBootApplication
      @RestController
      public class StackServerApplication {
      
          @GetMapping("/oauth/check_token")
          public java.security.Principal userInfo(Principal principal){
              return principal;
          }
      
          @Autowired
          private OAuth2RestTemplate userInfoRestTemplate;
      
          public static void main(String[] args) {
              SpringApplication.run(StackServerApplication.class, args);
          }
      
          @Bean
          @Primary
          public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ClientContext context,
                  OAuth2ProtectedResourceDetails authorizationCodeResourceDetails) {
              return new OAuth2RestTemplate(authorizationCodeResourceDetails, context);
          }
      
          @Bean
          public UserInfoRestTemplateFactory userInfoRestTemplateFactory() {
              return new UserInfoRestTemplateFactory() {
      
                  @Override
                  public OAuth2RestTemplate getUserInfoRestTemplate() {
                      return userInfoRestTemplate;
                  }
              };
          }
      }
      

      application.yml:

      security:
        oauth2:
          client:
           clientId: client
           clientSecret: secret
           accessTokenUri: http://localhost:9977/auth/oauth/token
           userAuthorizationUri: http://localhost:9977/auth/oauth/authorize
           auto-approve-scopes: '.*'
           registered-redirect-uri: http://localhost:9977/auth/singin
           clientAuthenticationScheme: form
           resource:
             token-info-uri: http://localhost:9977/oauth/check_token  
             preferTokenInfo: false  
      
      server:
        use-forward-headers: true
        port: 9977
      
      zuul:
        sensitiveHeaders:
        ignoredServices: '*'
        ignoreSecurityHeaders: false
        addHostHeader: true
        routes:
          service:
            url: http://localhost:8080
      
      proxy:
        auth:
          routes:
            service: oauth2
      

      资源服务器:

      @EnableResourceServer
      @SpringBootApplication
      public class StackResourceApplication {
      
          public static void main(String[] args) {
              SpringApplication.run(StackResourceApplication.class, args);
          }
      
          @RestController
          public class ServiceController {
      
              @GetMapping("/home")
              public String home() {
                  return "Hermie";
              }            
          }
      }
      

      application.yml:

      spring:
        application:
          name: service
      security:
        oauth2:
          resource:
            token-info-uri: http://localhost:9977/oauth/check_token  
            preferTokenInfo: false
      

      当我调用“http://localhost:9977/service/home”时,服务器告诉我我没有被授权。

      【讨论】:

      • 查看您的代码我发现您不要使用 jwt 令牌,否则我希望使用 security.oauth2.resource.jwt.key-value valorized 标签。在任何情况下,即使在普通的 oauth2 令牌中,也必须在网关和任何资源服务器中配置 security.oauth2.resource.token-info-uri: your/auth/server:yourport/oauth/check_token,否则 sso 和资源服务器不能验证您的令牌
      • 无论如何我已经更新了我的答案,以试图解释如何克服你的问题
      • 是的,我删除了 jwt 令牌,以使其更容易。在我的“StackServerApplication”中,我添加了一个 oauth/check_token 休息端点,并在 yml 文件中添加了“资源”标签,但在访问“localhost:9977/service/home”时我仍然未经授权。
      • 您已删除身份验证服务器上的 UserInfoRestTemplateFactory 和 @GetMapping("/oauth/check_token") 应映射到另一个路径,因为此路径已由 @FrameworkEndpoint 实现,最好不要触摸内部弹簧组件
      • github.com/mrFlick72/oauth2-demo/tree/master 这是我的解决方案的链接
      猜你喜欢
      • 2017-02-14
      • 2019-06-20
      • 2020-06-16
      • 1970-01-01
      • 1970-01-01
      • 2019-11-27
      • 2020-08-02
      • 2011-06-20
      • 2017-11-14
      相关资源
      最近更新 更多