How to use both GlobalMethodSecurityConfiguration and a WebSecurityConfigurerAdapter in a Spring application - java

I have some API, several resources should be available to everyone, the rest for users.
I to proctect resources I have implemented a class which extends WebSecurityConfigurerAdapter like here:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(final HttpSecurity http) throws Exception
{
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.authenticationManagerResolver((request) -> http.getSharedObject(AuthenticationManager.class))
.and().oauth2Login()
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and().cors();
}
}
And then I was trying to follow https://www.baeldung.com/spring-deny-access to allow some of resources be accessible to everyone
So I did according to this example
GlobalMethodSecurityConfiguration & WebSecurityConfigurerAdapter
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration
{
#Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new CustomPermissionAllowedMethodSecurityMetadataSource();
}
#Configuration
public static class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer()
.authenticationManagerResolver((request) -> http.getSharedObject(AuthenticationManager.class))
.and().oauth2Login()
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and().cors();
}
}
}
and the implementation of CustomPermissionAllowedMethodSecurityMetadataSource
public class CustomPermissionAllowedMethodSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource
{
#Override
protected Collection<ConfigAttribute> findAttributes(Method method, Class<?> targetClass)
{
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
List attributes = new ArrayList<>();
// if the class is annotated as #Controller we should by default deny access to all methods
if (AnnotationUtils.findAnnotation(targetClass, Controller.class) != null)
{
attributes.add(DENY_ALL_ATTRIBUTE);
}
if (annotations != null)
{
for (Annotation a : annotations)
{
// but not if the method has at least a PreAuthorize or PostAuthorize annotation
if (a instanceof PreAuthorize || a instanceof PostAuthorize)
{
return null;
}
}
}
return attributes;
}
#Override
protected Collection<ConfigAttribute> findAttributes(Class<?> clazz)
{
return null;
}
#Override
public Collection<ConfigAttribute> getAllConfigAttributes()
{
return null;
}
}
At the end I have added to the endpoint in rest controller:
#PreAuthorize("permitAll()")
Unfortunately, without a user, I cannot access this endpoint.
Do I use GlobalMethodSecurityConfiguration and a WebSecurityConfigurerAdapter wrong?
Is it a correct way to achieve what I mentioned at the beginning (some endpoints protected, some not)?

You have to understand the difference between Method Security and Http Security.
Method security is how to protect methods internally from being called. This is usually used in for instance client applications, desktop applications etc. Here you place an annotation on a specific method and will protect it from being called internally.
HttpSecurity is an implementation that deals with how to protect http api endpoints. This is usually done with preimplemented filters in spring boot and this is what you should be looking at, not method security.
You have currently implemented method security and trying to protect http endpoints using it.
I suggest you start with the spring security hello world java configuration part in the official documentation to learn how to implement HttpSecurity in spring boot. Or here is another tutorial.
And here is an example
#Configuration
#EnableWebSecurity
public class CustomWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user1").password(passwordEncoder().encode("user1Pass"))
.authorities("ROLE_USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/securityNone").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
this configuration permits all requests to /securityNone and sets all other endpoints to need authentication.

Related

Disable securitycontext without using depricated WebSecurityConfigurerAdapter

I'm trying to rewrite following class in order to get rid of the depricated WebSecurityConfigurerAdapter:
#EnableWebSecurity
public class MyWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity security) throws Exception {
security.mvcMatcher("/my/path/*").securityContext().disable();
}
}
And I've tried to rewrite this with the help of the official Spring documentation. The following two attempts resulted in 403 Errors when trying to access resources on that path:
#EnableWebSecurity
public class MyWebSecurityConfiguration {
#Bean
public SecurityFilterChain filterChain(HttpSecurity security) throws Exception {
security.mvcMatcher("/my/path/*").securityContext().disable();
return security.build();
}
}
#EnableWebSecurity
public class ConsentWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
public WebSecurityCustomizer webSecurityCustomizer() throws Exception {
return (web) -> web.ignoring().mvcMatchers("/v1/containers/*");
}
}
While in the original code everything is running
I also faced the same scenario of discarding the deprecated method and replacing it with SecurityFilterChain
if you want to disable the security on given path then try this:
security.mvcMatcher("/my/path/*").permitAll();
Edit: Here is my migrated code which worked fine with permitting every request without authentication.
#Configuration
#EnableWebMvc
public class SecurityConfig {
#Autowired
private UserDetailsService userDetailsService;
#Bean
protected SecurityFilterChain authorizationConfig(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.authorizeRequests()
.antMatchers("/login", "/post/**", "/newcomment/**", "/page/**","/api/","/api/posts/filter",
"/api/comments").permitAll();
return httpSecurity.build();
}
You can use below code for reference
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration {
private final String[] WHITE_LABEL_URLS = {"/blogapp", "/usercreation", "/css/**", "/saveuser", "/page/**"};
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.httpBasic()
.and()
.authorizeHttpRequests()
.antMatchers(WHITE_LABEL_URLS).permitAll()
.anyRequest().authenticated()
.securityContext().disable();
return httpSecurity.build();
}
}

Spring Security 5 / Login / REST API Accessibility / Bad Credentials even if they are right?

