Минимальная настройка для защищенной микросервисной среды - PullRequest
0 голосов
/ 05 июля 2018

Я хотел бы настроить микросервисную среду единого входа с Gradle, Spring Boot2, Zuul, JWT, микросервисами с REST-Api и самодельным сервером аутентификации. Что такое аннотации для сервера аутентификации, шлюза и микросервисов, когда они действуют как клиенты OAuth и как серверы ресурсов? Какая минимальная настройка?

Ответы [ 2 ]

0 голосов
/ 08 июля 2018

Спасибо за ваш быстрый ответ. У меня проблема с этим. Возможно, у вас есть еще одна хорошая идея. Я не авторизован для доступа к своему ресурсу на 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', сервер говорит мне, что я не авторизован.

0 голосов
/ 05 июля 2018

для конфигурации 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 token relay. Он также доступен как функция в этом случае. Я использую 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----

-

конечно, это очень минимальная конфигурация, но для запуска достаточно идентификатора

UPDATE:

не забывайте настройку ресурса в приложении yml на вашем шлюзе, sso и любом другом сервере ресурсов, потому что в противном случае невозможно будет проверить токен на вашем сервере аутентификации.

В случае простого токена 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

типичная точка входа данных вашей учетной записи на сервере авторизации в случае предпочтения Tokenenfo: false.

@RestController
@RequestMapping("/account")
class UserRestFullEndPoint {

    @GetMapping("/userInfo")
    public Principal userInfo(Principal principal){
        return principal;
    }
}

UserInfoRestTemplateFactory в случае, если конфигурация token-info-uri будет предоставлена ​​автоматически к весне, единственное, что нужно помнить, это настроить Oauth2RestTemplate, потому что в противном случае ваш токен больше не будет обновляться

ОБНОВЛЕНИЕ 2

В случае отсутствия токена JWT недостающая конфигурация должна быть добавлена @EnableResourceServer на вашем сервере аутентификации.

Таким образом, ваша пользовательская информация uri вернет основной объект, например, json. Проблема состояла в том, что ваша конечная точка в любом случае вернет значение NULL, и, следовательно, ваша служба получила 401. Это происходит потому, что ваш сервер аутентификации может только возвращать токен и не может предоставлять другие службы, которые не являются конечной точкой платформы, для обеспечения маркер. Поскольку вам нужно возвращать информацию о пользователе, вам нужен способ предоставления ресурса, и, следовательно, вам необходимо предоставить доступ к серверу авторизации, даже как к серверу ресурсов. В случае jwt это бесполезно, потому что проверка токена и информация о пользователе будет предоставлена ​​самим токеном. Информация о пользователе будет предоставлена ​​jwt и подтверждением с помощью ключа jwt.

резюме вашего сервера авторизации будет выглядеть так:

@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 (resourceserver) будет:

@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());
    }

}

Я надеюсь, что это может быть полезно для вас

...