We have a simple application with only two consumers and 5 endpoints. For one endpoint I need some way of authentication. I like the stripe way of doing this, but I don't know how I can build this in spring boot.
"Authentication to the API is performed via HTTP Basic Auth. Provide your API key as the basic auth username value. You do not need to provide a password."
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/qr")
.hasRole("user")
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.and()
.csrf()
.disable();
}
#Bean
public UserDetailsService userDetailsService() {
val encodedPassword = new BCryptPasswordEncoder().encode("test");
final InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("admin").password(encodedPassword).roles("user").build());
//manager.createUser(User.withUsername("admin").roles("user").build());
return manager;
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
I tried to remove the password from manager.createUser but this doesn't work.
Basic authentication is made of user:password in base64 encoded form.
So your user must have a password equal to empty string for Basic Authentication to work.
You can also get rid of BCryptPasswordEncoder and use NoOpPasswordEncoder since you don't use the password value.
Related
In my Spring Boot (v. 2.7.0) app, I'm using Spring Security for authentication. If a user attempts to login with invalid credentials, the server responds with a 403 (Forbidden) status code, but I would expect 401 (Unauthorized) to be used instead.
I can't find anything in my configuration that indicates the default behaviour of Spring Security has been overriden, but whether it has or not, I want 401 to be returned when authentication fails.
I stepped through the relevant Spring Security code and it appears the Spring Security
method SimpleUrlAuthenticationFailureHandler.onAuthenticationFailure is called when authentication fails. This method includes the line
response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
But for some reason 403 is returned to the client - so I guess the response is being changed subsequent to the line above.
How can I change Spring Security to return 401 when authentication fails? I've included my security configuration below for reference.
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration {
#Autowired
private AuthenticationConfiguration authenticationConfiguration;
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
var jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager);
var jwtAuthorisationFilter = new JwtAuthorisationFilter();
http.cors().and().csrf().disable().authorizeRequests()
.anyRequest().authenticated().and()
.addFilter(jwtAuthenticationFilter)
.addFilterAfter(jwtAuthorisationFilter, BasicAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
The JwtAuthenticationFilter calls authenticationManager.authenticate which causes a org.springframework.security.authentication.BadCredentialsException to be thrown if the credentials are invalid.
Update
I tried adding a custom AuthenticationFailureHandler bean as described in this article, but my custom bean is never invoked (the default bean SimpleUrlAuthenticationFailureHandler is called instead).
You can provide your own custom AccessDeniedHandler implementation.
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
var jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager);
var jwtAuthorisationFilter = new JwtAuthorisationFilter();
http.cors().and().csrf().disable().authorizeRequests()
.anyRequest().authenticated().and()
.addFilter(jwtAuthenticationFilter)
.addFilterAfter(jwtAuthorisationFilter, BasicAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.accessDeniedHandler( (request, response, exception) ->
response.sendError(HttpStatus.UNAUTHORIZED.value(), exception.getMessage()
));
return http.build();
}
I have this error related to Spring security that I cannot fix, and it is happening only though the browser. When I try to replicate it through postman, it suddenly works and everything's fine.
I authorized unauthenticated requests for /login, and authenticated requests to the rest of the API.
Therefore when I make a call to /login, it does let me authenticate.
But, when I make a call to any of my API endpoints that is not /login, and I'm already authenticated, my backend returns this strange error:
I don't really know what this error means.
However, when I do the same process through Postman, it works and the endpoint returns what it is supposed to return.
Login API call:
Another API call that is not login:
As you can see, through Postman it does work well.
But doing the exact same through the Angular UI I'm developing leads to the former error.
My Spring security configuration:
#Configuration
#AllArgsConstructor
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String FRONTEND_URL = "http://localhost:4200/";
private final UserService userService;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/v*/registration/**", "/login-status/**")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll()
.loginPage(FRONTEND_URL + "/login")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/login-status/success", true)
.failureUrl("/login-status/failure")
.and()
.logout()
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setPasswordEncoder(bCryptPasswordEncoder);
provider.setUserDetailsService(userService);
return provider;
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList(FRONTEND_URL));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
I hope you could help me cause I'm really stuck with this. I'm a front-end dev who is trying to find my ways to develop a backend, just to learn, but man, shits hard. Thank you very much, and I hope my bad English didn't hurt your eyes too much. Thank you for your help.
I would like to configure web security layer based on my request type.
If the request starts with /rest then it should use Basic authentication with stateless session management and for login authentication then it should use CSRF with stateful session management.
I have tried below code.
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.authorizeRequests()
.antMatchers("/rest/**").hasRole("SUPER_ADMIN")
.anyRequest().fullyAuthenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.formLogin().and().logout().permitAll();
}
It works with basic authentication but it doesn't work for the login request because the session is not stateful. Can anyone please help me to configure Spring security. I am new to Spring security.
You need
1. Rest API's to be authenticated by basic authentication
2. Your web application be authenticated by form login.
And authorization is other part in both cases that you can set it as per your requirement.
Let me explain what was wrong with your approach. By your approach you can achieve only one authentication entry point from one configuration. i.e, you can't achieve multiple authentication entry point.
Now coming to your first requirement of achieving multiple authentication entry point.
1. For Rest API resources -- authentication by HttpBasicAuthentication for antMatcher /rest/**
2. For WebApp resources -- authentication by Form Login for antMatcher other than /rest/**
To achieve this
1. You need to have implementation of WebSecurityConfigurerAdapter of different configuration order and different antMatcher patterns.
2. Order of each configuration is important.
- wildcard pattern(/**) should be placed last order
- non wildcard pattern or restricted pattern(/rest/**) should be placed first order
3. As those configuration classes are static and inner classes for a class which is annotated #EnableWebSecurity you should be careful while defining bean using #bean and autowiring using #Autowired.
Note:
Most of people makes mistake by not defining antmather for authorizeRequest()
If first configuration #Order(1) class is configured as below
http.authorizeRequests()
2nd configuration will become dead configuration because
http.authorizeRequests() => http.antMatcher("/**").authorizeRequests()
And all URL's will be configured only for first configuration only.
Refer code given below for better understanding.
#Configuration
#EnableWebSecurity
public class SpringSecurityConfiguration
{
#Bean
public PasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
#Configuration
#Order(1)
public static class BasicAuthSecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
public void configureInMemoryAuthentication(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication()
.withUser("superadmin")
.password(passwordEncoder.encode("superadmin#123#"))
.roles("SUPER_ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
.antMatcher("/rest/**")
.authorizeRequests()
.antMatchers("/rest/**").hasRole("SUPER_ADMIN")
.and().httpBasic();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
#Configuration
#Order(2)
public static class LoginFormSecurityConfig extends WebSecurityConfigurerAdapter
{
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
public void configureInMemoryAuthentication(AuthenticationManagerBuilder auth) throws Exception
{
auth.inMemoryAuthentication()
.withUser("user")
.password(passwordEncoder.encode("user#123#"))
.roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http
.antMatcher("/**") //wild card i.e, allow all (But already /rest/** is filtered by 1st config)
.authorizeRequests()
.antMatchers("/resources/**").permitAll()
.antMatchers("/**").authenticated()
.and().formLogin()
.defaultSuccessUrl("/app/user/dashboard")
.and().exceptionHandling()
.accessDeniedPage("/403")
.and().logout()
.invalidateHttpSession(true);
http.sessionManagement().maximumSessions(1).expiredUrl("/login?expired");
}
}
}
This question has requirement of different sets of URL's(/rest/** and other than /rest/**) for different authentication filters. Here user's (for both basic auth and form login) may be authenticated against a single table (say user_details) or multiple tables (say api_users and web_users)
If you have requirement like there is no different set of URL's but two sets of users say customer and employees(staff) both are accessing same application but they needs to be authenticated against different tables(say users and customer table) in that case refer my another answer Spring Security user authentication against customers and employee table
You have to allow users to access login page without authentication and same you can do with static pages. See below configuration.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
I am using spring boot 2.1.4 with dependencies of actuator. I wanted to configure separate authentication and authorization mechanisms for actuator and my application. I read the Multiple HttpSecurity and configured my WebSecurityAdapter as follows:
#Configuration
public class ProvisioningServiceSecurityConfiguration {
#Value("${actuator.user.name}")
private String actuatorUserName;
#Value("${actuator.password}")
private String actuatorPassword;
#Value("${actuator.role}")
private String actuatorRole;
#Bean
public UserDetailsService userDetailsService() throws Exception {
// ensure the passwords are encoded properly
UserBuilder users = User.withDefaultPasswordEncoder();
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(users.username("user").password("password").roles("ADMIN").build());
manager.createUser(
users.username(actuatorUserName).password(actuatorPassword).roles(actuatorRole).build());
return manager;
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/locations/**")
.antMatcher("/organizations/**")
.antMatcher("/productTypes/**")
.authorizeRequests()
.anyRequest().hasRole("ADMIN")
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class ActuatorWebSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/manage/**")
.authorizeRequests()
.anyRequest().hasRole("ACTUATOR_ADMIN")
.and()
.httpBasic();
}
}
/*#Configuration
public static class FormLoginWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin();
}
}*/
}
Note: I have disabled form Login temporarily
When I run a curl request
curl -XGET http://localhost:9797/provisioningService/organizations/all
I am able to see the output. Its as though the spring security never existed. When I enable form login, I get the spring login screen. The other behavior that I observed is if I interchange the username and password of /locations with the actuator username and password, I still get a valid response back.
I understand the form login is more of a fallback but I want to disable the form login (probably we may move to cas) and use authentication and authorization only based on the spring security httpBasic. I am not able to understand the mistake I am making.
My requirement is finally :
1) a request to /organizations or /locations etc should be accessible only if the username password is "user" and "password"
2) a request to /manage which is the actuator api should be accessible only if the username and password and role matches with the actuator username and password.
3) Any other API can be permitAll / form login
How do i go about achieving this?
1) Spring Security has a function to control access by filtering by Authorities(after Authentication), but there is no function to filter by the information required for login. You need business logic to verify that you are attempting to log in with the corresponding ID and password during login.
2) As mentioned above, access control with ID and password is not provided.
I recommend creating Authorities for only the two accounts you requested.
3) .antMatcher("/form").permitAll()
Preconditions
I have two Java Spring applications(App 'A' and App 'B') that were created via JHipster(monolithic application). Both applications uses keycloak for authentication/authorization.
Both applications have an angular frontend and support login via ouath (spring-security). Here ist my SecurityConfiguration of Application A and B:
#Configuration
#Import(SecurityProblemSupport.class)
#EnableOAuth2Sso
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
public SecurityConfiguration(CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
}
#Bean
public AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler() {
return new AjaxLogoutSuccessHandler();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
return new SecurityEvaluationContextExtension();
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**")
.antMatchers("/h2-console/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(corsFilter, CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler())
.permitAll()
.and()
.headers()
.frameOptions()
.disable()
.and()
.authorizeRequests()
.antMatchers("/api/profile-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/v2/api-docs/**").permitAll()
.antMatchers("/swagger-resources/configuration/ui").permitAll()
.antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesConstants.ADMIN);
}
}
In App B i also have an ResourceServerConfiguration. This checks if the header contains an "Authorization" key. If true, the user can login via JWT(Bearer Authentication). I tested this via Postman and it works fine:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(final HttpSecurity http) throws Exception {
http
.requestMatcher(new RequestHeaderRequestMatcher("Authorization")).authorizeRequests()
.anyRequest().authenticated();
}
}
Further more both apps are in the same keycloak realm and have the access-type "public".
Problem:
Now i want to call an endpoint of App B via an Spring RestTemplate from App A. The problem is, that i do not have an access_token that i can put in my rest request/restTemplate. When i look in my request that is send from my frontend, i only got an JSESSIONID. There is no access_token/JWT in the header.
Question
Is there a way to get the access_token of the current user out of the JSESSIONID/the HttpSession or the spring security context? Do i need something like a Tokenstore where i store every token that comes from keycloak?
Did anyone else have similar problems or any idea how i could solve that problem?
After some research it turns out that the problem lies within the generated jhipster code.
I followed the authentication process in the application and saw, that there was a call to the /account endpoint directly after authentication, where the user information were retrieved. The call is triggerd by the frontend. First time this endpoint is called, there is a principal with a bearer token available. Within the /account endpoint, a call to the userService with the principal object is performed. More precisley
getUserFromAuthentication(OAuth2Authentication authentication)
is called. Within this method there is a part that replaces the OAuth2Authentication with a new UsernamePasswordAuthenticationToken and inserts it into the SecurityContext:
UsernamePasswordAuthenticationToken token = getToken(details, user,
grantedAuthorities);
authentication = new OAuth2Authentication(authentication.getOAuth2Request(), token);
SecurityContextHolder.getContext().setAuthentication(authentication);
So after that, the access_token is lost. I am not quite sure, why it was replaced with the new OAuth2Authentication, but i tend to extend this part and keep the access_token in my securityContext for further restcalls.