Spring: forwarding to /oauth/token endpoint loses authentication - spring-boot

I'm building a Spring Boot authorization server which needs to generate Oauth2 tokens with two different auth methods. I want to have a different endpoint for each method, but by default Spring only creates /oauth/token, and while it can be changed, I don't think it is possible to have two different paths for it.
As an alternative, I'm trying to create two methods in a controller which do an internal forward to /oauth/token, adding a parameter to the request so I can know where it came from.
I have something like this:
#RequestMapping(value = "/foo/oauth/token", method = RequestMethod.POST)
public ModelAndView fooOauth(ModelMap model) {
model.addAttribute("method", "foo");
return new ModelAndView("forward:/oauth/token", model);
}
This performs the forward correctly, but the auth fails with:
There is no client authentication. Try adding an appropriate authentication filter.
The same request works correctly when sent to /oauth/token directly, so I'm guessing that the problem is that the BasicAuthenticationFilter is not running after the forward.
How can I make it work?

I had exactly the same issue. After some research I found out that the problem was caused by Spring Boot 2, not by Spring Security configurations. According to the Spring Boot 2.0 migration guide:
Spring Security and Spring Session filters are configured for ASYNC, ERROR, and REQUEST dispatcher types.
and the Spring Boot's SecurityFilterAutoConfiguration source code:
#Bean
#ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes(
SecurityProperties securityProperties) {
if (securityProperties.getFilter().getDispatcherTypes() == null) {
return null;
}
return securityProperties.getFilter().getDispatcherTypes().stream()
.map((type) -> DispatcherType.valueOf(type.name())).collect(Collectors
.collectingAndThen(Collectors.toSet(), EnumSet::copyOf));
}
where the defaults for securityProperties.getFilter().getDispatcherTypes() are defined in SecurityProperties as:
private Set<DispatcherType> dispatcherTypes = new HashSet<>(
Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));
Thus by default, Spring Boot configures Spring Security so that its filters will not be applied to FORWARD requests (but only to ASYNC, ERROR and REQUEST), and therefore no security filter will be applied to authenticate the requests when forwarding them to /oauth/token.
The solution is simple. You can either add the following line to your application.properties in order to apply default filters to ALL forwarded requests
spring.security.filter.dispatcher-types=async,error,request,forward
or create your own custom filter chain with a path matcher and dispatcherType=FORWARD to only filter requests that are forwared to /oauth/token.

Looking carefully to the filter chains created for the Oauth endpoints, and for the forwarding controllers, it's easy to see that the latter are missing the BasicAuthenticationFilter, because they aren't authenticated, and auth isn't performed again after the forward.
To solve it, I created a new config like this:
#Configuration
public class ForwarderSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private List<AuthorizationServerConfigurer> configurers = Collections.emptyList();
#Autowired
private FooClientDetailsService fooClientDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
for (AuthorizationServerConfigurer configurerBit : configurers) configurerBit.configure(configurer);
http.apply(configurer);
http
.authorizeRequests()
.antMatchers("/foo/oauth/token").fullyAuthenticated()
.and()
.requestMatchers()
.antMatchers("/foo/oauth/token");
http.setSharedObject(ClientDetailsService.class, fooClientDetailsService);
}
}
This code mimics what Spring Oauth does behind the scenes (here), running identical filter chains with the same authentication options on both endpoints.
When the /oauth/token endpoint finally runs, it finds the auth results that it expects, and everything works.
Finally, if you want to run a different ClientDetailsService on two forwarding endpoints, you just have to create two configuration classes like this one, and replace the ClientDetailsService on the setSharedObject call in each of them. Note that for this, you'll have to set different #Order values in each class.

Related

Spring authorization at package level; AOP?

