英文:
Spring - Remote Basic Authentication
问题
我正在将一个单体式的Java/Spring服务器重写为微服务,同时向客户端提供相同的API接口,以便他们不会注意到任何变化。
在单体式服务器中,我们使用Spring-Security
和Spring-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("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 {
... 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&username=user@example.com&grant_type=password&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是基于令牌的安全授权和身份验证,可以分为四个组件:
- 受保护的资源(只能由具有适当授权的已认证用户访问)
- 资源所有者(定义哪个应用程序可以调用其服务,哪些用户被允许访问该服务以及他们可以做什么)
- 应用程序(将代表用户调用服务的应用程序)
- 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:
- A Protected Resource (can be accessed only by the authenticated user that have the proper authorization)
- A Resource Owner (defines what application can call their service which user are allowed to access the service and what can they do)
- An Application (is the application that is going to call the service on behalf of the users)
- 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:
<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>
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. @EnableAuthorizationServer
is 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("/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;
}
}
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("your application")
.secret("your password")
.authorizedGrantTypes( "refresh_token","password","client_credentials")
.scopes("webclient","mobileclient");
}
}
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("you user")
.password(passwordEncoder().encode("your password"))
.roles("your role");
}
}
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.
<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>
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论