I am implementing a REST API using Spring Boot (2.0.1) to work with MongoDB (3.6). I'm really stuck. I've also tried other tips from StackOverFlow but it didn't help for some reason.
I have configured the SecurityConfig.java to permit the access to certain areas and also created a User inMemoryAuthentication, to be able to login to HAL Browser (Spring) and etc. But the problem is, that whatever address I put in browser I get a Login form and the credentials used in the inMemoryAuthentication is always wrong for some reason. The only way I've found to access the API is by excluding SecurityAutoConfiguration in the main class. But this opens up every permission to access everything including HAL Browser without authentication.
Would someone show me what I am doing wrong? I want to permit only certain paths/addresses to everyone, permit everything else only to use with TokenAuthentication (have already a custom implementation of it) and have one user (username, password) to access HAL Browser.
Here is my SecurityConfig.java:
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Autowired
private final TokenAuthenticationService tokenAuthenticationService;
#Autowired
protected SecurityConfig(final TokenAuthenticationService tokenAuthenticationService) {
super();
this.tokenAuthenticationService = tokenAuthenticationService;
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable()
.addFilterBefore(new TokenAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/hello").permitAll()
.antMatchers("/test2").permitAll()
.antMatchers("/register").permitAll()
.antMatchers("/useraccount").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// auth
// .inMemoryAuthentication()
// .withUser("user1").password("password").roles("ADMIN");
auth
.inMemoryAuthentication()
.withUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER"));
// auth
// .userDetailsService(userService);
}
// #Bean
// #Override
// public UserDetailsService userDetailsService() {
// UserDetails user =
// User.withDefaultPasswordEncoder()
// .username("user")
// .password("password")
// .roles("USER")
// .build();
//
// return new InMemoryUserDetailsManager(user);
// }
// #Bean
// public UserDetailsService userDetailsService() {
// InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// manager.createUser(User.withUsername("user").password("pass").roles("USER", "ADMIN").build());
// return manager;
// }
}
I've tried different approaches as you see (commented blocks) but still no luck.
Even though I have permitAll() on /register, i still get the auto generated login form, which won't accept any credentials.
So as i've said earlier the only way to use my API is to exclude the SecurityAutoConfiguration (#EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class) but it is not a secure option.
Is there any way to resolve this?
From what I can see, it's likely that your SecurityConfig class never gets called, as it doesn't have any annotation indicating to Spring Boot that it should look for beans to autowire in the class (#Autowired)
To give you an idea, the following will never be called:
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
System.out.println("This will never called");
}
}
Whereas, if we had #EnableWebSecurity:
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
System.out.println("This is called");
}
}
Keep in mind that Spring Boot will not detect annotations inside a class if the class itself is not annotated with #Component or with another annotation that inherits the #Component annotation (such as #Configuration, #Service, ...)
EDIT: I quickly put together a program to imitate your situation:
SecurityConfiguration.java:
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser(new User("root", "root", Arrays.asList(new SimpleGrantedAuthority("USER"))))
.passwordEncoder(fakePasswordEncoder());
}
#Bean
public PasswordEncoder fakePasswordEncoder() {
return new PasswordEncoder() {
#Override
public String encode(CharSequence charSequence) {
return null; // matches(...) will always return true anyways
}
#Override
public boolean matches(CharSequence charSequence, String s) {
return true;
}
};
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/hello").permitAll()
.antMatchers("/test2").permitAll()
.antMatchers("/register").permitAll()
.antMatchers("/useraccount").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().logoutRequestMatcher(new AntPathRequestMatcher("/logout")).permitAll();
}
}
Note that I just quickly made a password encoder that ignores the password because that would require more work
ExampleController.java:
#RestController
public class ExampleController {
#GetMapping("/")
public Object index() {
return getCurrentUser();
}
public Object getCurrentUser() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return ((UsernamePasswordAuthenticationToken)auth).getPrincipal();
}
}
And when I login with the username root and the any password (remember, the fake password encoder doesn't care about the password), it redirects me to / and displays the following:
{"password":null,"username":"root","authorities":[{"authority":"USER"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true,"enabled":true}
(which is normal because that's what I'm making it output)

Spring Security ignore path for filter

How can I configure Spring Security to use a custom filter for all requests except the ones I whitelist in the same level, e.g. "/login" skips my filter but every thing else "/**" goes through the filter.
As a workaround I could use different prefixes, "/secured/**" vs "/whitelist/**" or ignore the whitelisted ones in the filter, but that does not seem to be a clean solution.
I already tried setting up two configurations with #Order(1 and 2) but it didn't work.
#EnableWebSecurity
public class SpringSecurityConfig {
#Configuration
#Order(1)
#EnableGlobalMethodSecurity(securedEnabled = true)
public static class JwsSecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
#Autowired
private StatelessAuthenticationFilter statelessAuthenticationFilter;
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/login");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/**").authenticated()
.anyRequest().authenticated()
.and().addFilterBefore(statelessAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return authenticationManager();
}
}
}

Spring REST security - Secure different URLs differently

I have working REST API under Spring 4 using Basic authentication. These REST services are under /api/v1/** URL. However, I want to add another set of REST endpoints under different url /api/v2/**, but protected with token-based authentication.
Is it possible to do this with one servlet ? How to configure Spring Security to use different forms of authentication for different URLs ?
Thank you.
Here's a code sample in Java config that uses UserDetailsService and has different security configurations for different URL endpoints:
#Configuration
#EnableWebMvcSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Configuration
#Order(1)
public static class ApiWebSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v1/**")
.httpBasic()
.realmName("API")
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/v1/**").authenticated();
}
}
#Configuration
#Order(2)
public static class ApiTokenSecurityConfig extends WebSecurityConfigurerAdapter{
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.antMatcher("/api/v2/**")
/* other config options go here... */
}
}
}

Spring OAuth2 Resource allowed only for clients and token auto creation

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 ?

Categories