I have a Spring Boot web application exposing few rest endpoints. I wanted to know how we can enable basic authentication only for selected rest endpoints. Let's say I want only /employee/{id} request to be authenticated and ignore all the other rest endpoints. I am using the following code. My question is will the antMatcher only authenticate the request specified? Currently its enabling authentication for all rest endpoints:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// How does it work will it only authenticate employee &
// ignore any other request?? Its authenticating all the requests currently.
http
.authorizeRequests()
.antMatchers("/employee/*").authenticated()
.and()
.httpBasic()
.and()
.csrf()
.disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
By default Spring Boot will secure all endpoints when Spring Security is on the classpath.
You need to explicitly add an exclusion for all other endpoints to be permitted without authentication.
Example:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/employee/*").authenticated()
.anyRequest().permitAll()
.and()
.httpBasic()
.and()
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password("admin").roles("USER");
}
}
Related
We have used the spring authentication server for providing my other spring boot application(Resource server). There are some endpoints that do not need to get authenticated for a specific host.
I have used the bellow code example but it works only without the OAuth server.
#Override
public void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/url/**").permitAll().
anyRequest().authenticated();
}
Have you tried
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(2)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/media/allVideos")
.permitAll()
.anyRequest()
.authenticated();
}
}
https://spring.io/guides/tutorials/spring-boot-oauth2/
I am using Spring Boot 1.5.9 and have an application that has an API that uses OAuth2 client credentials, with formlogin for a CMS that uses Thymeleaf in the same Spring Boot application.
For this to work, I have the following bean to configure the form login:
#Configuration
public class WebSecurityGlobalConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// api security is handled elsewhere (See OAuth2ServerConfiguration)
.antMatchers("/api/**", "/oauth/**", "/management/**")
.permitAll()
// end api security
.anyRequest().hasRole(UserRole.ADMIN.name())
.and()
.formLogin().loginPage("/login")
.permitAll()
.and()
.logout().permitAll();
}
}
So for the form login part, I declare everything related to API, Oauth and /management (the custom context-path I have set in application.properties for the actuator endpoints):
management.context-path=/management
management.security.roles=ADMIN
For Oauth2, I have this:
#Configuration
public class OAuth2ServerConfiguration {
private static final String RESOURCE_ID = "my-app-service";
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/api/**")
.permitAll()
.and()
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.authorizeRequests()
.antMatchers("/management/health", "/management/info").permitAll()
.antMatchers("/management/**").hasRole(UserRole.ADMIN.name())
.anyRequest().authenticated();
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private TokenStore tokenStore;
#Autowired
private SecurityConfiguration securityConfiguration;
// NOTE: If you set a new validity, you need to clear the 'oauth_access_token' table
// in the database. Only new tokens get the new validity.
#Value("${myapp.security.oauth.access-token-validity-seconds:43200}") // 12 hours by default
private int accessTokenValiditySeconds;
#Value("${myapp.security.oauth.refresh-token-validity-seconds:2592000}") // 30 days by default
private int refreshTokenValiditySeconds;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.passwordEncoder(passwordEncoder);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(securityConfiguration.getMobileAppClientId())
.authorizedGrantTypes("password", "refresh_token")
.scopes("mobile_app")
.resourceIds(RESOURCE_ID)
.accessTokenValiditySeconds(accessTokenValiditySeconds)
.refreshTokenValiditySeconds(refreshTokenValiditySeconds)
.secret(passwordEncoder.encode(securityConfiguration.getMobileAppClientSecret()));
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore).
authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
}
}
}
I want the following behaviour:
If user has role ADMIN by using an Oauth2 access token, all actuator endpoints must be accessible
If the user does not have this ADMIN role, only /health and /info should be accessible (If ADMIN, /health should show extra info like it is by default)
The current behaviour:
The info and health endpoints can be viewed by everybody, but as ADMIN, you don't get extra info. For the other endpoints, I get a 401 if I try with an access token of an ADMIN user with:
{
"timestamp": "2018-01-30T13:45:26.625+0000",
"status": 401,
"error": "Unauthorized",
"message": "Full authentication is required to access this resource.",
"path": "/management/beans"
}
If I set management.security.enabled=false then the ADMIN user has access, but all non-ADMIN users also have access.
What should I change to get the wanted behaviour?
I managed to make it work with the following in the configure method of ResourceServerConfiguration :
http
.requestMatchers()
.antMatchers("/api/**")
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/api/**")
.permitAll()
.and()
.requestMatchers()
.antMatchers("/api/**")
.and()
.authorizeRequests()
.and()
.requestMatchers()
.antMatchers("/management/**")
.and()
.authorizeRequests()
.antMatchers("/management/health", "/management/info").permitAll()
.antMatchers("/management/**").hasRole(UserRole.ADMIN.name())
.anyRequest()
.authenticated()
Using multiple antMatchers directly on the http object does not work, you need to first use requestMatchers
I am trying to give role based authorization for resources. It works with out roles if I do it like
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/hello"),
new AntPathRequestMatcher("/user")
))
.authorizeRequests()
.anyRequest().access("#oauth2.hasScope('read')");
}
#Override
public void configure(ResourceServerSecurityConfigurer resources)
throws Exception {
resources.resourceId("openid");
}
}
If I use below method it won't work for test resources.
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.requestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/hello"),
new AntPathRequestMatcher("/user")
))
.authorizeRequests()
.antMatchers("/test").hasRole("ADMIN")
.anyRequest().access("#oauth2.hasScope('read')");
}
It completely ignores token based authorization. How can I implement this? Another issue I am getting is if I remove requestMatcher block, Oauth client can not get the authorization code, after submitting user credentials to login form it reloads login page again. But with the previous block of code it works fine. What I am doing wrong here?
Here is my security configuration class
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/img/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().loginPage("/login").permitAll()
.defaultSuccessUrl("/hello")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout");
}
}
When you use roles in spring you have to use prefix ROLE (for example ROLE_ADMIN) to make it work with default settings.
I have a Spring-Boot app with the actuator and security starter dependencies.
I want to secure the actuator-endpoints with HTTP-basic-auth.
The following code works as intended, IF the mangagment.port is the default one.
But I want to set this property to mangagment.port=9000.
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").fullyAuthenticated()
.and()
.httpBasic()
.and()
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("4321").roles("USER", "ADMIN");
}
}
How can I secure differnt ports?
Do I have to use a port-mapper as following and if so, does that have any security concerns?
.portMapper()
.http(9090).mapsTo(9443)
.http(80).mapsTo(443);
In Specific
I want to have HTTP Basic authentication ONLY for a specific URL pattern.
In Detail
I'm creating an API interface for my application and that needs to be authenticated by simple HTTP basic authentication. But other web pages should not be using HTTP basic but rather a the normal form login.
Current Configuration - NOT Working
#Override
protected void configure(HttpSecurity http) throws Exception {
http //HTTP Security
.csrf().disable() //Disable CSRF
.authorizeRequests() //Authorize Request Configuration
.antMatchers("/connect/**").permitAll()
.antMatchers("/", "/register").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/api/**").hasRole("API")
.anyRequest().authenticated()
.and() //HTTP basic Authentication only for API
.antMatcher("/api/**").httpBasic()
.and() //Login Form configuration for all others
.formLogin().loginPage("/login").permitAll()
.and() //Logout Form configuration
.logout().permitAll();
}
Waited for 2 days and didn't get any help here. But my research provided me a solution :)
Solution
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true, proxyTargetClass = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private AuthenticationProvider authenticationProvider;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider);
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/api/**")
.authorizeRequests()
.anyRequest().hasAnyRole("ADMIN", "API")
.and()
.httpBasic();
}
}
#Configuration
#Order(2)
public static class FormWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/img/**", "/lib/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() //HTTP with Disable CSRF
.authorizeRequests() //Authorize Request Configuration
.antMatchers("/connect/**").permitAll()
.antMatchers("/", "/register").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and() //Login Form configuration for all others
.formLogin()
.loginPage("/login").permitAll()
.and() //Logout Form configuration
.logout().permitAll();
}
}
}
I dunno if it can be helpful but I couldn't implement the above solution. I found a workaround defining a single Security
#Configuration class
extending
WebSecurityConfigurerAdapter
with both httpBasic() and formLogin() configured. Then I created a custom
CustomAuthEntryPoint implements AuthenticationEntryPoint
that has this logic in the commence method:
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException
{
String urlContext = UtilityClass.extractUrlContext(request);
if (!urlContext.equals(API_URL_PREFIX))
{
String redirectUrl = "urlOfFormLogin"
response.sendRedirect(request.getContextPath() + redirectUrl);
}
else
{
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
Dunno which is the "best practice strategy" about this issue