春季 – 远程基本身份验证

huangapple go评论79阅读模式
英文:

Spring - Remote Basic Authentication

问题

我正在将一个单体式的Java/Spring服务器重写为微服务,同时向客户端提供相同的API接口,以便他们不会注意到任何变化。

在单体式服务器中,我们使用Spring-SecuritySpring-Security-OAuth2

第一部分是创建一个基于Java的API网关,它将作为隧道处理所有的身份验证/授权,连接到单体服务器。

在创建一个新的微服务(使用Spring Initializr)后,我尝试配置Spring Security,将所有的身份验证都通过隧道发送到单体服务器,使用如下代码:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final AuthenticationProvider authenticationProvider;

    public WebSecurityConfig(AuthenticationProvider authenticationProvider) {
        this.authenticationProvider = authenticationProvider;
    }
}
@Component("authenticationProvider")
public class CustomRemoteAuthenticationProvider extends RemoteAuthenticationProvider {

    @Override
    @Autowired
    @Qualifier("remoteAuthenticationManager")
    public void setRemoteAuthenticationManager(RemoteAuthenticationManager remoteAuthenticationManager) {
        super.setRemoteAuthenticationManager(remoteAuthenticationManager);
    }

}
@Service("remoteAuthenticationManager")
public class CustomRemoteAuthenticationManager implements RemoteAuthenticationManager {

    @Override
    public Collection<? extends GrantedAuthority> attemptAuthentication(String username, String password) throws RemoteAuthenticationException {
        ... 在这里我会发起一个HTTP调用到单体服务器的`/oauth/login`
    }
}

当我访问Spring Security的http://localhost:8080/login页面时,似乎是起作用的,我可以成功登录,并且请求被隧道传送到了我们的单体服务器。

但是,当我尝试配置OAuth2资源服务器时出现问题,因为我们的客户端目前是使用在Header中带有基本认证信息的POST请求到oauth/token来进行认证:

Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

注意:Basic认证的令牌对于所有客户端都是静态的(我知道这没有什么价值,但这是目前的实现方式,我想实现一个完全兼容的API网关)

这使得他们可以与该端点通信,并获取用户/密码的有效令牌(其内容是application/x-www-form-urlencoded):

password=somepassword&username=user@example.com&grant_type=password&scope=read%20write

问题在于,Spring Security返回了一个401 Unauthorized的错误,甚至没有让请求进入/oauth/login路由并进行远程认证。

我找不到一种方式将基本认证信息隧道传递到单体服务器,以便/oauth/login将会使用远程基本认证进行认证,认证成功后它将充当隧道,将请求体传递到单体服务器的/oauth/login端点(就像我在上面的WebSecurityConfig中成功做的那样)。

如果有任何指导,将不胜感激。提前谢谢!

英文:

I'm rewriting a Monolith Java/Spring server to Microservices while exposing the same API interface to the clients so they won't notice any change was made.

In the Monolith server we use Spring-Security and Spring-Security-OAuth2.

The first part is creating a Java based API-Gateway which will handle all authentication/authorization as a tunnel to the Monolith server.

After creating a new microservice (using spring initializr) I have tried to configure spring security to tunnel all Authentication to the Monolith Server using:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final AuthenticationProvider authenticationProvider;

    public WebSecurityConfig(AuthenticationProvider authenticationProvider) {
        this.authenticationProvider = authenticationProvider;
    }
}
@Component(&quot;authenticationProvider&quot;)
public class CustomRemoteAuthenticationProvider extends RemoteAuthenticationProvider {

    @Override
    @Autowired
    @Qualifier(&quot;remoteAuthenticationManager&quot;)
    public void setRemoteAuthenticationManager(RemoteAuthenticationManager remoteAuthenticationManager) {
        super.setRemoteAuthenticationManager(remoteAuthenticationManager);
    }

}
@Service(&quot;remoteAuthenticationManager&quot;)
public class CustomRemoteAuthenticationManager implements RemoteAuthenticationManager {

    @Override
    public Collection&lt;? extends GrantedAuthority&gt; attemptAuthentication(String username, String password) throws RemoteAuthenticationException {
        ... here I do an HTTP call to the Monolith `/oauth/login`
    }
}

This seems to be working as I visit the http://localhost:8080/login page of the spring-security, I can successfully login and the request is tunneled into our Monolith server.

The problem starts when I try to configure the OAuth2 resource server, as our clients currently authenticating using a POST to oauth/token with some Basic Authentication in the Header:

Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Note: The token for the basic is a static token for all clients (I know it's not worth anything but this is the way it's currently implementated and I try to implement a fully compatible API-Gateway)

Which allows them to communicate with that end-point and get a valid token for the user/password in the body (Which is application/x-www-form-urlencoded):

password=somepassword&amp;username=user@example.com&amp;grant_type=password&amp;scope=read%20write

The problem is that spring-security returning a 401 Unauthorized for that call without even letting the request enter the /oauth/login route and do a remote authentication.

