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 ?
Related
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.
I followed this great tuto : https://developer.okta.com/blog/2020/01/06/crud-angular-9-spring-boot-2#spring-boot-as-an-oauth-2-0-resource-server
I would like to add Roles to the spring part. I don’t want to use claims like in this tuto https://developer.okta.com/blog/2018/09/26/build-a-spring-boot-webapp but I would like to add my customs role (from my own db).
I tryed to override userDetailsService() from WebSecurityConfigurerAdapter to use my custom UserDetailService. But it seems this method is never called … (see println), should it be?
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
#Override
protected void configure(HttpSecurity http) throws Exception {
//#formatter:off
http
.authorizeRequests()
.antMatchers("/home/**").permitAll()
.antMatchers("/admin/**").hasAuthority("ADMIN")
.anyRequest().authenticated()
.and()
.oauth2Login()
.and()
.oauth2ResourceServer().jwt();
//#formatter:on
}
#Override
protected UserDetailsService userDetailsService() {
return username -> {
System.out.println("yo");
Optional<co.simplon.blog.model.User> user = userRepository.findByName(username);
return User
.withUsername(username)
.password(user.get().getPassword())
.authorities(Arrays.asList(user.get().getRole()))
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.disabled(false)
.build();
};
}
}
you have to add you UserDetailsService implementation in the WebConfigurerSEcurityAdapter:
#Override
public void configure(AuthenticationManagerBuilder builder)
throws Exception {
builder.userDetailsService(new MyUserDetailsService());
}
Then you'll see that your implementation will start being called.
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)
So far I've had the password grant type and that worked perfectly fine.
Recently I started implementing the Authorization code grant of OAuth in my project. I'm able to get the authorization code from the server. Using the code I'm again able to get the access-token.
The problem is I'm unable to reach the resource server using my access-token. I'm getting redirected to Spring's default /login page everytime I try to access any resource.
Below is the Resource Server:
#Configuration
#PropertySource("classpath:webservices-application.properties")
#EnableResourceServer
public class ResourceServer extends ResourceServerConfigurerAdapter{
#Value("${security.oauth2.resource.id}")
private String resourceId;
#Bean
public JdbcTokenStore getTokenStore() {
return new JdbcTokenStore(dataSource);
}
#Autowired
private DataSource dataSource;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/oauth/**","/login","/").permitAll()
.anyRequest().authenticated();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(getTokenStore())
.resourceId(resourceId).stateless(false);
}
}
WebSecurity:
#Configuration
#EnableWebSecurity
#EnableOAuth2Sso
public class CustomWebsecurity extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/oauth/**","/login","/").permitAll()
.anyRequest().authenticated()
.and()
.formLogin();
}
}
The AuthorizationServer:
#Configuration
#EnableAuthorizationServer
#EnableOAuth2Sso
protected class AuthorizationApplication extends AuthorizationServerConfigurerAdapter {
#Autowired
public AuthorizationApplication (ApplicationContext applicationContext, AuthenticationManager authenticationManager) {
this.passwordEncoder = applicationContext.getBean(PasswordEncoderImpl.class);
this.authenticationManager = authenticationManager;
}
private PasswordEncoder passwordEncoder;
private AuthenticationManager authenticationManager;
#Bean
protected AuthorizationCodeServices getAuthorizationCodeServices() {
return new JdbcAuthorizationCodeServices(dataSource);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
AuthorizationCodeServices services = getAuthorizationCodeServices();
JdbcTokenStore tokenStore = getTokenStore();
endpoints
.userDetailsService(userDetailsService)
.authorizationCodeServices(services)
.authenticationManager(authenticationManager)
.tokenStore(tokenStore)
.approvalStoreDisabled();
}
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
security.passwordEncoder(passwordEncoder);
}
}
The issue might be because of some incorrect configuration of the WebSecurity class. But, I've tried multiple configurations with no luck.
With some guidance from #dur, I was able to reach to the solution.
Here's one of the culprits:
The default order of the OAuth2 resource filter has changed from 3 to SecurityProperties.ACCESS_OVERRIDE_ORDER - 1. This places it after the actuator endpoints but before the basic authentication filter chain. The default can be restored by setting security.oauth2.resource.filter-order = 3
All in all, I made the following changes:
Used #EnableOauth2Client instead of #EnableOAuth2Sso at the ResourceServer as well as the AuthorizationServer, because the latter was giving me the following error:
java.lang.IllegalArgumentException: URI must not be null
Removed CustomWebSecurity and did all the security configurations in the ResourceServer itself.
Change the filter order of the Resource filter by putting the following in the properties file:
security.oauth2.resource.filter-order = 3
Some basic change in the security configuration.
Here's my ResourceServer class now:
#Configuration
#PropertySource("classpath:webservices-application.properties")
#EnableResourceServer
#EnableOAuth2Sso
public class ResourceServer extends ResourceServerConfigurerAdapter{
#Value("${security.oauth2.resource.id}")
private String resourceId;
#Bean
public JdbcTokenStore getTokenStore() {
return new JdbcTokenStore(dataSource);
}
#Autowired
private DataSource dataSource;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.requestMatchers().antMatchers(
"/protected_uri_1",
"/protected_uri_2",
"/protected_uri_3")
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and().formLogin();
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(getTokenStore())
.resourceId(resourceId);
}
}
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();
}
}
}