we have a spring boot app with a java package that has spring controllers with endpoints for admin-like functionality. right now they all start with the same request mapping.
so one way i could do authorization of every endpoint in the package is by WebSecurityConfigurerAdapter implementation...
http.authorizeRequests()
.antMatchers("/admin/**").access("hasRole('ADMIN')")
but i was thinking it would be nice to use AOP somehow to target the package of the admin controllers to PreAuthorize all controller methods in the package. just in case someone on the project creates a new controller in the proejct with a different request mapping it would automatically be projected. also, if we decided to PreAuthorize at the #Service level instead of the controller level then this way could be used as well.
so, is it possible to PreAuthorize at the package level with AOP or some other way?
Spring provides default AOP interceptor for #Secured and #PreAuthorized annotations, but it works only on class or method level, and AFAIK not intended to be expanded to package level.
To put it simply, Spring intercepts calls to certain methods or all class methods with these annotations and check whether SecurityContextHolder holds Authority object and whether its collection of GrantedAuthority matches any of the annotation's value field values. So, you can do the same thing using AOP, for example like this:
#Aspect
#Component
public class AdminServiceAOPAuthorization {
private static final List<String> ALLOWED_ROLES = List.of("ADMIN", "SUPER_ADMIN");
#Pointcut("within(com.example.service.admin.*)") // <- any method in any class of the package
public void adminServiceLayer() {}
#Before("adminServiceLayer()")
public void authorize(JoinPoint jp) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
// throw some exception depending on the logic
}
boolean authorized = authentication.getAuthorities().stream()
.anyMatch(ga -> ALLOWED_ROLES.contains(ga.getAuthority()));
if (!authorized) {
throw new AccessDeniedException("Access denied");
// log or whatever
}
}
}
For better performance I'd advise to use it at the service layer to let Spring create proxy using implemented interface (if you use them, of course) instead of proxying controller class.

How does spring security pass credentials? / Postman

I just created a basic layer to secure my Spring API.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
#Override
public UserDetailsService userDetailsService() {
List<UserDetails> users = new ArrayList<>();
users.add(User.withDefaultPasswordEncoder().username("Admin").password("xxx!!").roles("ADMIN").build());
users.add(User.withDefaultPasswordEncoder().username("Pharmacist").password("xxx!!").roles("PHARMACIST").build());
users.add(User.withDefaultPasswordEncoder().username("Office").password("xxx!!").roles("OFFICE").build());
return new InMemoryUserDetailsManager(users);
}
}
I am using Postman to test my endpoints and it's working via the Authorization function of Postman.
What if I want to use this parameters (username,password,role) in my frontend?
I mean, that I am trying somehow to see how spring security passes the values to the url: http://localhost:8080 but i cant find out how. I need this to be able to handle in my Frontend with these vaules.
I am really new in this Spring Security Chapter.

How to simulate session closing / expiring in Spring Boot tests?

