I have a Spring Cloud Gateway running with this dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
And with this annotation in the main method:
#EnableWebFluxSecurity
In my properties file I have these properties:
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://keycloak/realms/dpse-realm
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://keycloak/realms/dpse-realm/protocol/openid-connect/certs
When I do this above every call that go to gateway is required to have a valid JWT token issued by a keycloak server.
My question is how can I avoid the security when I call actuator endpoints.
At this time health check is required to have a token.
I was trying to find a property or another way than create a #Bean or #Configuration class.
But, at least, the correct answer for my particular problem is this:
#EnableWebFluxSecurity
public class SecurityConfiguration {
private final WebEndpointProperties webEndpointProperties;
public SecurityConfiguration(
WebEndpointProperties webEndpointProperties) {
this.webEndpointProperties = webEndpointProperties;
}
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.csrf()
.disable()
.authorizeExchange()
.pathMatchers(webEndpointProperties.getBasePath() + "/health/**",
"/" + webEndpointProperties.getBasePath() + "/info/**")
.permitAll()
.and()
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}
}
Now it allows that actuator end point is permited and other calls have to be authenticated with token.
Related
Struggling with new Spring Security 6.x. Having the following SecuritsFilterChain:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity
public class SecurityConfiguration {
public SecurityConfiguration() {}
#Bean
#Order(1)
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/swagger-ui/**", "/swagger-ui/index.html**", "/swagger-ui/index.html/**", "/v3/api-docs/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterAfter(new JWTAuthorizationFilter(), BasicAuthenticationFilter.class));
return http.build();
}
Unfortunately the URL:
/swagger-ui/index.html
executes the filter JWTAuthorizationFilter which should not be executed.
Without the filter it works.
First, allow me to suggest a few changes for readability, and then I'll comment after that:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(...).permitAll()
.anyRequest().authenticated()
)
.addFilterAfter(new JWTAuthorizationFilter(), BasicAuthenticationFilter.class)
return http.build();
}
The nice thing about this is that it's a little easier to see the authorization rules separately from the rest of the filters being configured.
permitAll
Authentication filters are called on every request.
permitAll(), authenticated(), and hasRole() all work with AuthorizationFilter, which is placed after the authentication filters in the filter chain.
What this means is that JWTAuthorizationFilter, BasicAuthenticationFilter, and all authentication filters are not affected by any authorization rules that you add.
If there are certain requests that JWTAuthorizationFilter should skip, that is up to that filter to say so.
My Spring Webflux application provides multiple authentication methods for the APIs, the user either presents a JWT token or he presents a userid and password. I understand that each authentication method is a separate SecurityWebFilterChain. In my security config I defined 2 Beans, one for basic auth and one for JWT. Setting up each one for different endpoints works fine using a SecurityMatcher, but how do I setup both for the same endpoint. I want either basic auth or JWT token to authenticate for a specific endpoint. All my attempts result in the first authentication method failing and returning a 401 unauthorized without attempting to try the second method. How do I get it not to fail but to try the second SecurityWebFilterChain bean?
Here is the code from my security config
#Configuration
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class SecurityConfig {
#Autowired private SecurityContextRepository securityContextRepository;
#Bean
SecurityWebFilterChain webHttpSecurity(
ServerHttpSecurity http, BasicAuthenticationManager authenticationManager) {
http.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/api/something/**"))
.authenticationManager(authenticationManager)
.authorizeExchange((exchanges) -> exchanges.anyExchange().authenticated())
.httpBasic()
.and()
.csrf()
.disable();
return http.build();
}
#Bean
SecurityWebFilterChain springWebFilterChain(
ServerHttpSecurity http, AuthenticationManager authenticationManager) {
String[] patterns =
new String[] {
"/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/v3/api-docs/**",
"/webjars/**",
};
return http.cors()
.disable()
.exceptionHandling()
.authenticationEntryPoint(
(swe, e) ->
Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED)))
.accessDeniedHandler(
(swe, e) ->
Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN)))
.and()
.csrf()
.disable()
.authenticationManager(authenticationManager)
.securityContextRepository(securityContextRepository)
.authorizeExchange()
.pathMatchers(patterns)
.permitAll()
.pathMatchers(HttpMethod.OPTIONS)
.permitAll()
.anyExchange()
.authenticated()
.and()
.build();
}
The first Bean sets up basic auth for one specific endpoint using a custom authentication manager which veruifies the userid and password, the second bean sets up JWT auth for all other endpoints (with a custom AuthenticationManager that verifies the token etc.) except those that are excluded. Lets say I have the following endpoints
/api/something
/api/whatever
.....
endpoint 1 I want to authenticate with either basic auth or JWT
endpoint 2,3 ,n I want only JWT
As I have it now endpoint 1 is using only basicAuth and all other endpoints use JWT. How can I add JWT to endpoint 1 as well?
I'm trying to create form login with spring boot webflux. I can login and after login I'm redirectored successfully. But when I browse to a page that requires authentication, I'm getting error. If I remove the page from security config and get principal from ReactiveSecurityContextHolder I'm getting the user details.
Here is my security config:
public class SecurityConfig {
#Autowired
private UserService userService;
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/user/account")
.authenticated()
.anyExchange().permitAll()
.and()
.formLogin()
.loginPage("/user/login")
.authenticationSuccessHandler(new RedirectServerAuthenticationSuccessHandler("/"))
.authenticationManager(reactiveAuthenticationManager())
.and()
.logout()
.and()
.build();
}
#Bean
public ReactiveAuthenticationManager reactiveAuthenticationManager() {
return authentication -> userService.loginUser(authentication)
.switchIfEmpty(Mono.error(new UsernameNotFoundException(authentication.getName())))
.map(user -> new UsernamePasswordAuthenticationToken(user, null));
}
}
Do I need to do anything else in the ReactiveAuthenticationManager? Is that even required?
In this repository : https://github.com/mohamedanouarbencheikh/dashboard-auth-microservice
you have a complete example of spring security implementation with jwt in microservice architecture using spring cloud routing (gateway) which is based on reactive programming and Netty as application server, and angular as frontend
Answering to my own question so that anyone facing same problem can get some help:
The issue was resolved when I've changed the UsernamePasswordAuthenticationToken constructor and passed the authority parameter as null. This is really ridiculous. Here is the updated code:
#Bean
public ReactiveAuthenticationManager reactiveAuthenticationManager() {
return authentication -> userService.loginUser(authentication)
.switchIfEmpty(Mono.error(new UsernameNotFoundException(authentication.getName())))
.map(user -> new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()));
}
I've also simplified the config by removing authenticationSuccessHandler and authenticationManager from the config. Spring automatically redirects to /. For authenticationManager it automatically checks for a ReactiveAuthenticationManager bean and uses if found. Here is my updated config:
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.csrf().disable()
.authorizeExchange()
.pathMatchers("/user/account")
.authenticated()
.anyExchange().permitAll()
.and()
.formLogin()
.loginPage("/user/login")
.and()
.logout()
.logoutUrl("/user/logout")
.logoutSuccessHandler(logoutSuccessHandler("/user/bye"))
.and()
.build();
}
I'm using Spring Security OAuth2 to create my own authorization server. In my case I want to enable a Angular client (SPA) to use the Authorization Code Grant.
The client can use the oauth/authorize endpoint, the user can log in and the browser is redirect to the SPA. Now the client wants to get the token via oauth/token. But this endpoint is secured and needs client id and client secret. The security is enabled by default in Spring.
In the docs I could find the following:
The token endpoint is protected for you by default by Spring OAuth in the #Configuration support using HTTP Basic authentication of the client secret. This is not the case in XML (so it should be protected explicitly).
As far as I know there shouldn't be a client secret used in public clients. But that means, that the oauth/token endpoint should not be secure.
Question: Is it a good practice to disable auth for oauth/token? If not, how should I solve this?
This is my WebSecurityConfig:
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#Override
protected UserDetailsService userDetailsService() {
// WARN: Do not use the default password encoder in production environments!
return new InMemoryUserDetailsManager(
User.withDefaultPasswordEncoder()
.username("user-a")
.password("password")
.roles("USER_ROLE")
.build()
);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http = http
.requiresChannel()
.anyRequest()
.requiresSecure()
.and()
.cors()
.and()
.authorizeRequests()
.antMatchers("/.well-known/**")
.permitAll()
.and();
super.configure(http);
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(singletonList("*"));
configuration.setAllowedMethods(asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
I've been working on securing a Restful Service using Spring Security Oauth. I've been banging my head trying to secure the /oauth/token endpoint using SSL and only allowing for POST calls.
I'm using #EnableAuthorizationServer which states
Convenience annotation for enabling an Authorization Server (i.e. an
AuthorizationEndpoint and a TokenEndpoint) in the current application
context, which must be a DispatcherServlet context. Many features of
the server can be customized using #Beans of type
AuthorizationServerConfigurer (e.g. by extending
AuthorizationServerConfigurerAdapter). The user is responsible for
securing the Authorization Endpoint (/oauth/authorize) using normal
Spring Security features (#EnableWebSecurity etc.), but the Token
Endpoint (/oauth/token) will be automatically secured using HTTP Basic
authentication on the client's credentials. Clients must be registered
by providing a ClientDetailsService through one or more
AuthorizationServerConfigurers.
Which is great, but I can't seem to override the token endpoint piece or enforce POST-only calls, like with the intercept-url xml syntax
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore()
}
#Autowired
AuthenticationManager authenticationManager
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient('testApp')
.scopes("read", "write")
.authorities('ROLE_CLIENT')
.authorizedGrantTypes("password","refresh_token")
.secret('secret')
.accessTokenValiditySeconds(7200)
}
}
I secured my Resource server with
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Autowired
private RestAuthenticationEntryPoint authenticationEntryPoint;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.requiresChannel().anyRequest().requiresSecure()
.and()
.csrf()
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
.disable()
.headers()
.frameOptions().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
}
}
Is there a similar builder syntax for the Authorization Servers TokenEndpoint security that uses requiresChannel?
I ended up creating my own config using
org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerSecurityConfiguration
Since i'm using Spring boot I just autowired the SecurityProperties and added this line for the SSL on the Oauth endpoints
if (this.security.isRequireSsl()) {
http.requiresChannel().anyRequest().requiresSecure();
}
And for the POST requirement
http
.authorizeRequests()
.antMatchers(HttpMethod.POST,tokenEndpointPath).fullyAuthenticated()
.antMatchers(HttpMethod.GET,tokenEndpointPath).denyAll()
Afterwards removed the #EnableAuthorizationServer so it would use my config.