I am programing a Springboot api rest but i have a problem with Spring security.
When i want to Make a request to the server , it throws Unauthorized 401 but i have already configured spring security. Here is the code:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers(HttpMethod.GET, "/characters/**").permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
#Override
#Bean
protected UserDetailsService userDetailsService() {
UserDetails admin= User.builder().username("admin").password(passwordEncoder().encode("123")).roles("ADMIN")
.build();
UserDetails user= User.builder().username("user").password(passwordEncoder().encode("123")).roles("USER")
.build();
return new InMemoryUserDetailsManager(admin,user);
}
}
Request method:
#PreAuthorize("hasRole('ADMIN')")
#RequestMapping(value ="/characters" ,method = RequestMethod.GET)
public List<ImagenNombreDTO> listarPersonajes(){
try {
return personajeService.listarPersonajes();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Make following changes inside antmatchers() method of SecurityConfig.java file.
Add one more entry of "/characters" endpoint like following and see whether the error still persists or not.
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().antMatchers(HttpMethod.GET, "/characters","/characters/**").permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
Related
I'm trying to add security headers to my Spring Boot application.
It already had a Java class with multiple filters extending from WebSecurityConfigurerAdapter. But whenever I try to add the annotation #EnableWebSecurity to this class or even with a new custom one I always receive NullPointerException for the bean springSecurityFilterChain.
Changing the order to add some filters seems to solve this problem but whenever I try to enter the app I can't because it seems the HTTP Authorization header field is null (which I recover inside one of my custom filters).
Do any have a clue of what is happening?
EDIT: After some days of cheking this I noted that the Authorization header was not the problem as the code is built to let that call enter without it and before any change it was already sent without header.
Still with the same call and the changes I'm receiving a 403 FORBIDDEN (before any change this call was receiving 302 FOUND).
This happens before even reaching the controller and I can only get debugging until the filter.
As there were no other changes in the code except the #EnableWebSecurity and the way to add one filter I suspect the problem is around here but i can't find what is causing it exactly.
EDIT: I'm adding the code in case anyone need to see it.
This is the class that has the multiple filters:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity //ADDED THIS ONE
public class MultipleEntryPointsSecurityConfig {
#Configuration
#Order(1)
public class OauthSecurityAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2RestTemplate restTemplate;
#Bean
public CustomFilterOneFilter customFilterOneFilter() {
final CustomFilterOneFilter filter = new CustomFilterOneFilter ("/testLogin");
filter.setRestTemplate(restTemplate);
return filter;
}
#Bean
public FilterRegistrationBean<OAuth2ClientContextFilter> oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter) {
FilterRegistrationBean<OAuth2ClientContextFilter> registration = new FilterRegistrationBean<OAuth2ClientContextFilter>();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.antMatcher("/login")
.cors()
.and()
.csrf().disable()
//CHANGED THIS
// .addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
//FOR THESE TWO
.addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(openIdConnectFilter(), OAuth2ClientContextFilter.class)
.httpBasic()
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/testLogin"))
.and()
.logout()
.logoutSuccessUrl("/logout")
.permitAll()
.and()
.csrf().disable()
.authorizeRequests()
.anyRequest().authenticated();
// #formatter:on
}
}
#Configuration
#Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public JwtSecurityFilter authenticationJwtTokenFilter() {
return new JwtSecurityFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/**")
.cors()
.and()
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/**").authenticated();
http
.addFilterAfter(new UsernamePasswordAuthenticationFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
//CHANGED THE BELOW ONE FOR THE TWO ABOVE
//http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
#Configuration
#Order(3)
public static class PublicConfigurationAdapter extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/**").permitAll()
.antMatchers("/api/v1/login/**").permitAll();
}
}
}
And this is the custom filter where I try to recover the Authorization header:
#Component
public class JwtSecurityFilter extends OncePerRequestFilter{
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
String authHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
//FAILS HERE!
if(authHeader == null || !authHeader.startsWith("Bearer ")) {
SecurityContextHolder.getContext().setAuthentication(null);
chain.doFilter(request, response);
return;
}
...
}
}
Edit :
Thx Thomas Andolf !
It works when i use embended tomcat in springboot 'spring i launched on IntelliJ and the angular part with visual studio code.
But it does not work when i publish the war in provided tomcat on my raspberry pi...
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests(authorizeRequests ->
authorizeRequests.antMatchers(HttpMethod.POST, "/rest/gender").permitAll()
.antMatchers(HttpMethod.POST, "/rest/login").permitAll()
.antMatchers(HttpMethod.POST, "/rest/names").permitAll()
.anyRequest().authenticated()
)
.httpBasic()
.authenticationEntryPoint(authEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
The angular part of the project is published in tomcat/webapps/ROOT.
The war is published in tomcat/webapps/baby-project-api.
I use tomcat/conf/Catalina/localhost/rewrite.config like this :
RewriteRule ^/rest/(.+)$ /baby-project-api/rest/$1
Original Question
I try to use Basic Authentication on an api with spring boot security and i need some path to be not secured.
POST /rest/login is not secured with the config,
GET /rest/gender is secured and that's what i want
Any idea why POST /rest/gender is still secured ?
There is my WebSecurityConfig :
#Configuration
#EnableAutoConfiguration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationEntryPoint authEntryPoint;
#Autowired
private IParentRepository parentRepository;
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/rest/gender").permitAll()
.antMatchers(HttpMethod.POST, "/rest/login").permitAll()
.antMatchers(HttpMethod.POST, "/rest/names").permitAll()
.anyRequest().authenticated()
.and().httpBasic()
.authenticationEntryPoint(authEntryPoint);
//.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
final List<Parent> parents = parentRepository.findAll();
InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> mngConfig = auth.inMemoryAuthentication();
for (Parent parent : parents) {
mngConfig.withUser(User.withUsername(parent.getUsername()).password(parent.getPassword()).roles("ADMIN").build());
}
}
}```
POST /rest/login is not secured with the config,
GET /rest/gender is secured and that's what i want
Any idea why POST /rest/gender is still secured ?
can you please try doing it the way they actually do it in the documentation and see if it works.
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests(authorizeRequests ->
authorizeRequests.antMatchers(HttpMethod.POST, "/rest/gender").permitAll();
authorizeRequests.antMatchers(HttpMethod.POST, "/rest/login").permitAll();
authorizeRequests.antMatchers(HttpMethod.POST, "/rest/names").permitAll();
authorizeRequests.anyRequest().authenticated();
)
.httpBasic()
.authenticationEntryPoint(authEntryPoint);
}
After all, i did not find a great solution by this way.
i open all the api and restricted some parts with pre-auth :
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().permitAll()
.and().httpBasic()
.authenticationEntryPoint(authEntryPoint)
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
And on the controller :
#RestController
#PreAuthorize("isAuthenticated()")
#RequestMapping("/rest/gender")
public class GenderController {
[...]
// protected by the # on the class
#GetMapping(value = "")
public List<Gender> listerGender(final SecurityContextHolderAwareRequestWrapper request){
return genderService.listerGender(request);
}
#PreAuthorize("permitAll()")
#PostMapping(value = "", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> creerGender(#Valid #RequestBody Gender gender){
return this.genderService.creerGender(gender);
}
I think we can make it cleaner but at least it works
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.
I am trying to implement the impersonate using SwitchUserFilter in Spring but I'm getting an error. The project runs good without this implementation. Also the project is using Java annotations not xml configuration and has SecureAuth authentication. And the parts involved in the code into the SecurityConfig class is:
#Configuration
#ComponentScan(basePackages = {"com.project.*"})
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
#PropertySource("classpath:app.properties")
#Import({TransactionManagersConfig.class, MailConfig.class})
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private SwitchUserFilter switchUserFilter;
#Autowired
protected AuthenticationSuccessHandler authenticationSuccessHandler;
#Bean
public UserDetailsService userDetailsServiceBean() {
try {
return super.userDetailsServiceBean();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
#Bean
public SwitchUserFilter switchUserFilter() {
SwitchUserFilter switchUserFilter = new SwitchUserFilter();
switchUserFilter.setUserDetailsService(userDetailsServiceBean());
switchUserFilter.setUsernameParameter("username");
switchUserFilter.setSwitchUserUrl("/switch");
switchUserFilter.setExitUserUrl("/exit");
switchUserFilter.setTargetUrl("/");
return switchUserFilter;
}
//more beans
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.headers().disable();
http //SAML CONFIG
.httpBasic()
.authenticationEntryPoint(samlEntryPoint()).and()
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
http //DISABLE CROSS-SITE REQUEST FORGERY
.csrf()
.disable();
//Impersonate Interceptor
http
.addFilterAfter(switchUserFilter(), FilterSecurityInterceptor.class);
http
.authorizeRequests()
.antMatchers("/impersonate").permitAll()
.antMatchers("/api/**").permitAll()
.antMatchers("/#/**").permitAll()
.antMatchers("/switch").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/index")
.permitAll().successHandler(authenticationSuccessHandler);
http
.logout().logoutSuccessUrl(env.getProperty("realm.url.restart"));
http
.exceptionHandling().accessDeniedPage("/error?code=403&error=Access Denied&detail=You are not authorized to access.");
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(samlAuthenticationProvider());
}
#Override
public void configure(WebSecurity webSecutity) throws Exception {
webSecutity
.ignoring().antMatchers("/resources/**");
}
}
Error:
java.lang.IllegalStateException: UserDetailsService is required.
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter$UserDetailsServiceDelegator.loadUserByUsername(WebSecurityConfigurerAdapter.java:393)
at org.springframework.security.web.authentication.switchuser.SwitchUserFilter.attemptSwitchUser(SwitchUserFilter.java:209)
at org.springframework.security.web.authentication.switchuser.SwitchUserFilter.doFilter(SwitchUserFilter.java:155)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at
My url stops on:
http://localhost:8080/switch?j_username=angel_cuenca
If you need more part of the code, pleasure to share.
Can you try to set the userDetailsService implementation to the configuration, like in this ?
I don't see in your configuration:
auth.userDetailsService(userService);
I want to have a custom Authentication Provider for spring security and i have implemented it like this
#Component
public class ApiCustomAuthenticationProvider implements AuthenticationProvider {
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("ahsgdvjasdhgjasjdh");
return new UsernamePasswordAuthenticationToken("aman", "12345");
}
#Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}
Right now i don't have any logic as i just want to see if spring security is actually using this authentication provider .
i have my security config file as
#Configuration
#EnableWebSecurity
//#ImportResource("classpath:/security/spring_saml_sso_security.xml")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/*#Autowired
MetadataGeneratorFilter metadataGeneratorFilter;
#Autowired
FilterChainProxy samlFilter;
#Autowired
SAMLEntryPoint samlEntryPoint;
*/
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
try {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/static/**").permitAll()
.antMatchers("/settings/api/**").permitAll()
.antMatchers("/api/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.loginProcessingUrl("/login")
// .usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("/index",true)
.and()
.httpBasic();
// .defaultSuccessUrl("/", true);
} catch (Exception e) {
// TODO Auto-generated catch block
System.out.println("sadhiasdniaaaaaaaaaaaaaaaa:");
e.printStackTrace();
}
}
#Bean
public ApiCustomAuthenticationProvider apiCustomAuthenticationProvider() {
return new ApiCustomAuthenticationProvider();
}
}
i want to know if this
#Bean
public ApiCustomAuthenticationProvider apiCustomAuthenticationProvider() {
return new ApiCustomAuthenticationProvider();
is the correct way of telling spring security to use the custom authentication manager .
You need to add this in Spring security config:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new ApiCustomAuthenticationProvider());
}
or
auth.authenticationProvider(apiCustomAuthenticationProvider())
And as a reminder, if you return token :
UsernamePasswordAuthenticationToken("aman", "12345"),
spring will not give authorization to user. Instead you need to assign role :
List<GrantedAuthority> grantedAuths = new ArrayList<GrantedAuthority>();
grantedAuths.add(new SimpleGrantedAuthority("ROLE_USER"));
UsernamePasswordAuthenticationToken("aman", "12345",grantedAuths) ;
As stated above,you are giving user ROLE_USER and then user can use all authenticated page.
Hope its help.