I can't find a way to tunnel the Basic Authentication into the monolith server so the /oauth/login will actually authenticate against the Monolith with Remote Basic Authentication and after success it will act as a tunnel and will pass the body itself into the Monolith /oauth/login end point (as I succesfully did in the WebSecurityConfig above)

Any direction will be appreciated.
Thanks in advance!

答案1

得分: 1

以下是翻译好的内容:

我正在进行一个类似的项目,我认为我可以通过一些指导来帮助你。

OAuth2是基于令牌的安全授权和身份验证,可以分为四个组件:

  1. 受保护的资源(只能由具有适当授权的已认证用户访问)
  2. 资源所有者(定义哪个应用程序可以调用其服务,哪些用户被允许访问该服务以及他们可以做什么)
  3. 应用程序(将代表用户调用服务的应用程序)
  4. OAuth2认证服务(位于应用程序和受保护的资源之间)

在你的案例中,受保护的资源是你想要将其分解为微服务架构的单体应用程序。

  • 首先,你需要创建一个授权服务。

从Spring Initializr创建SpringCloud项目,确保以下依赖项存在:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-security</artifactId>
</dependency>

然后,在OAuth2服务的主类中添加两个注解。@EnableResourceServer 用于告诉你的微服务它是受保护的资源。我将在下面解释为什么需要这样做。@EnableAuthorizationServer 将告诉Spring Cloud该服务将被用作OAuth2服务。以下是代码片段:

@SpringBootApplication
@EnableResourceServer
@EnableAuthorizationServer
public class Oauth2ServerApplication {

    public static void main(String[] args) {
        SpringApplication.run(Oauth2ServerApplication.class, args);
    }

}

我们创建一个端点来暴露用户信息。这个端点将由其他服务调用。这就是为什么我们使用@EnableResourceServer对这个应用程序进行了注解的原因。以下是返回用户信息的REST端点:

@RestController
@RequestMapping("/user")
public class UserRestController {

    @GetMapping(produces="application/json")
    public Map<String, Object> getUser(OAuth2Authentication user) {
        Map<String, Object> userInfo = new HashMap<>();
        userInfo.put("user", user.getUserAuthentication().getPrincipal());
        userInfo.put("authorities", user.getUserAuthentication().getAuthorities());
        return userInfo;
    }

}

现在,你将在OAuth2服务中注册应用程序。你将定义将访问受保护资源的应用程序。你将创建一个配置类,定义可以使用你的服务的应用程序。

@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    OAuth2ConfigParameters oauth2ConfigParameters;

    @Bean
    public OAuth2ConfigParameters oAuth2ConfigParameters() {
        return new OAuth2ConfigParameters();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("your application")
                .secret("your password")
                .authorizedGrantTypes("refresh_token", "password", "client_credentials")
                .scopes("webclient", "mobileclient");
    }
}

你需要为应用程序定义用户和角色。如果你以前使用过SpringBoot进行安全性配置,这将很熟悉。请查看下面的代码片段:

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    @Bean
    public UserDetailsService userDetailsServiceBean() throws Exception {
        return super.userDetailsServiceBean();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override 
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("your user")
            .password(passwordEncoder().encode("your password"))
            .roles("your role");
    }
}

最后,使用Postman检查是否可以从OAuth2服务中检索令牌。

HttpMethod: POST,URL:http://localhost:应用端口/oauth/token

Authorization Type: Basic,username:客户端ID,password:客户端密码

Body: 表单数据,

grant:password

scope:webclient

username:你的用户名

password:你的密码

在检索到令牌后,测试是否可以使用生成的令牌访问暴露用户信息的端点。

HttpMethod: Get,URL:localhost:应用端口/user

Authorization:Bearer,Token:生成的令牌


  • 其次,保护将从网关服务器访问的旧单体应用程序。 请记住,你的旧单体应用程序是受保护的资源。

首先,你需要将Spring Security和OAuth2的库添加到你要保护的服务中。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
</dependency>

接下来,在单体应用程序的application.yml中,配置你的oauth2服务的服务点。这样做是因为单体应用程序是受保护的服务,每次收到请求时,你都希望检查请求的令牌是否有效。

security:
  oauth2:
    resource:
      userInfoUri: http://localhost:oauth2-app-port/auth/user

之后,不要忘记添加@EnableResourceServer,它将使单体应用程序成为受保护的资源。


@SpringBootApplication
@EnableResourceServer
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

然后,我们指定所需的限制。下面

英文:

I am doing a similar project and I think that I can help you with some guidance.

OAuth2 is token-based security authorization and authentication that we can break in four components:

  1. A Protected Resource (can be accessed only by the authenticated user that have the proper authorization)
  2. A Resource Owner (defines what application can call their service which user are allowed to access the service and what can they do)
  3. An Application (is the application that is going to call the service on behalf of the users)
  4. OAuth2 Authentication Service(stands between the application and the protected resource)

In your case, the protected resource is the monolith that you want to break in a microservices architecture.

  • The first thing you need to do is create an authorization service.