I would like to add a couple of tests to the example shown here:
https://spring.io/guides/gs/securing-web/
to be able to verify that a user can no longer access resources requiring authentication when session closes or expires. I would like to simulate both the following conditions in my tests:
a) the user voluntarily ends their session (e.g. close their browser);
b) the session times out;
I don't know how to reproduce those conditions using MockMvc.
I managed to do the following:
#Test
public void sessionIsInvalid() throws Exception {
FormLoginRequestBuilder login = formLogin()
.user("user")
.password("password");
mockMvc.perform(login)
.andExpect(authenticated())
.andDo(mvcResult -> {
MockHttpSession session = (MockHttpSession)mvcResult.getRequest().getSession();
session.invalidate();
mockMvc.perform(get("/hello")
.session(session))
.andExpect(status().isFound());
});
}
...which seems to work but I am not totally sure what invalidate does in this context and whether it matches condition a) above.
To emulate the session timeout, I've done instead:
#Test
public void sessionExpires() throws Exception {
FormLoginRequestBuilder login = formLogin()
.user("user")
.password("password");
mockMvc.perform(login)
.andExpect(authenticated())
.andDo(mvcResult -> {
MockHttpSession session = (MockHttpSession)mvcResult.getRequest().getSession();
session.setMaxInactiveInterval(1);
Thread.sleep(3);
mockMvc.perform(get("/hello")
.session(session))
.andExpect(status().isFound());
});
}
...but this doesn't work. Can someone help me understand what I am doing wrong?
When using Spring Boot with Spring Security (which is all about in you link), my approach is this:
create a custom spring security filter that is able to "convince" spring security that the session is expired (whatever it believes a session is)
add the custom filter just before ConcurrentSessionFilter
create an inner static #TestConfiguration class which could, in theory, just configure the HttpSecurity to add the custom filter (that's all we want). In practice I found that usually I have to have the class annotated with #TestConfiguration to extend my project's security configuration class (or at least the main one, if having many, e.g. SecurityConfiguration for my project); because in SecurityConfiguration I usually declare other #Bean too (e.g. CorsConfigurationSource) I usually have to also use #WebMvcTest(properties = "spring.main.allow-bean-definition-overriding=true", ...) to avoid the bean overriding error; have the class annotated with #TestConfiguration to be annotated with #Order(HIGHEST_PRECEDENCE) too.
create a simple web mvc test trying to GET some project-existing endpoint, e.g.:
#Test
#SneakyThrows
#WithMockUser
void sessionExpired() {
this.mvc.perform(get("/some-endpoint-here")).andExpect(...);
}
run the test and expect for your configured session expiration strategy to kick in; see HttpSecurity.sessionManagement(session -> session...expiredUrl(...)) or HttpSecurity.sessionManagement(session -> session...expiredSessionStrategy(...))
The below spring security configuration provided as a #TestConfiguration works with Spring Boot 2.3.12.RELEASE (and probably many more).
#TestConfiguration
#Order(HIGHEST_PRECEDENCE)
static class Config extends SecurityConfiguration {
public Config(SessionInformationExpiredStrategy expiredSessionStrategy, InvalidSessionStrategy invalidSessionStrategy) {
super(expiredSessionStrategy, invalidSessionStrategy);
}
#SneakyThrows
#Override
protected void configure(HttpSecurity http) {
super.configure(http);
// the custom filter as a lambda expression
http.addFilterBefore((request, response, chain) -> {
// preparing some objects we gonna need
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpSession session = httpRequest.getSession(false);
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// getting our hands on the object that Spring Security believes
// is the "session" and which is in ConcurrentSessionFilter
List<Filter> filters = (List) ReflectionTestUtils.getField(chain, "additionalFilters");
int currentPosition = (int) ReflectionTestUtils.getField(chain, "currentPosition");
ConcurrentSessionFilter concurrentSessionFilter = (ConcurrentSessionFilter) filters.get(currentPosition);
SessionRegistry sessionRegistry = (SessionRegistry) ReflectionTestUtils.getField(concurrentSessionFilter, "sessionRegistry");
// the "session" does not exist (from Spring Security's PoV),
// we actually have to create (aka "register") it
sessionRegistry.registerNewSession(session.getId(), authentication.getPrincipal());
// the actual session expiration (from Spring Security's PoV)
sessionRegistry.getSessionInformation(session.getId()).expireNow();
// let the filters continue their job; ConcurrentSessionFilter
// follows and it'll determine that the "session" is expired
chain.doFilter(request, response);
}, ConcurrentSessionFilter.class);
log.debug("begin");
}
}
session.setMaxInactiveInterval(1); // in seconds
Thread.sleep(3); // in milliseconds

Spring Security HttpSecurity Configuration Testing

