Query on Http Basic Authentication in Spring Security - java

Being new to Spring security, was trying a simple REST GET call with HTTP Basic Authentication. Scenario :
For correct usr/pwd, gives http ok. Then I change only the pwd. For incorrect pwd also it gives 200 ok. And the server log says :
o.s.s.w.a.i.FilterSecurityInterceptor : Did not re-authenticate
Question - how do I reauth every time and send http 401 unauthorized to the client?
Code :
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
log.info("In securityFilterChain with HttpSecurity");
http
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()//Customizer.withDefaults())
.authenticationEntryPoint(authEntryPoint);
return http.build();
}
#Bean
public UserDetailsService initUser() {
UserDetails user = User.builder()
.username("basic")
.password(encoder.encode("basic123"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
#Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(initUser())
.and()
.build();
}

Related

Spring security: REST with MVC

Im begginer in spring security! I have MVC + REST application written on Spring Boot. I have a code for securing my app:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {
#Bean
#Order(1)
public SecurityFilterChain apiFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(
SessionCreationPolicy.STATELESS).and()
.securityMatcher("/api/**")
.authorizeHttpRequests(authorize ->
authorize.requestMatchers("/api/user/**").hasRole("ROOT")
.anyRequest().authenticated())
.httpBasic(basic ->
basic.authenticationEntryPoint(
(request, response, exp)->
response.setStatus(401)))
.build();
}
#Bean
public SecurityFilterChain formFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests(authorize ->
authorize.requestMatchers("/login*", "/web-res/**").permitAll()
.anyRequest().authenticated())
.formLogin(form ->
form.loginPage("/login")
.failureUrl("/login?error"))
.logout(logout ->
logout.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSIONID"))
.build();
}
}
Thats work fine, but when i trying getting data from MVC (that was authorized with formFilterChain) with ajax to /api/** (that controls by apiFilterChain) - i need use basic auth.
How i can fix that to take data from /api/** with authorized by form login method?
My method after reading documentation:
#Configuration
#EnableWebSecurity
#EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {
private final Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>
.AuthorizationManagerRequestMatcherRegistry> securedRequests = authorize -> {
authorize.requestMatchers("/login*", "/web-res/**").permitAll()
.requestMatchers("/api/user/**").hasRole("ROOT")
.anyRequest().authenticated();
};
#Bean
#Order(1)
public SecurityFilterChain apiFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(
SessionCreationPolicy.NEVER).and()
.authorizeHttpRequests(securedRequests)
// checking for "Authorization" header
.securityMatcher(request ->
request.getHeader("Authorization") != null)
.httpBasic(basic ->
basic.authenticationEntryPoint(
(request, response, exp)->
response.setStatus(401)))
.build();
}
#Bean
public SecurityFilterChain formFilterChain(
HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.authorizeHttpRequests(securedRequests)
.formLogin(form ->
form.loginPage("/login")
.failureUrl("/login?error"))
.logout(logout ->
logout.logoutUrl("/logout")
.logoutSuccessUrl("/")
.invalidateHttpSession(true)
.clearAuthentication(true)
.deleteCookies("JSESSIONID"))
.build();
}
}
First filter will working if request has header "Authorization"
// checking for "Authorization" header
...
.securityMatcher(request ->
request.getHeader("Authorization") != null)
...
If header "Authorization" not found - then executing formFilterChain.

spring security in memory authentication accepts any password after first authentication

I want to add simple config for basic authentication using spring security InMemoryUserDetailsManager
After adding following configuration I am able to authenticate with the in memory user (myUser) and the password for this user:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(inMemoryUserDetailsManager());
}
#Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
List<UserDetails> userDetailsList = new ArrayList<>();
userDetailsList.add(User.withUsername("myUser").password(passwordEncoder().encode("password"))
.roles("USER").build());
return new InMemoryUserDetailsManager(userDetailsList);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
The thing is that if I change the password from postman I am still able to authenticate. If I stop application server and start the application again and try with wrong password and correct username it returns 401 ( which is expected). However if next request is sent with the correct header with username and password (myUser, password) and then send the request after that with wrong password it seems the wrong password is accepted. As soon as I change the username to some random word it returns 401 unauthorized. Something is missing from my configuration and I do not have a clue what is it.
Spring by default stores the HttpSession of the Authentication details. So whenever user logs in and authentication is successful, the details are stores in ThreadLocal and whenever the next login happens, it picks it up from the security context instead of authenticating again. Spring Security provides multiple Policies for Session Management. For your use case, you need to configure your HttpSecurity with SessionCreationPolicy.STATELESS.
http
.csrf()
.disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
You can also refer the below article for detailed information:
https://www.javadevjournal.com/spring-security/spring-security-session/

spring boot oauth2 feign allow anonymous requests

I have a client service that distribute a single page application. All the requests from the single page app pass through the client service that uses proxies (Feign) to redirect the calls.
I'd like to allow anonymous calls but I'm not able to do that with my current configuration.
So to make it simpler I have three services : a client, an oauth2 server and an oauth2 resource server.
The oauth2 server is also a resource server.
The client is connected to the oauth2-server with this configuration
security:
oauth2:
client:
clientId: autorisation_code_client
clientSecret: *******
accessTokenUri: https://localhost:****/oauth2-server/oauth/token
userAuthorizationUri: https://localhost:****/oauth2-server/oauth/authorize
#tokenCheckUri: https://localhost:****/oauth2-server/oauth/check_token
resource:
userInfoUri: https://localhost:****/oauth2-server/me
Here is the WebSecurityConfigurerAdapter class of the client, when an user try to access to the login path he's redirected to the oauth2-server to authenticate himself.
#Override
public void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.authorizeRequests()
.antMatchers(
"/",
"/index.html",
"/login**",
"/logout**",
//resources
"/assets/**",
"/static/**",
"/*.ico",
"/*.js",
"/*.json").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(csrfHeaderFilter(), SessionManagementFilter.class);
}
The feign proxy used by the client, I'd like to configure the oauth2-server/user/like/*** path to be accessible by anonymous users.
#RestController
#FeignClient(name = "oauth2-server", url = "https://localhost:****")
public interface ProxyOauth2Server {
#GetMapping(value = "oauth2-server/user/like/{name}")
ResponseEntity<?> getUserLikeName(#PathVariable("name") String name);
}
To transmit the token through Feign I have this configuration in the client Main class.
#EnableConfigurationProperties
#SpringBootApplication
#EnableFeignClients("com.tutosharing.client.proxies")
public class ClientUiApplication {
#Autowired
private SecurityPropertiesConfig config;
#Bean
protected OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri(config.getAccessTokenUri());
resource.setUserAuthorizationUri(config.getUserAuthorizationUri());
resource.setClientId(config.getClientId());
resource.setClientSecret(config.getClientSecret());
return resource;
}
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
return new OAuth2FeignRequestInterceptor(oauth2ClientContext, resource);
}
}
Now the oauth2 server which also serves as a resource server
#SpringBootApplication
#EnableResourceServer
#EnableAuthorizationServer
#EnableConfigurationProperties
public class AuthorizationServerApplication {}
the oauth2 server WebSecurityConfigurerAdapter class
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers()
.antMatchers("/",
"/login",
"/login.do",
"/oauth/authorize**")
.and()
.authorizeRequests()
.antMatchers(
"/",
"/login",
"/login.do")
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login.do")
.usernameParameter("*********")
.passwordParameter("*********")
.and()
.userDetailsService(userDetailsServiceBean())
.requiresChannel()
.anyRequest()
.requiresSecure();
}
}
The Rest controler method I'd like to allow to anonymous users
#RestController
public class UserRControllerRest {
#GetMapping({"/user/like/{name}"})
#JsonView(View.SimpleUser.class)
#PreAuthorize("hasRole('ROLE_USER')")
public ResponseEntity getUserLikeName(#PathVariable String name) {
Set<AuthUser> users = this.userRepository.findByNameLike(name);
return new ResponseEntity(users, HttpStatus.OK);
}
}
If I configure the Rest method with #PreAuthorize("hasRole('ROLE_ANONYMOUS')")
and the WebSecurityConfigurerAdapter like this
http.requestMatchers()
.antMatchers(
...
"/user/like/**",
...)
.and()
.authorizeRequests()
.antMatchers("/user/like/**")
.anonymous()
...
}
} // #formatter:on
I'm able to get an answer if I contact directly the oauth2-server with Postman, but not if I pass through the client service that uses Feign, I'm always redirected to the login page.
So how can I allow anonymous request Through Feign ?
I've found a solution but I'm not sure this is the Best way. So if you have another solution you are welwome.
So far I used this configuration to get the Token from the oauth2-server anytime an user made a request from the client through Feign.
#Bean
protected OAuth2ProtectedResourceDetails resource() {
AuthorizationCodeResourceDetails resource = new AuthorizationCodeResourceDetails();
resource.setAccessTokenUri(config.getAccessTokenUri());
resource.setUserAuthorizationUri(config.getUserAuthorizationUri());
resource.setClientId(config.getClientId());
resource.setClientSecret(config.getClientSecret());
return resource;
}
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor(#Qualifier("oauth2ClientContext") OAuth2ClientContext oauth2ClientContext,
OAuth2ProtectedResourceDetails resource) {
return new OAuth2FeignRequestInterceptor(oauth2ClientContext, resource);
}
The problem with that configuration is that anytime I made a request with Feign a request is sent to the oauth2-client to the /oauth/authorize endpoint. But if the user is not connected it fails, so an unauthenticated user cannot make any request from the client service.
So I used another RequestInterceptor.
#Bean
public RequestInterceptor requestTokenBearerInterceptor() {
return requestTemplate -> {
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (!principal.equals("anonymousUser")) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)
SecurityContextHolder.getContext().getAuthentication().getDetails();
requestTemplate.header("Authorization", "bearer " + details.getTokenValue());
}
};
}
This way the token that the client service already has, once the user is connected, is added to the request whitout making another request to the /oauth/authorize endpoint. I think the token is sent with every request, I don't think it's a good practice for security matters.
Also in the WebSecurityConfigurerAdapter classes of the client-server I need to add the path so that it is accessible to non-connected users
http.antMatcher("/**")
.authorizeRequests()
.antMatchers(
"/oauth2-server/user/like/**",
...)
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
...;
same for the oauth2-server
http.antMatcher("/**")
.authorizeRequests()
.antMatchers(
"/user/like/**",
...)
.permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
...;
With that configuration an unauthenticated user can make a request to an unprotected endpoint.

