I have a spring restful application, backend - Spring 2.4.3, frontend - Angular, when I trying to restrict access to individual pages, I get 401 code. I've tried all variations of hasRole () and hasAuthority () nothing helps. What am I doing wrong?
SecurityConfig.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/login", "/registration").permitAll()
.antMatchers("/profile","/profile/*").hasAnyAuthority("USER","ADMIN","INTERVIEWER")
.antMatchers("/getAllUsers").permitAll()
.anyRequest().authenticated();
http
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
/*.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class)*/
.cors();
}
Role.java
#XmlType
#XmlEnum
public enum Role implements GrantedAuthority {
ADMIN,
USER,
INTERVIEWER;
#Override
public String getAuthority() {
return this.name();
}
}
Result:
something wrong :(
Based on your provided code, the line .addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class) is commented out. I can't speak to what happens when you un-comment that line (since it is a custom filter), but without that line, you have no means of authenticating. This results in your entry point (which is not provided in your example) being invoked, and seems to be returning your 401 status code.
You can test this by commenting out the lines:
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
and adding .formLogin().and() instead. Form Login will provide a default authentication entry point, default authentication filter, and (if using spring boot) default user details service with a randomly generated password printed to your console, which you can use to test logging in. See the docs for more info on this.
A note on testing with hello world (out of the box) configuration: It is a very useful technique to use formLogin() for testing authorization rules (e.g. .antMatchers("/profile","/profile/*").hasAnyAuthority("USER","ADMIN","INTERVIEWER")) in Spring Security. It allows you to eliminate your authentication mechanism from being the problem. Once you are confident your authorization rules are working, you can move on to configuring your own authentication scheme. When possible, seek to utilize an existing scheme provided by Spring Security, and only create a custom filter when you cannot use an out of the box scheme. You can read about JWT authentication in the docs.
Related
I have a Spring Boot based application. I want the URL /camunda/app/welcome/default/#!/login to be accessible without any authentication, while the URLs
/camunda/app/welcome/default/#!/welcome,
/camunda/app/welcome/default/#!/dashboard,
/camunda/app/tasklist/**, and
/camunda/app/admin/**
must be secured (i. e. only authenticated users should be able to access them).
To achieve this, I wrote the following configuration:
#Configuration
#EnableWebSecurity
public class MyConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.and()
.authorizeRequests()
.antMatchers("/camunda/app/welcome/default/#!/login").permitAll()
.antMatchers("/camunda/app/welcome/default/#!/welcome",
"/camunda/app/welcome/default/#!/dashboard",
"/camunda/app/tasklist/**",
"/camunda/app/admin/**",
"/oauth2/authorization/**",
"/oauth2/code/myredirecturl")
.authenticated()
.and()
.oauth2Login(...)
.logout()
.logoutRequestMatcher(...)
.logoutSuccessHandler(...);
}
}
However with this configuration unauthenticated users can access URLs that are supposed to be protected (/camunda/app/welcome/default/#!/welcome, /camunda/app/welcome/default/#!/dashboard, /camunda/app/tasklist/**, /camunda/app/admin/**).
What is wrong with my configuration and how can I fix it?
Sadly to say, but that will not work, because there is actually only one url:
/camunda/app/welcome/default/
and parts after '#' symbol are called 'anchors':
#!/welcome,
#!/dashboard,
Anchors are not processed on backend, because they point to some place in html document that was loaded on client side.
https://www.w3docs.com/snippets/html/how-to-create-an-anchor-link-to-jump-to-a-specific-part-of-a-page.html
So you cant solve it by Spring only, there must be some frontend logic.
Also these two masks:
/camunda/app/tasklist/, and
/camunda/app/admin/
could be covered by Spring Boot, because point to different urls, not anchors.
Make sure you use the URL encoding of #, which is %23 when calling the endpoints. Otherwise, the characters after the # will not be considered.
Making a request to /camunda/app/welcome/default/#!/welcome without properly encoding will be interpreted as a request to /camunda/app/welcome/default/. Since that endpoint doesn't require authentication then anyone will be allowed to access it.
Since all endpoints except /camunda/app/welcome/default/#!/login require authentication you condense your HttpSecurity configuration. I'll rewrite it below using the lambda style configuration to make it more readable:
http
// no need to add requestMatchers since you aren't changing the default configuration
.authorizeRequests(authz -> authz
.antMatchers("/camunda/app/welcome/default/#!/login").permitAll()
.anyRequest().authenticated() // any request that does not match the above rule ^ will require an authenticated user
)
.oauth2Login(...)
.logout(...)
I've set up some security filters in my Spring Boot application and I have defined specific URL patterns I want
#Bean
public FilterRegistrationBean<CustomAuthenticationFilter> authenticationFilter(){
FilterRegistrationBean<CustomAuthenticationFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setFilter(new CustomAuthenticationFilter());
registrationBean.addUrlPatterns("/data/*", "/record/*","/records/*","/storage/*","/query/*");
return registrationBean;
}
I'm adding the filter into the WebConfig like so
#Override
public final void configure(HttpSecurity http) throws Exception {
http.addFilterAfter(new CustomAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.antMatcher(ApplicationInfoService.API_PATH + "/info")
.authorizeRequests().anyRequest().permitAll();
}
When I make a request to something like the following;
http://localhost:8080/api/record/v1/record/search?offset=0&count=0
the filter isn't activated. However, I did notice the url in the antMatcher did activiate the filter, so maybe there's something there.
Either way, I'm still trying to grasp how much of the api path the addUrlPattern needs? Where or what else do I need to add beside adding addFilter<Before|After> in the WebSecurityConfig class.
I have taken a look at a whole lot of examples and questions in SO, but none seem to have helped me. Hoping someone can help me understand what else I could be missing.
Javadoc of .antMatcher(...) says
Allows configuring the HttpSecurity to only be invoked when matching the provided ant pattern.
This means that your security is only ever applied to /info requests. All others bypass security.
The code you wrote is the same as:
http.antMatcher(ApplicationInfoService.API_PATH + "/info");
http.addFilterAfter(new CustomAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
http.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
http.authorizeRequests()
.anyRequest().permitAll();
Note that the following are two entirely different things:
http.antMatcher("/foo/**");
http.authorizeRequests()
.antMatchers("/foo").permitAll()
.anyRequest().hasRole("ADMIN");
Without the first, security processes all requests. With it, only requests with path /foo are processed by the security module, while all other requests entirely bypasses security.
The second specifies that requests with path /foo are permitted, and that all other requests requires a user with role ADMIN.
I have been trying to configure Spring Boot security in order to allow some urls without requiring an authentication and not allowing any other requests without an authentication. I am having trouble achieving this.
As per my understanding, anyRequest().authenticated() requires previously declared antMatchers to require authentication.
How is it possible to achieve my requirement.
My Http Security configuration
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.antMatchers(HttpMethod.POST,SIGN_UP_URL).permitAll()
.antMatchers(HttpMethod.GET,banner_top_url).permitAll()
.antMatchers(HttpMethod.GET,banner_bottom_url).permitAll()
.antMatchers(HttpMethod.GET,javascript_url).permitAll()
.antMatchers(HttpMethod.GET,stylesheet_url).permitAll()
.antMatchers(HttpMethod.GET,photos_url).permitAll()
.antMatchers(HttpMethod.GET,transformed_photos_url).permitAll()
.antMatchers(HttpMethod.GET,preview_url).permitAll()
.antMatchers(HttpMethod.GET, "/", "/**/*.html", "/static/favicon.ico", "/**/*.js", "/**/*.js.map", "/**/*.css", "/**/*.png", "/**/*.jpg", "/**/*.jpeg", "/**/*.gif", "/**/*.ttf", "/**/*.json", "/**/*.woff", "/**/*.woff2", "/**/*.eot", "/**/*.svg").permitAll()// allows static content from resource folder
.antMatchers("/error").permitAll() // By default Security framework disables error pages (Unauthrorized)
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().addFilterBefore(jwtExceptionHandler,CorsFilter.class)
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().formLogin().disable();
}
I assume that the below urls must be granted access without authentication.
SIGN_UP_URL
banner_top_url
banner_bottom_url
javascript_url
stylesheet_url
photos_url
transformed_photos_url
preview_url
The problem is this line : .anyRequest().authenticated()
If I remove it, then all the endpoints within the REST interface becomes available without authentication which I do not want.
Why aren't you excluding the static resource files globally via web.ignoring?
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
By default, Spring-security allows to pass everything. You have to tell Spring what can pass and what cannot pass. By removing anyRequest().authenticated you are telling to spring that everything that matches the patterns you mentioned are allowed to go and with the rest do what you do by default, that means, proceed. Here you are Spring Security doc: https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc-httpsecurity
I have found that Spring-Working as intended. That being said,any antMAtchers will match the requestPath and not the resourcePath. An example is provided below.
*localhost:8080/image.jpg*
points at the root of the application which is src/main/resources/static/image.jpg
Now why is static used as a resource handler, that is because in the staticResourceConfiguration.java class I had the following lines
registry
.addResourceHandler("/resources/**")
.addResourceLocations("/resources/");
registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
I am new to spring security and was checking how to authorize requests to URLs in my application.
According to the documentation here, we add authorization as follow:
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/signup", "/about").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated()
.and()
// ...
.formLogin();
}
As this method worked fine for me, I was wondering if there's another dynamic way to specify this configuration. By using some sort of annotations for our REST controllers for example?
I have a solution in mind that would be really practical, but I wanted to make sure that there's no other way to do this before starting to develop my own code.
Thank you for your help.
Yes there is an annotations as #Secured/#PreAuthorize/#PostAuthorize. this annotations are preferred way for applying method-level security, and supports Spring Expression Language out of the box, and provide expression-based access control.
for e.g
#PreAuthorize("hasRole('ADMIN')")
public String yourControllerMethod() {
return response;
}
for detail check here.
The only other way is to use the #Secured/#PreAuthorize/#PostAuthorize annotations. But you must put them on all webservices you want to secure.
Usually, when I build a webservices application, I like to authorize all requests on the WebSecurityConfigurerAdapter, and then secure requests one by one with these annotations.
I want to restrict certain rest endpoints to be only for LDAP users in a certain group.
I followed the guide https://spring.io/guides/gs/authenticating-ldap/ to setup LDAP authentication which is working perfectly. So how do I restrict certain rest endpoints?
I tried
#PreAuthorize("hasRole('developers')")
#RequestMapping("/foo")
public String foo(HttpServletRequest request) {
return "Welcome to FOO " + request.getRemoteUser();
}
but it still lets users not in the developers group access that endpoint
You can modify your WebSecurityConfigurerAdapter configuration to something like:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.and()
.antMatchers("/foo").hasRole("developers")
.and()
.formLogin();
}
I am not exactly sure of the syntax and if that first rule will override your second rule, but it will be similar to that.
Or, you can try configuring security on a method by method basis like this sample.
#EnableGlobalMethodSecurity(securedEnabled=true) needed to be added to the webSecurityConfig. Once I did that I was able to use #Secured("ROLE_DEVELOPERS") and that method was then restricted to that role.