Create SpringCloud project from spring intilizr make sure that the dependencies below are present:

		&lt;dependency&gt;
			&lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
			&lt;artifactId&gt;spring-cloud-starter-oauth2&lt;/artifactId&gt;
		&lt;/dependency&gt;

		&lt;dependency&gt;
			&lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
			&lt;artifactId&gt;spring-cloud-starter-security&lt;/artifactId&gt;
		&lt;/dependency&gt;

After that in the main class of oauth2 service you need to add two annotation. @EnableResourceServer is used to tell your microservice that is a protected resource. I will explain below why this is needed. @EnableAuthorizationServeris going to tell spring cloud that is service is going to be used as OAuth2Service. Below it is the code snippet:

@SpringBootApplication
@EnableResourceServer
@EnableAuthorizationServer
public class Oauth2ServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(Oauth2ServerApplication.class, args);
	}

}

We create an endpoint that is going to exposes the user information. This endpoint is going to be called by other services. This is the reason why we annotated this application with @EnableResourceServer. Below is the rest endpoint that return the user information:

@RestController
@RequestMapping(&quot;/user&quot;)
public class UserRestController {
	
	@GetMapping(produces=&quot;application/json&quot;)
	public Map&lt;String,Object&gt; getUser(OAuth2Authentication user){
		Map&lt;String,Object&gt; userInfo = new HashMap&lt;&gt;();
		userInfo.put(&quot;user&quot;,user.getUserAuthentication().getPrincipal());
		userInfo.put(&quot;authorities&quot;, user.getUserAuthentication().getAuthorities());
		return userInfo;
	}

}

Now you will register the application in oauth2 service. You will define the application that will access the protected resource. You will create a configuration class that will define what application can use your service.

@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

	@Autowired
	private AuthenticationManager authenticationManager;
	@Autowired
	private UserDetailsService userDetailsService;
	@Autowired
	private PasswordEncoder passwordEncoder;
	@Autowired
	OAuth2ConfigParameters oauth2ConfigParameters;

	@Bean
	public OAuth2ConfigParameters oAuth2ConfigParameters() {
		return new OAuth2ConfigParameters();
	}
	
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService);
    }
	
	@Override
	public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
		clients.inMemory()
		.withClient(&quot;your application&quot;)
		.secret(&quot;your password&quot;)
		.authorizedGrantTypes( &quot;refresh_token&quot;,&quot;password&quot;,&quot;client_credentials&quot;)
        .scopes(&quot;webclient&quot;,&quot;mobileclient&quot;);
	}
}

You need to define what users and roles for the application. This is familiar if you have done security with SpringBoot. Check the snippet below:

@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

	@Override
	@Bean
	public AuthenticationManager authenticationManagerBean() throws Exception {
		return super.authenticationManagerBean();
	}
	
	@Override
	@Bean
	public UserDetailsService userDetailsServiceBean() throws Exception {
		return super.userDetailsServiceBean();
	}
	
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
	
	@Override 
	public void configure(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()
		.withUser(&quot;you user&quot;)
		.password(passwordEncoder().encode(&quot;your password&quot;))
		.roles(&quot;your role&quot;);
	}
}

In the end check with postman if you can retrieve the token from oauth2 service.

HttpMethod: POST, URL : http://localhost:application-port/oauth/token

Authorization Type: Basic , username: Client id, password: client secret

Body: form data,

grant:password

scope:webclient

username: your username

password: your password

After retrieving the token, test if you can access that token the URL of the endpoint which you expose user information.

HttpMethod: Get, URL: localhost:application-port/user

Authorization: Bearer, Token: token generated


  • Second, protecting your old monolith which will be accessed from the gateway server. Remember that your old monolith is a protected resource.

First you need to add Spring Security and OAuth2 jars to the service you are trying to protect.

&lt;dependency&gt;
&lt;groupId&gt;org.springframework.cloud&lt;/groupId&gt;
&lt;artifactId&gt;spring-cloud-security&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.springframework.security.oauth&lt;/groupId&gt;
&lt;artifactId&gt;spring-security-oauth2&lt;/artifactId&gt;
&lt;/dependency&gt;

Next in the application.yml of the monolith we configure service point of your oauth2 service. This is done because the monolith is a protected service and every time a request came you want to check if the token of the request is valid.

security:
 oauth2:
  resource:
   userInfoUri: http://localhost:oauth2-app-port/auth/user

After that don't forget to add the @EnableResourceServer which makes the monolith a protected resource.


@SpringBootApplication
@EnableResourceServer
public class Application {

	public static void main(String[] args) {
		SpringApplication.run(Application.class, args);
	}
}

After that we specify the restriction we want. I have provided below with example restricting access to only authenticated users.

@Configuration
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
	@Override
	public void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests().anyRequest().authenticated();
	}
}

  • Recap

Your user is authenticated from OAuth service and has the generated token. With the generated token send a request to monolith through the gateway server. The gateway tries to access monolith by propagating the token that it received from the request. The monolith check if token it is valid.

Below is a link of my microservice architecture with zuul gateway server and oauth2 server: https://github.com/rshtishi/payroll.
You can check it if you want to see more details.

huangapple
  • 本文由 发表于 2020年4月10日 19:46:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/61139608.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定