Postman showing HTML instead of JSON

I have a simple Spring Boot + Spring Security REST app with quotations. Only 3 endpoints for GET, POST, DELETE. Only moderator and admin accounts defined. GET rest method works fine - it shows list of quotations. The problem is with POST and DELETE methods. When I try to invoke them in Postman it returns HTML (logging form defined in SecurityConfig).
QuotationApi.java
#RestController
public class QuotationApi {
private List<Quotation> quotations;
public QuotationApi() {
this.quotations = new ArrayList<>();
quotations.add(new Quotation("Those who dare to fail miserably can achieve greatly.", "John F. Kennedy"));
quotations.add(new Quotation("Get busy living or get busy dying.", "Stephen King"));
}
#GetMapping("/api")
public List<Quotation> getQuotation() {
return quotations;
}
#PostMapping("/api")
public boolean addQuotation(#RequestBody Quotation quotation) {
return quotations.add(quotation);
}
#DeleteMapping("/api")
public void deleteQuotation(#RequestParam int index) {
quotations.remove(index);
}
}
SecurityConfig.java
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// creating users
#Bean
public UserDetailsService userDetailsService() {
UserDetails moderator = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("MODERATOR")
.build();
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("admin")
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(moderator, admin);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/api").permitAll()
.antMatchers(HttpMethod.POST,"/api").hasRole("MODERATOR")
.antMatchers(HttpMethod.DELETE,"/api").hasRole("ADMIN")
.anyRequest().hasRole("ADMIN")
.and()
.formLogin().permitAll()
.and()
.logout().permitAll()
.and()
.csrf().disable();
}
}
I have Basic_auth in Postman:
EDIT after Andreas's help (working code):
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.GET,"/api").permitAll()
.antMatchers(HttpMethod.POST,"/api").hasRole("MODERATOR")
.antMatchers(HttpMethod.DELETE,"/api").hasRole("ADMIN")
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll()
.and()
.csrf().disable();
}
Doesn't matter that Postman is sending Basic authentication header, when you haven't enabled Basic authentication in Spring.
Since you only called formLogin() to enable form based authentication, you have to login using the form POST.
Of course, you could just call httpBasic() to enable Basic authentication too.