I have a Spring Boot + Spring Security application that has severalantMatchers paths; some fullyAuthenticated(), some permitAll().
How to I write a test that verifies SecurityConfiguration has my endpoints under /api/** (and ultimately others) secured correctly?
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
//...
.antMatchers("/api/**").fullyAuthenticated()
}
}
Using spring-boot-1.5.2.RELEASE, spring-security-core-4.2.2-release.
Clarification1: I want to as-directly-as-possible test the SecurityConfiguration, as opposed to transitively testing via one of the /api/** endpoints, which may have their own #PreAuthorize security.
Clarification2: I would like something similar to this WebSecurityConfigurerAdapterTests.
Clarification3: I would like to #Autowire something at the Spring Security layer, ideally HttpSecurity, to test.
So you want to ensure that if someone changes .antMatchers("/api/**") to .antMatchers("/WRONG_PATH/**") then you have a test that will figure it out ?
The rules you define using HttpSecurity will end up configuring a FilterChainProxy with one or more SecurityFilterChain, each with a list of filters. Each filter, such as UsernamePasswordAuthenticationFilter
(used for form-based login), will have a RequestMatcher defined in the super class AbstractAuthenticationProcessingFilter. The problem is that RequestMatcher is an interface which currently have 12 different implementations, and this includes AndRequestMatcher and OrRequestMatcher, so the matching logic is not always simple. And most importantly RequestMatcher only has one method boolean matches(HttpServletRequest request), and the implementation often does not expose the configuration, so you will have to use reflection to access the private configurations of each RequestMatcher implementation (which could change in the future).
If you go down this path, and autowire FilterChainProxy into a test and use reflection to reverse-engineer the configuration, you have to consider all the implementation dependencies you have. For instance WebSecurityConfigurerAdapter has a default list of filters, which may change between releases, and unless disable it, and when it is disabled you have to define every filter explicitly. In addition new filters and RequestMatchers could be added over time, or the filter chain generated by HttpSecurity in one version of Spring Security may be slightly different in the next version (maybe not likely, but still possible).
Writing a generic test for your spring security configuration, is technically possible, but it is not exactly an easy thing to do, and the Spring Security filters certainly were not designed to support this. I have worked extensively with Spring Security since 2010, and I have never had the need for such a test, and personally I think it would be a waste of time trying to implement it. I think the time will be much better spent writing a test framework that makes it easy to write integration tests, which will implicitly test the security layer as well as the business logic.
I see below test case can help you achieve what you want. It is an Integration Test to test the Web Security configuration and we have similar testing done for all our code that is TDD driven.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application.class)
#WebAppConfiguration
public class WebConfigIT {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Before
public void setup() throws Exception {
mockMvc = webAppContextSetup(webApplicationContext)
.addFilter(springSecurityFilterChain)
.build();
}
#Test
public void testAuthenticationAtAPIURI() throws Exception {
mockMvc.perform(get("/api/xyz"))
.andExpect(status.is3xxRedirection());
}
This though looks like doing an explicit testing of the end-point (which is anyways a testing one have to do if doing TDD) but this is also bringing the Spring Security Filter Chain in context to enable you test the Security Context for the APP.
MockMVC should be enough to verify you security configuration since the only thing it mocks is the Http layer. However if you really wish to test your Spring Boot application, Tomcat server and all, you need to use #SpringBootTest, like this
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class NoGoServiceTest {
#LocalServerPort
private int port;
private <T> T makeDepthRequest(NoGoRequest request, NoGoResponse response, String path, Class<T> responseClass) {
testService.addRequestResponseMapping(request, response);
RestTemplate template = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON));
headers.add("Authorization", "Bearer " + tokenProvider.getToken());
RequestEntity<NoGoRequest> requestEntity = new RequestEntity<>(request, headers, HttpMethod.POST, getURI(path));
ResponseEntity<T> responseEntity = template.exchange(requestEntity, responseClass);
return responseEntity.getBody();
}
#SneakyThrows(URISyntaxException.class)
private URI getURI(String path) {
return new URI("http://localhost:" +port + "/nogo" + path);
}
// Test that makes request using `makeDepthRequest`
}
This code is a part on a test taken from an open source project (https://github.com/maritime-web/NoGoService). The basic idea is to start the test on a random port, which Spring will then inject into a field on the test. This allows you to construct URLs and use Springs RestTemplate to make http request to the server, using the same DTO classes as your Controllers. If the authentication mechanism is Basic or Token you simply have to add the correct Authorization header as in this example.
If you use Form authentication, then it becomes a bit harder, because you first have to GET /login, then extract the CSRF token and the JSessionId cookie, and the POST them with the credentials to /login, and after login you have to extract the new JSessionId cookie, as the sessionId is changed after login for security reasons.
Hope this was what you needed.
If you want to programatically know which endpoints exist, you can autowire the List of RequestHandlerProvider into your test and filter them based on the path they are exposed on.
#Autowired
List<RequestHandlerProvider> handlerProviders;
#Test
public void doTest() {
for (RequestHandlerProvider handlerProvider : handlerProviders) {
for (RequestHandler requestHandler : handlerProvider.requestHandlers()) {
for (String pattern : requestHandler.getPatternsCondition().getPatterns()) {
// call the endpoint without security and check that you get 401
}
}
}
}
Using the RequestHandlerProvider is how SpringFox determines which endpoint are available and their signature, when it build the swagger definition for an API.
Unless you spend a long time building the correct input for each endpoint you will not get 200 OK back from the endpoint when including a valid security token, so you probably have to accept 400 as a correct response.
If you are already worried some developer would make security related mistakes when introducing a new endpoint, I would be equally worried about the logic of the endpoint, which is why I think you should have an integration test for each of them, and that would test your security as well.
Thinking outside the box a little, and answering the question in a different way, would it not be easier to simply define a static String[], e.g.
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final String[] FULLY_AUTH_PUBLIC_URLS = {"/api/**", "/swagger-resources/**", "/health", "/info" };
protected void configure(HttpSecurity http) throws Exception {
http
//...
.antMatchers(FULLY_AUTH_PUBLIC_URLS).fullyAuthenticated()
}
}
...
And then if the purpose of the test is to ensure that no changes are made to the public urls simply test the known list?
The assumption here is that Spring Security works and has been tested so the only thing we are testing for is that the list of public URLs has not been changed. If they have changed a test should fail highlighting to the developer that there are dragons changing these values? I understand this does not cover the clarifications but assuming the supplied static public URLs are known to be accurate then this approach would provide a unit testable back stop if this is needed.

Add custom endpoint to Spring OAuth2 Authorization Server

I am interested in adding a custom rest endpoint to my OAuth2 Authorization server.
I want to add a registration endpoint that my UI resource server can call, register a user, and get back a token all in one shot (auto login on registration).
I can make this in two requests since the UI Resource Server has the password of the user, but I would prefer to do it in one, since the I am re-using the Authorization Server to store all my user credentials.
I have created an endpoint like
#FrameworkEndpoint
class RegistrationController {
#Autowired
LocalUserAuthenticationService userDetailsService
#Autowired
TokenEndpoint tokenEndpoint
#RequestMapping(value = "/registration", method = RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public ResponseEntity<OAuth2AccessToken> registerUser(Principal principal,
#RequestBody #Valid RegistrationRequest registrationRequest) {
userDetailsService.register(registrationRequest.email, registrationRequest.password)
return tokenEndpoint.getAccessToken(principal, [grant_type: 'password', username: registrationRequest.email, password: registrationRequest.password])
}
}
And I register it in my context
#EnableAuthorizationServer
public class AuthServerConfig {
#Bean
public RegistrationController(){
return new RegistrationController()
} ... more
}
However, the request is always unauthorized when it is used this way. It says it cannot find the user. It can resolve the basic auth credentials, but wherever it is looking for them it cannot find them despite this bean being registered within this context.
The documentation for #FrameworkEndpoint says
Use with #RequestMapping and all the other #Controller features (and match with a FrameworkEndpointHandlerMapping in the servlet context)
but i cannot seem to crack how to actually do that. Or if I'm misunderstanding it.
How can I get this properly registered so it works like the other framework beans?

Resources