I am trying to build a web service using WebFlux. When I tried to configure Spring Security with Spring WebFlux, the requests are not intercepted by Spring Security. My Spring Security config is:
#EnableWebFlux
#EnableWebFluxSecurity
#Configuration
public class WebConfig {
#Bean
public MapUserDetailsRepository userDetailsRepository() {
UserDetails cust =
User.withUsername("user1").password("password")
.roles("USER").build();
UserDetails admin =
User.withUsername("admin1").password("password")
.roles("ADMIN").build();
return new MapUserDetailsRepository(cust, admin);
}
#Bean
public SecurityWebFilterChain springWebFilterChain(
HttpSecurity httpSecurity) {
return httpSecurity.authorizeExchange().anyExchange().
authenticated().and().build();
}
}
One way is to use method security. You need to add #EnableReactiveMethodSecurity to your configuration class and then secure handler component methods with annotations, such as #PreAuthorize("isAuthenticated()")
This is how I managed to get this working with Spring Boot 2.0.0.M4, but again this may depend on what kind of request handling you are doing.
This is an issue when using Spring Security with WebFlux. The workaround is posted here: https://jira.spring.io/browse/SPR-16144
Related
I'm using Spring Security for Single Sign On implementation. I've added the Spring Security filter chain in my application with annotations, and not in the web.xml.
I want the Spring Security filter chain to only apply to the /saml URLs, and I don't want the /myapi requests to go through Spring Security. But my /myapi requests are still going through the Spring Security filter chain, and Spring's filters.
Here's my code for Spring Security filter registration:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(new SamlExtensionUrlForwardingFilter(), DisableEncodeUrlFilter.class)
.authorizeHttpRequests((authorize) -> authorize
.antMatchers("/saml/*").authenticated()
.antMatchers("/myapi/*").permitAll()
)
.saml2Login()
.successHandler(new MyAuthenticationSuccessHandler());
return http.build();
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/myapi/*");
}
With the webSecurityCustomizer(), I tried to exclude the /myapi/ endpoints from the Spring Security workflow, and bypass the Spring Security filter chain and other Spring Security filters for the endpoint.
Additionally, I tried to get the same results with the following as well, assuming that permitAll() would mean Spring Security authorization is not required for the /myapi requests:
.antMatchers("/myapi/*").permitAll()
Neither of the above two worked, and the /myapi requests still go through Spring'a filters like DisableEncodeUrlFilter, CsrfFilter etc. Ideally, I want only /saml/* requests to go through the Spring Security filter chain and Spring related filters.
I use spring security in spring cloud gateway. cloud version is Finchely.SR2 and spring boot version is 2.0.x
Then i set two role in one matcher like this:
.pathMatchers("/apis/**").hasRole("TEST1")
.pathMatchers("/apis/**").hasRole("TEST2")
but, when i startup application and do test, just TEST1 role can access. TEST2 role get FORBIDDEN 403 response.
I know in spring-boot-starter-web with spring-security, it has any method like
.antMatchers("/apis/**").hasAnyRole("TEST1", "TEST2")
Does webflux spring security has any API like hasAnyRole(String...roles) to use?
There isn't one available in the base APIs from Spring Security, however I've been using the following for anyAuthority,
public class HasAnyAuthority<T> implements ReactiveAuthorizationManager<T> {
private final Collection<String> allowedAuthorities;
public HasAnyAuthority(Collection<String> allowedAuthorities) {
this.allowedAuthorities = allowedAuthorities;
}
#Override
public Mono<AuthorizationDecision> check(final Mono<Authentication> authentication,
T object) {
return authentication.filter(Authentication::isAuthenticated)
.flatMapIterable(Authentication::getAuthorities)
.map(GrantedAuthority::getAuthority).any(allowedAuthorities::contains)
.map(AuthorizationDecision::new)
.defaultIfEmpty(new AuthorizationDecision(false));
}
with usage like,
.access(new HasAnyAuthority<>(allowedAuth.getAuthorities())
hasAnyRole and hasAnyAuthority will be available in WebFlux in Spring Security 5.2.0.
You can try them out now in 5.2.0.M3.
The syntax is the same
.pathMatchers("/apis/**").hasAnyRole("TEST1", "TEST2")
If it helps anyone
.pathMatchers("/apis/**")
.access((mono, context) -> mono.map(auth -> auth.getAuthorities().stream()
.filter(e -> (e.getAuthority().contains(TEST1) || e.getAuthority().contains(TEST2)))
.count() > 0)
.map(AuthorizationDecision::new))
I want to write some kind of unit test which depends on Spring Security.
For example, I have some service method which uses some repository and marked with #PreAuthorize annotation. Repository I can mock with Mockito, there is no problem. Also I can mock Security Context by #WithSecurityContext annotation. But when I run test, the #PreAuthorize annotation is just ignored. Of course I can run that test with #SpringBootTest annotation as an integration test and in this case the Security Context is up but this way is heavy and slow.
Is there a way to run unit test with only Spring Security Context raised?
UPDATE
Made an example of such kind of test. Thanks to #Sam Brannen for giving right direction.
#ActiveProfiles("method-security-test")
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {ExampleService.class, ExampleServiceTest.MethodSecurityConfiguration.class})
public class ExampleServiceTest {
private ExampleService service;
#Autowired
public void setService(ExampleService service) {
this.service = service;
}
#Test
#WithMockUser(username = "john_doe")
public void testAuthenticated() {
String actualMessage = service.example();
Assert.assertEquals("Message of john_doe", actualMessage);
}
#Test(expected = AuthenticationException.class)
public void testNotAuthenticated() {
service.example();
Assert.fail();
}
#TestConfiguration
#EnableGlobalMethodSecurity(prePostEnabled = true)
static class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
}
}
#Service
class ExampleService {
#PreAuthorize("isAuthenticated()")
String example() {
Principal principal = SecurityContextHolder.getContext().getAuthentication();
return "Message of " + principal.getName();
}
The #PreAuthorize annotation from Spring Security will only be honored if Spring Security proxies your component (e.g., service bean).
The simplest way to make that happen is by annotating an #Configuration class with #EnableGlobalMethodSecurity(prePostEnabled = true), having your component registered as a bean (e.g., via component scanning or an #Bean method), and including your AuthenticationManager setup.
You can then create a focused integration test using #ContextConfiguration (without Spring Boot testing support) to load an ApplicationContext from your #Configuration class. And you can use #Autowired to get access to your proxied component which will be advised with the #PreAuthorize security check support.
You might find this old blog post useful as well for background information: https://spring.io/blog/2013/07/04/spring-security-java-config-preview-method-security/
I am new to Spring AMQP and want to use annotation based configuration for both producers and consumers using latest spring amqp 1.5.4 ,
Is there any pseoudo code available for configuration which does the logic for creating connection or #Queue etc.
Probably the quickest way to get started would be use Spring Boot - boot will create all the beans you need (connecting to localhost by default but easily overridable with properties).
You can also look at some of the Spring AMQP test cases.
Have a class annotated with #Configuration in Spring Boot which can provide you with annotation based bean definition :
Here is a sample :
#Configuration
public class QueueConfig {
#Bean
public AmqpAdmin amqpAdmin() {
return new RabbitAdmin(connectionFactory());
}
#Bean
public RabbitTemplate rabbitTemplate() {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory());
rabbitTemplate.setExchange("myQueue");
return rabbitTemplate;
}
#Bean
Queue rabbitQueue() {
return new Queue(WORKERS_QUEUE_NAME, true, false, false, null);
}
}
I have questions on the following areas: spring-session and spring-security.
Spring Session
I have a application protected with Spring Security through basic in-memory authentication as provided in the example sample.
I see spring is creating session id's even the authentication is not successful, meaning I am seeing x-auth-token in my response header as well in the Redis DB even if I don't supply basic authentication credential details.
How do we avoid creating sessions for authentication failures?
Spring Security
Want to use spring security to protect resources assuming spring session creates session only for the protected resources.
Assuming a Signin API (/signin - HTTP Post) validates (username & password) credentials against a third-party REST API .
Once the external API validates the credentials, how do I update the spring security context on the successful authentication?
Access to other secured resources with the x-auth-token needs to be validated and based on the information access to the secured resource should be provided.
Do we need to have Spring Security in this case or shall I use a basic filter and spring session? What is recommended?
Typically it would be best to break your questions into multiple StackOverflow questions since you are more likely to find someone that knows the answer to a single question than both.
How do we avoid creating sessions for authentication failures ?
By default Spring Security will save the last unauthenticated request to session so that after you authenticate it can automatically make the request again. For example, in a browser if you request example.com/a/b/c and are not authenticated, it will save example.com/a/b/c to the HttpSession and then have the user authenticate. After you are authenticated, it will automatically give you the result of example.com/a/b/c. This provides a nice user experience so that your users do not need to type the URL again.
In the case of a REST service this is not necessary since the client would remember which URL needs to be re-requested. You can prevent the saving by modifying the configuration to use a NullRequestCache as shown below:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.requestCache()
.requestCache(new NullRequestCache())
.and()
.httpBasic();
}
You can provide custom authentication by providing your own AuthenticationProvider. For example:
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
public class RestAuthenticationProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
String username = token.getName();
String password = (String) token.getCredentials();
// validate making REST call
boolean success = true;
// likely your REST call will return the roles for the user
String[] roles = new String[] { "ROLE_USER" };
if(!success) {
throw new BadCredentialsException("Bad credentials");
}
return new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.createAuthorityList(roles));
}
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
}
You can then configure your RestAuthenticationProvider using something like this:
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Bean
public RestAuthenticationProvider restAuthenticationProvider() {
return new RestAuthenticationProvider();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, AuthenticationProvider provider) throws Exception {
auth
.authenticationProvider(provider);
}
}
Session IDs are getting stored in Redis even when authentication fails.
Rob's answer of setting NullRequestCache didn't work for me. That is, there were redis entries even after setting request cache to NullRequestCache. To make it work, I had to use an authentication failure handler.
http.formLogin().failureHandler(authenticationFailureHandler()).permitAll();
private AuthenticationFailureHandler authenticationFailureHandler() {
return new AuthenticationFailureHandler();
}
public class AuthenticationFailureHandler
extends SimpleUrlAuthenticationFailureHandler {
}
Note that the failure handler does nothing but extend the default handler explicitly. It returns a 401 in case of failure. If you were doing redirects, you can configure it in the handler easily.