I have multiple WebSecurityConfigurerAdapter's in my code, each resides in a different class.
We are migrating to spring-security 5.2, and as a result, we should remove #EnableResourceServer and replace it with oauth2RespourceServer DSL way.
First WebSecurityConfigurerAdapter is called CommonWebSecurityConfig, and it includes many ant-matchers that don't have any shared prefix:
#Configuration
#EnableWebSecurity
#ComponentScan
#Order(Ordered.HIGHEST_PRECEDENCE)
public class CommonWebSecurityAutoConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
// .requestMatchers()
// .antMatchers("/* Should I add all (long) list of matchers here?*/")
// .and()
.authorizeRequests()
.antMatchers(GET, "/health").permitAll()
.antMatchers("/loggers/**").hasAuthority("scope1")
.antMatchers("/info/**").hasAuthority("scope2")
.antMatchers("/metrics/**").hasAuthority("scope3")
.antMatchers("/configurations/**").hasAuthority("scope4")
.antMatchers("/odata.svc/**").hasAuthority("scope5")
.antMatchers("/dataCenters/**").hasAuthority("scope6")
.antMatchers("/metadata/v1**").hasAuthority("scope7")
...
...
...
...
...
... // list goes on
...
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(getJwtAuthoritiesConverter());
}
Second WebSecurityConfigurerAdapter is AccountsWebSecurityConfig meant to be the second to be validated against. I.e. if a request did not match any ant matcher in CommonWebSecurityConfig, it should be validated against AccountsWebSecurityConfig
#Configuration
#EnableWebSecurity
#ComponentScan(basePackages = "com.xyz..**.security")
#Order(AFTER_COMMON)
public class AccountsWebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
// .requestMatchers()
// .antMatchers("/accounts/**")
// .and()
.authorizeRequests()
.mvcMatchers("/accounts/v1/go").hasAuthority("scope33")
...
... // list goes on
...
.and()
.oauth2ResourceServer()
.jwt()
.jwtAuthenticationConverter(getJwtAuthoritiesConverter());
}
My questions:
Requests are not being validated against AccountsWebSecurityConfig: any request that starts with "accounts" is granted access!
When I uncomment the 3 lines at the beginning of configure() in both classes, it start to work.
This is a different behavior to what we had prior to spring-security 5.2: we did not have to add requestMatchers to make it work. Is this a new requirement?
If yes, then do I have to add requestMatchers to all antMatchers? what if I have so many of them that don't share a prefix, as in CommonWebSecurityConfig?
Do I have to add and().oauth2ResourceServer() to both of them?
Related
I'm trying to make a web application that uses:
SpringBoot,
Mysql,
JDBC
, MVC, DAO
Thymeleaf,
IntelliJ
And I'm trying to figure out how Spring security works (which I'm having a lot of difficulty with).
My views are organized as follows:
resources(folder): - ________static(folder)
|____templates(folder):__________images(folder)
|___userOnly(folder):_____header.html
| |__help.html
| |__menu.html
| |__newDocForm.html
| |__profil.html
|
|__firstPage.html
|__header.html
|__home.html
|__index.html
|__inscriptionForm.html
|__loginPage.html
I would like to do that unidentified users can access all views except those contained in "userOnly" and that my "loginPage" page is used as the login page.
If I understood correctly, I must create a class that inherits from "WebSecurityConfigurerAdapter".
What I have done.
And then configure "configure", which I can't do correctly :(
#Configuration
#EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/userOnly/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/loginPage.html");
}
}
Sorry if my questions seems strange but english is not my first language
As of Spring-Boot 2.7 the use of WebSecurityConfigurerAdapter is deprecated. If you're using Spring-Boot 2.6 or older the other answers might suit you better.
To my best knowledge the recommended way for defining security config in Spring-Boot 2.7 is as follows:
#Configuration
public class WebSecurityConfig
{
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception
{
// #formatter:off
http.authorizeRequests()
.mvcMatchers("/userOnly/**").permitAll()
.anyRequest().permitAll();
http.formLogin()
.permitAll()
.loginPage("/loginPage.html");
http.logout()
.permitAll();
// #formatter:on
return http.build();
}
}
The use of web.ignoring() in the answer from voucher_wolves is, I believe, not recommended, instead one should add those cases to http.mvcMatcher().permitAll().
On a side note, I would personally recommend whitelisting the public pages and adding authentication to everything else, (for example a public folder). This way if you forget to add security to a link it's not public by default.
You need to tell Spring security what URL are public with something like this -
#Configuration
#EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
private static final String[] PUBLIC_URLS = {"/public/*"};
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/userOnly/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/loginPage.html");
}
#Override
public void configure(WebSecurity web) {
List<RequestMatcher> matchers =
Arrays.asList(urls).stream().map(url -> new
AntPathRequestMatcher(url)).collect(Collectors.toList());
web.ignoring().requestMatchers(new OrRequestMatcher(matchers));
}
}
With OrRequestMatcher , you can create list of all URLs which you need to be public.
You can also use NegatedRequestMatcher to get all the private URL
RequestMatcher privateUrlMatcher = new
NegatedRequestMatcher(publicUrlMatcher);
I also suggest you to keep all public url under /src/main/resources/static/publicui and all private under /src/main/resources/static/privateui and have public permission for /publicui/*
try the following in your SecSecurityConfig class
#Configuration
#EnableAutoConfiguration
#EnableWebSecurity
public class SecSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/users").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.usernameParameter("email")
.defaultSuccessUrl("/lib/allBooks")
.permitAll()
.and()
.logout().logoutSuccessUrl("/lib").permitAll();
http
.csrf().disable();
}
}
Just modify the parameters set for your application. if you don't have login form yo can skip
.usernameParameter("email")
.defaultSuccessUrl("/lib/allBooks")
.permitAll()
I am trying to develop Spring Security project with JWT.
I want access Login api with out Spring Security (without JWT token). But with below configuration, every time (for login api as well) it is checking for JWT token giving me 403 error.
Below is my WebSecurityConfig.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthFilter jwtAuthFilter;
#Autowired
private TokenAuthenticationService jwtAuthenticationProvider;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jwtAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/api/v1/login");
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/api/v1/login")
.permitAll()
.and()
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
}
}
Thanks in advance
For login path configuration something like this can be used:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin()
.usernameParameter("username") // default is username
.passwordParameter("password") // default is password
.loginPage("/authentication/login") // default is /login with an HTTP get
.failureUrl("/authentication/login?failed") // default is /login?error
.loginProcessingUrl("/authentication/login/process"); // default is /login
// with an HTTP
// post
}
If some paths need to be ignored configure(WebSecurity web) can be overridden:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/v1/somepath").antMatchers("/static/**");
}
There is filter class named JwtAuthFilter that is being executed before every service you call.
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
this code provides to be executed filter before every request, but its okay, you have to see this FilterClass there must be some check if token doesnt exist filter class must be returned and request will directly go to the login service. if you can show that Filter class and I will help you.
My spring boot application has an Application class. When I run it (as an application), it launches itself within an embedded servlet container (Tomcat, in my case). Somehow (through Application's #annotations, I suppose), WebSecurityConfig (extending WebSecurityConfigurerAdapter) in the same package is loaded.
WebSecurityConfig contains two important blocks of configuration information:
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
#EnableGlobalMethodSecurity(prePostEnabled = true) // enables method-level role-checking
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userSearchBase("CN=Users,DC=some,DC=domain,DC=com")
.userSearchFilter("(sAMAccountName={0})")
.groupSearchBase("OU=Groups,DC=some,DC=domain,DC=com")
.groupSearchFilter("(member={0})")
.contextSource()
.managerDn("cn=ad-bind,cn=users,dc=some,dc=domain,dc=com")
.managerPassword("APASSWORD!")
.url("ldaps://some.domain.com:636");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("***************** WebSecurityConfig.configure *************************");
http.csrf().disable();
http
.headers()
.frameOptions()
.disable();
http
.authorizeRequests()
.antMatchers("/resources/images/*", "/me", "/products", "/product/**", "/offerings", "/offering/**", "/client/**")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").defaultSuccessUrl("/me")
.permitAll()
.and()
.logout()
.permitAll();
http.logout().logoutSuccessUrl("/me");
}
}
configureGlobal() contains the configuration for our internal LDAP system and it works just fine.
configure() specifies which URLs are public, which are only to be shown to logged-in users and which relative URLs to send users to as they log in.
Now I'm into integration testing and have written some methods to test controllers that do not require authentication. Those tests work as expected. The Application class fires up and the tests execute against it.
But now I want to test controller methods that DO require authentication. The way I think this is accomplished is by telling the test class to fire up an alternative Application class (TestApplication, in my case) and WebSecurityConfig that creates dummy users:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = TestApplication.class) // fires up with TestApplication.class instead of Application.class
#WebAppConfiguration
public class ProductControllerTests {
// test methods here, this time with username/password included
}
#Configuration
#EnableAutoConfiguration
public class TestApplication extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(applicationClass, args);
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass);
}
private static Class<TestApplication> applicationClass = TestApplication.class;
}
#Configuration
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("testuser").password("userpass").roles("USER");
auth.inMemoryAuthentication().withUser("testadmin").password("adminpass").roles("ADMIN");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("***************** WebSecurityConfig.configure *************************");
http.csrf().disable();
http
.headers()
.frameOptions()
.disable();
http
.authorizeRequests()
.antMatchers("/resources/images/*", "/me", "/products", "/product/**", "/offerings", "/offering/**", "/client/**")
.permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").defaultSuccessUrl("/me")
.permitAll()
.and()
.logout()
.permitAll();
http.logout().logoutSuccessUrl("/me");
}
}
So my question is: When I execute the unit test class, I believe TestApplication is firing. However, it is NOT picking up the alternative WebSecurityConfig class and its auth.inMemoryAuthentication() test users. How do I force my application to use one WebSecurityConfig when running the application normally, but a different WebSecurityConfig when running the unit tests?
You can configure your TestApplication to include just the beans that you would like to test. In other words, make sure that your WebSecurityConfig is not part of the test configuration. If you read the javadoc of #SpringBootApplication you will notice that it is a composite annotation that consists of (among others) the #ComponentScan annotation. Consequently your Application and your TestApplication will perform a recursive scan from the package in which the class is located. The Spring reference docs has a specific chapter about Using filters to customize scanning.
Alternatively, if you are using Spring Security version 4 or greater you may find the additions of #WithMockUser and #WithUserDetails interesting.
In your security configuration class, add #Profile annotation to disable in unit test profile. like:
#Configuration
#Profile("!" + Constants.SPRING_PROFILE_UNITTEST)
public class WebSecurityConfig { ....}
And let your another security config for test just in test dir.
I have an App that is using OAuth2 password grant type to manage the user authorizations to his resources. All App resources are only allowed access for a client with once provided token to act on behalf of some user, except the URI to create users, this one I want that only authenticaed clients have access to it. I'm using spring-security-oauth2 as my OAuth implementation and but can't figure out how to accomplish this in a less hacky way than the one described bellow:
POST /users to be acessed only by authenticated clients.
Currently I figured out how to this by removing #EnableAuthorizationServer and creating a new class and extending AuthorizationServerSecurityConfiguration class and overriding method: configure( HttpSecurity http ) and creating a new #Configuration class and #Import AuthorizationServerEndpointsConfiguration and CustomAuthorizationServerSecurityConfiguration.
The problem is that, in my new custom class I need to override and copy/paste the entire method original code in the overrided method, ending with something like:
#Override
protected void configure( HttpSecurity http ) throws Exception {
AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
configure(configurer);
http.apply(configurer);
String tokenEndpointPath = handlerMapping.getServletPath("/oauth/token");
String tokenKeyPath = handlerMapping.getServletPath("/oauth/token_key");
String checkTokenPath = handlerMapping.getServletPath("/oauth/check_token");
http
.authorizeRequests()
.antMatchers(tokenEndpointPath).fullyAuthenticated()
.antMatchers( HttpMethod.POST, "/users/**").fullyAuthenticated()
.antMatchers(tokenKeyPath).access(configurer.getTokenKeyAccess())
.antMatchers(checkTokenPath).access(configurer.getCheckTokenAccess())
.and()
.requestMatchers()
.requestMatchers( new AntPathRequestMatcher(tokenKeyPath),
new AntPathRequestMatcher(tokenEndpointPath),
new AntPathRequestMatcher(checkTokenPath),
new AntPathRequestMatcher("/users/**", HttpMethod.POST.name()));
http.setSharedObject(ClientDetailsService.class, clientDetailsService);
}
My first question is, the a better way to do this?
The second thing that I want to do is to auto create the AccessToken by password grant type when a new user is created (in the URI POST /users), and I can't figure any way to do this.
Can someone provide any insight on this two needs?
Thanks
Not sure if this is what you are asking but what I understad is that you want
to configure specific security constrains for request on /users endpoint with POST method. so
this is how I would do this.I do not think that extending
AuthorizationServerSecurityConfiguration is neccesary since recomended way
is usually to extend just WebSecurityConfigurerAdapter in your main
security config class, remember that you can configure your HttpSecurity multiple times for multiple endpoints, but if you configure the same endpoint in multiple places the last configuration read will be the one active
#EnableWebSecurity public class SecurityConfiguration extends
WebSecurityConfigurerAdapter {
//other methods ...
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws
Exception {
return super.authenticationManagerBean();
}
#Order(1)
#Override
protected void configure(HttpSecurity http) throws Exception {
//configure your path here
//I purposly configured GET user to
// permit all to see diference
//for example
// #formatter:off
http
.authorizeRequests()
.antMatchers(HttpMethod.GET,"/user")
.permitAll()
.antMatchers(HttpMethod.POST,"/user")
.fullyAuthenticated()
.and().csrf().disable()
.formLogin();
// #formatter:on
}
}
and then in your Ouath configuration
#Configuration
public class OAuth2ServerConfiguration {
private static final String RESOURCE_ID = "restservice";
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
// #formatter:off
resources
.resourceId(RESOURCE_ID);
// #formatter:on
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.requestMatchers()
.antMatchers("/resources/**","/greeting")
.and()
.authorizeRequests()
.antMatchers("/resources").access("#oauth2.hasScope('read') or hasRole('ROLE_USER')")
.antMatchers("/greeting").access("#oauth2.hasScope('read')");
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// #formatter:off
endpoints
.tokenStore(this.tokenStore)
.authenticationManager(authenticationManager);
// #formatter:on
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// #formatter:off
clients
.inMemory()
.withClient("clientapp")
.authorizedGrantTypes("password","refresh_token")
.authorities("USER")
.scopes("read", "write")
.resourceIds(RESOURCE_ID)
.secret("123456");
// #formatter:on
}
#Bean
#Primary
public DefaultTokenServices tokenServices() {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setSupportRefreshToken(true);
tokenServices.setTokenStore(this.tokenStore);
return tokenServices;
}
}
}
As you can see above HttpSecurity is conconfigured twice once in class that extends WebSecurityConfigurerAdapter and also in your class extendingResourceServerConfigurerAdapter for your Ouath configuration
part of this example is taken from this gitHub example by royclarkson
https://github.com/royclarkson/spring-rest-service-oauth
I am not sure what you are asking about in your second question, could you clarify ?
I have followed the advice from the official documentation on how to configure two separate HttpSecurity instances:
#Configuration
#EnableWebSecurity
public class SoWebSecurityConfig
{
#Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(username -> {
log.info("\n\n\n ********* authenticating {} ************************************\n\n\n", username);
return new User(username, "", asList(new SimpleGrantedAuthority("TV")));
});
}
#Configuration
#Order(1)
public static class SwiperSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception { configureHttpSec(http, "/swiper"); }
}
#Configuration
#Order(2)
public static class TvSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception { configureHttpSec(http, "/tv"); }
}
static HttpSecurity configureHttpSec(HttpSecurity http, String urlBase) throws Exception {
http .csrf().disable()
.exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint())
.and() .authorizeRequests().antMatchers(urlBase+"/**").authenticated()
.and() .httpBasic()
.and() .logout().logoutUrl(urlBase+"/logout").logoutSuccessHandler((req,resp,auth) -> {})
;
return http;
}
}
In the logs I do see two filter chains being created:
2014-06-30 12:44:22 main INFO o.s.s.w.DefaultSecurityFilterChain - Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher#1, [org.springframework.security.web.context.request.as
ync.WebAsyncManagerIntegrationFilter#806996, org.springframework.security.web.context.SecurityContextPersistenceFilter#1937eaff, org.springframework.security.web.header.HeaderWriterFilter#71e4b308, org.springfr
amework.security.web.authentication.logout.LogoutFilter#1d1cbd0f, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#9b9a327, org.springframework.security.web.savedrequest.RequestCach
eAwareFilter#4993febc, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#67064bdc, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#78b612c6, org.s
pringframework.security.web.session.SessionManagementFilter#6d11ceef, org.springframework.security.web.access.ExceptionTranslationFilter#6e7c351d, org.springframework.security.web.access.intercept.FilterSecurit
yInterceptor#571a01f9]
2014-06-30 12:44:22 main INFO o.s.s.w.DefaultSecurityFilterChain - Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher#1, [org.springframework.security.web.context.request.as
ync.WebAsyncManagerIntegrationFilter#30c1da48, org.springframework.security.web.context.SecurityContextPersistenceFilter#427ae189, org.springframework.security.web.header.HeaderWriterFilter#4784efd9, org.spring
framework.security.web.authentication.logout.LogoutFilter#187e5235, org.springframework.security.web.authentication.www.BasicAuthenticationFilter#514de325, org.springframework.security.web.savedrequest.RequestC
acheAwareFilter#16a9eb2e, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#76332405, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#43a65cd8, or
g.springframework.security.web.session.SessionManagementFilter#3fba233d, org.springframework.security.web.access.ExceptionTranslationFilter#376c7d7d, org.springframework.security.web.access.intercept.FilterSecu
rityInterceptor#3b48e183]
but only the one I designate with Order(1) will actually get used; the URLs matching the other one will not get authenticated.
I have also tried following the docs more closely, using anyRequest() instead of ant matchers for the #Order(2) configuration, but the result was the same.
What are my options to get around this problem?
I am using Spring 4.0.5, Spring Security 3.2.4.
You have failed to follow the documentation in one crucial aspect. You have
http.authorizeRequests().antMatchers(urlBase+"/**").authenticated()
which means that you register this HttpSecurity as a global security module, which applies to all URLs, but only requires authentication on those selected with the Ant matcher. When you do this twice, you end up with two chained global security modules, so naturally only the first one will be responsible for all URLs.
The documentation instead advises this:
http.antMatcher(urlBase+"/**").authorizeRequests().anyRequest().authenticated()
which means that the Ant matcher will used to select which URL this security module is responsible for, and bypass it for all others. This way the second module in line gets its chance when appropriate.
So all you need to do is slightly adjust your static configurer method to the following:
static HttpSecurity configureHttpSec(HttpSecurity http, String urlBase) throws Exception {
http .csrf().disable()
.exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint())
.and() .antMatchers(urlBase+"/**").authorizeRequests().anyRequest().authenticated()
.and() .httpBasic()
.and() .logout().logoutUrl(urlBase+"/logout").logoutSuccessHandler((req,resp,auth) -> {})
;
return http;
}