spring security: BadCredendtials exception not handled by the chain and not returning 401

Good morning,
I was following this tutorial, in order to make spring security works with jwt token.
* my environment versions*
spring boot : 2.0.1 RELEASE
Spring security config
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.cors().configurationSource(request -> getCorsConfiguration())
.and()
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), PROTECTED_URLS)
.and()
.authenticationProvider(provider)
.addFilterBefore(restAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(PROTECTED_URLS)
.authenticated()
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
}
#Bean
TokenAuthenticationFilter restAuthenticationFilter() throws Exception {
final TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationManager(authenticationManager());
filter.setAuthenticationSuccessHandler(successHandler());
return filter;
}
#Bean
SimpleUrlAuthenticationSuccessHandler successHandler() {
final SimpleUrlAuthenticationSuccessHandler successHandler = new SimpleUrlAuthenticationSuccessHandler();
successHandler.setRedirectStrategy((HttpServletRequest request, HttpServletResponse response, String url) -> {
// no redirection strategy
});
return successHandler;
}
/**
* Disable Spring boot automatic filter registration.
*/
#Bean
FilterRegistrationBean disableAutoRegistration(final TokenAuthenticationFilter filter) {
final FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
#Bean
AuthenticationEntryPoint forbiddenEntryPoint() {
return new HttpStatusEntryPoint(FORBIDDEN);
}
Context:
Token authentication filter is throwing BadCredentialsException, in case the token does not match expectation.
Expected behaviour:
spring chain will catch this error and return 401 error back to the user.
Current Behaviour:
the server crashes with 500 error. the error is not handled by spring security.

Categories