Spring Security + REST + postgreSQL - spring

I did a tutorial https://auth0.com/blog/securing-spring-boot-with-jwts/ about authorization using Spring Security, but this example use hardcoded user data. I would like to authorize using database PostgreSQL. How can I do that? Or do you know some examples on github using Spring REST Security and PostgreSQL?
package com.example.security;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
// And filter other requests to check the presence of JWT in header
.addFilterBefore(new JWTAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.inMemoryAuthentication()
.withUser("admin")
.password("password")
.roles("ADMIN");
}
}

You can use it with your custom userdetailservice like this:
#Autowired
private CustomUserDetailService userDetailsService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
;
}
and add customuserdetail service :
#Service
public class CustomUserDetailService implements UserDetailsService {
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = getUserFromDatabase();
UserItem userItem = new UserItem(user.getUsername(),user.getPassword(),true,true,true,true, new ArrayList<GrantedAuthority>());;
userItem.setAuthorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN", "ROLE_USER"));
return userItem;
}
}

You need to create a bean for dataSource like this
#Bean
public DriverManagerDataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("org.postgresql.Driver");
driverManagerDataSource.setUrl("jdbc:postgresql://127.0.0.1:5432/mydb");
driverManagerDataSource.setUsername("postgres");
driverManagerDataSource.setPassword("root");
return driverManagerDataSource;
}
And then autowire javax.sql.DataSource inside your WebSecurityConfig class:
#Autowired
DataSource dataSource;
if your password is Bcrypt encoded then create a bean for passwordEncoder
#Bean(name="passwordEncoder")
public PasswordEncoder passwordencoder(){
return new BCryptPasswordEncoder();
}
Configure authentication like this:
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(
"select email,password from users where email=?").passwordEncoder(passwordencoder());
}
and finally hit the /login route.

Related

WebSecurityConfigurerAdapter is deprecated [duplicate]

I am trying to update the WebSecurityConfigurerAdapter as it has been deprecated. The class is configured as follows:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UsuariService userDetailsService;
#Autowired
private AuthEntryPointJwt unauthorizedHandler;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.antMatchers("/api/auth/**").permitAll().antMatchers("/api/test/**").permitAll().antMatchers("/api/v1/**").permitAll().anyRequest()
.authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
}
}
Now without the WebSecurityConfigurerAdapter I redefine the same class like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
#Autowired
UsuariService userDetailsService;
#Autowired
private AuthEntryPointJwt unauthorizedHandler;
#Bean
public AuthTokenFilter authenticationJwtTokenFilter() {
return new AuthTokenFilter();
}
#Bean
AuthenticationManager authenticationManager(AuthenticationManagerBuilder builder) throws Exception {
return builder.userDetailsService(userDetailsService).passwordEncoder(encoder()).and().build();
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/test/**").permitAll()
.antMatchers("/api/v1/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
But unfortunately I get the following error:
org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration':
Unsatisfied dependency expressed through method 'setFilterChains' parameter 0;
nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException:
Error creating bean with name 'filterChain' defined in class path resource [cit/base/app/security/WebSecurityConfig.class]:
Unsatisfied dependency expressed through method 'filterChain' parameter 0;
nested exception is org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.httpSecurity' defined in class path resource [org/springframework/security/config/annotation/web/configuration/HttpSecurityConfiguration.class]:
Bean instantiation via factory method failed;
nested exception is org.springframework.beans.BeanInstantiationException:
Failed to instantiate [org.springframework.security.config.annotation.web.builders.HttpSecurity]: Factory method 'httpSecurity' threw exception;
nested exception is java.lang.IllegalStateException:
Cannot apply org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$EnableGlobalAuthenticationAutowiredConfigurer#3fdc705c to already built object
I would appreciate any kind of help that would be most welcome.
I have managed to update the methods. This is the WebSecurityConfig class, and the methods are modified in the following way:
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
has become:
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
Explanation: In the old version you inject AuthenticationManagerBuilder, set userDetailsService, passwordEncoder and build it. But authenticationManager is already created in this step. It is created the way we wanted (with userDetailsService and the passwordEncoder).
Next, the configure() method for HttpSecurity is replaced by filterChain method as it is explained on the official site: https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter.
import com.myproject.UrlMapping;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#RequiredArgsConstructor
public class SecurityConfig {
private final UserDetailsService userDetailsService;
private final AuthEntryPointJwt unauthorizedHandler;
private final AuthTokenFilter authenticationJwtTokenFilter;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(UrlMapping.AUTH + UrlMapping.SIGN_UP).permitAll()
.antMatchers(UrlMapping.AUTH + UrlMapping.LOGIN).permitAll()
.antMatchers(UrlMapping.VALIDATE_JWT).permitAll()
.antMatchers("/api/test/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*");
}
};
}
}
I really hope my answer helped you!
Also, I have added this in my build.gradle file:
implementation 'javax.xml.bind:jaxb-api:2.3.0'
I hope this configuration will work for UserDetailsService, AuthenticationManagerBuilder and AuthenticationManager.
#Configuration
public class BeanConfiguration {
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
#Configuration
public class SpringSecurityConfiguration {
AuthenticationManager authenticationManager;
#Autowired
UserDetailsService userDetailsService;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userDetailsService);
authenticationManager = authenticationManagerBuilder.build();
http.csrf().disable().cors().disable().authorizeHttpRequests().antMatchers("/api/v1/account/register", "/api/v1/account/auth").permitAll()
.anyRequest().authenticated()
.and()
.authenticationManager(authenticationManager)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
}
#Component
class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private AccountService accountService;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Account account = accountService.findAccountByEmail(email);
return new UserPrincipalImp(account);
}
// ...
}
change your file like this :
#Configuration
#EnableWebSecurity
#EnableMethodSecurity(prePostEnabled = true)
#RequiredArgsConstructor
public class SpringSecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable().cors().disable().authorizeHttpRequests()
.requestMatchers("/user/register").permitAll()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer();
return http.build();
}
}
The complete implementation of SecurityConfig class without extending the WebSecurityConfigurerAdapter is as follows.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
UserDetailsService userDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Bean
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception{
// We don't need CSRF for this example
httpSecurity.csrf().disable()
// don't authenticate this particular request
.authorizeHttpRequests().antMatchers("/authenticate").permitAll()
// all other requests need to be authenticated
.anyRequest().authenticated().and()
// make sure we use stateless session; session won't be used to
// store user's state.
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
return httpSecurity.build();
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
It works in my case, the simplest way is to pass your userDetailService class directly in the SecurityFilterChain function.
Note :
http.userDetailsService(customUserDetailService);
BCryptPasswordEncoder class automicaly get autowired as password Encoder, if #Bean method is avilable in configration.
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
Code :
package com.example.blogapi.config;
import com.example.blogapi.security.CustomUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration {
#Autowired
private CustomUserDetailService customUserDetailService;
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(
(authz) -> authz.anyRequest()
.authenticated())
.httpBasic(Customizer.withDefaults())
.userDetailsService(customUserDetailService);
return http.build();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
security config
#Configuration
public class SecurityConfig {
#Bean
public PasswordEncoder passwordEncoder() {
int rounds = 12;
return new BCryptPasswordEncoder(rounds);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.httpBasic()
.and()
.authorizeHttpRequests()
/*.requestMatchers("/user/**").hasRole("USER")*/
.requestMatchers("/user/**", "/user/info/**").hasAuthority("USER")
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);;
return http.build();
}
#Bean
public AuthenticationManager authenticationManager(UserDetailsService customUserDetailsService) {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(customUserDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
List<AuthenticationProvider> providers = List.of(authProvider);
return new ProviderManager(providers);
}
}
service
#Service
#RequiredArgsConstructor
public class CustomUserDetailService implements UserDetailsService {
private final CustomerRepository customerRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final CustomerModel customer = customerRepository.findByEmail(username); /*email*/
Set<UserRole> roles = new HashSet<>();
roles.add(new UserRole("USER"));
roles.add(new UserRole("ADMIN"));
if (customer == null) {
throw new UsernameNotFoundException(username);
}
String email = customer.email();
String password = customer.password();
return User
.withUsername(email)
.password(password)
/*.roles("USER")*/ /*Into a Security filter must be expression -> hasRole()*/
.authorities(convertAuthorities(roles))
.build();
}
private Set<GrantedAuthority> convertAuthorities(Set<UserRole> userRoles) {
Set<GrantedAuthority> authorities=new HashSet<>();
for (UserRole userRole : userRoles) {
authorities.add(new SimpleGrantedAuthority(userRole.nameRole()));
}
return authorities;
}
}
you modify your class like this:
#Configuration
#EnableWebSecurity
public class Securityconfiguration{
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.withUsername("user1")
.password(passwordEncoder().encode("user1Pass"))
.roles("USER")
.build();
UserDetails manager = User.withUsername("user2")
.password(passwordEncoder().encode("user2Pass"))
.roles("MANAGER")
.build();
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder().encode("adminPass"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, manager, admin);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers("/index.html").permitAll()
.requestMatchers("/profile/**").authenticated()
.requestMatchers("/admin/**").hasRole("ADMIN")
.requestMatchers("/management/index").hasAnyRole("ADMIN","MANAGER")
.anyRequest().authenticated()
.and()
.httpBasic();
return http.build();
}
}

Spring Security inmemory authentication issue

I am using Spring Security inmemory authentication, but it is not working properly.
It is generating default password but it is not taking the user and password I generated in configuration. Below is my controller:
#CrossOrigin(origins = "*")
#EnableAutoConfiguration
#RestController
#RequestMapping("/api")
public class AppController {
#RequestMapping(value="/hello", method=RequestMethod.GET, produces="text/plain")
public String sayHello() {
return "Hello welcome to spring security!!!!";
}
}
And below is my security configuration class:
#Component
#EnableWebSecurity
/*#EnableGlobalMethodSecurity*/
#EnableAutoConfiguration(exclude = {
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class
})
public class SecurityProvider extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/api/*").hasRole("ADMIN")
.and()
.formLogin();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("ramu")
.password("password")
.roles("ADMIN")
.and()
.withUser("gopal")
.password("password")
.roles("USER");
}
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
// ALTHOUGH THIS SEEMS LIKE USELESS CODE,
// ITS REQUIRED TO PREVENT SPRING BOOT AUTO-CONFIGURATION
return super.authenticationManagerBean();
}
}
I also tried by excluding SecurityAutoconfiguration.class at main application by using below annonation:
#SpringBootApplication(exclude = {SecurityAutoConfiguration.class })
public class App {
public static void main( String[] args ) {
SpringApplication.run(App.class, args);
}
}
But no luck, it is not taking the username and password I configured in my configuration class.
I fixed your setup, see the code below.
The WebSecurityConfig (instead of the former ServiceProvider component):
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception{
http.authorizeRequests().antMatchers("/api/*").hasRole("ADMIN").and().formLogin();
}
PasswordEncoder passwordEncoder =
PasswordEncoderFactories.createDelegatingPasswordEncoder();
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication()
.passwordEncoder(passwordEncoder)
.withUser("ramu")
.password("password")
.roles("ADMIN")
.and()
.passwordEncoder(passwordEncoder)
.withUser("gopal")
.password("password")
.roles("USER");
}
}
The RestController:
#CrossOrigin(origins = "*")
#EnableAutoConfiguration
#RestController
#RequestMapping("/api")
public class AppController {
#RequestMapping(value="/hello",method=RequestMethod.GET,produces="text/plain")
public String sayHello(){
return "Hello welcome to spring security!!!!";
}
}
What I had to change:
ServiceProvider #Component -> WebSecurityConfig #Configuration
I registered a PasswordEncoder
I reverted all of the #EnableAutoConfiguration annotations
I reverted the AuthenticationManager Bean
I used the spring-parent:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Solution for spring-boot-starter-parent version 1.5.7.RELEASE:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception{
http.authorizeRequests().antMatchers("/api/*").hasRole("ADMIN").and().formLogin();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception{
auth.inMemoryAuthentication()
.withUser("ramu")
.password("password")
.roles("ADMIN")
.and()
.withUser("gopal")
.password("password")
.roles("USER");
}}
Maybe consider to use a kind of passwordEncoder here as well.

Spring security oauth2 always returning 403

I have a Spring boot app serving Rest endpoints which I'm securing using Spring security and Oauth2.
I want to secure all my endpoints except the endpoints used to authenticate, to create an account or some info stuff.
The security configuration is like this :
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private MongoTokenStore tokenStore;
#Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
//clients.withClientDetails(clientDetailsService);
clients.inMemory().withClient("app").secret("password")
.accessTokenValiditySeconds(30000).authorizedGrantTypes("password", "refresh_token")
.refreshTokenValiditySeconds(300000000)
.scopes("read");
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager)
.pathMapping("/oauth/confirm_access", "/access_confirmation");
}
#Bean
public TokenStore tokenStore() {
return this.tokenStore;
}
}
#Configuration
#EnableResourceServer
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
#Autowired
private SecurityContextService securityContextService;
#Autowired
private MongoTemplate mongoTemplate;
#Bean
public MongoUserDetailsManager mongoUserDetailsManager() throws Exception {
return new MongoUserDetailsManager(userRepository, securityContextService, authenticationManagerBean(), mongoTemplate);
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManagerBean())
.userDetailsService(mongoUserDetailsManager());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.
authorizeRequests()
.antMatchers("/login", "/oauth/authorize", "/oauth/token", "/server/version", "/clients/register").permitAll()
.and().csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.disable();
}
}
I can access to token endpoint to get my access_token, but I want to access to other secured endpoints using this access_token (by adding the Authorization:Bearer {access_toke} to the header), I always get HTTP 403.
Did I miss something? I'm not supposed as authorized if I add the Authorization header?
My Controllers are only annotated with these #RestController, #CrossOrigin
and #RequestMapping("/url")
There are 2 types of security configurations in case of OAuth security(as far as urls security is concerned) in Spring.
1. Basic Security Configuration
This class should implement WebSecurityConfigurerAdapter. It will handle all those requests coming without "Bearer" token type(urls that shouldn't be oauth protected).
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
#Autowired
private SecurityContextService securityContextService;
#Autowired
private MongoTemplate mongoTemplate;
#Bean
public MongoUserDetailsManager mongoUserDetailsManager() throws Exception {
return new MongoUserDetailsManager(userRepository, securityContextService, authenticationManagerBean(), mongoTemplate);
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManagerBean())
.userDetailsService(mongoUserDetailsManager());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.
authorizeRequests()
.antMatchers("/login", "/oauth/authorize", "/oauth/token", "/server/version", "/clients/register").permitAll()
.and().csrf().disable()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.disable();
}
}
2. Resource Server Configuration(OAuth Specific)
This class is responsible for handling all those requests coming with authorization header of type Bearer. It should be extended from ResourceServerConfigurerAdapter class. Here you should mention all those urls with security configurations that you like to be oauth protected.
#Configuration
#EnableResourceServer
public class OAuthResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.requestMatchers().antMatchers("/resources-to-be-protected/**").and().authorizeRequests()
.antMatchers("/resources-to-be-protected/**").access("#oauth2.isClient()");
}
}

How to get Spring Security to respond to Pre-Flight CORS request with OAUTH2

I am not a Java programmer by profession. I am working on building an app for our lab. It started out as a Spring MVC app with JSP files. It has now migrated to a Spring REST API that uses OAUTH2 as a standalone authentication and authorization server. I am using a MySQL database to store my data and users. I can successfully get an access_token issued if I use Postman to access the server. However if I use the Angular 2 client I setup I cannot get past the pre-flight OPTIONS request sent by the client to the server. I have tried several different ways to configure a CORS filter, but I cannot get that filter to get implemented and I always get a 401 code returned for the pre-flight request. I have tried implementing the suggested solution in the Spring blog https://spring.io/blog/2015/06/08/cors-support-in-spring-framework but that hasn't worked yet.
In my pom.xml file I am using these versions of Spring
Spring Framework - 4.3.2.RELEASE
Spring Security - 4.1.2.RELEASE
Spring Security OAUTH2 - 2.0.10.RELEASE
I also included Spring Boot 1.4.0 when trying to use FilterRegistrationBean
Here is my code as it is right now.
WebConfig Class:
#EnableWebMvc
#Configuration
#ComponentScan(basePackages={"eng.lab"})
#Import({ApplicationContext.class})
#PropertySource("classpath:application.properties")
public class WebConfig extends WebMvcConfigurerAdapter{
/* #Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}*/
}
SecurityConfig Class:
#Configuration
#EnableWebSecurity
#PropertySource("classpath:application.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(
"select username,password, enabled from user where username=?")
.authoritiesByUsernameQuery(
"select u.username, r.role from User u, Role r where r.id=u.roleId and u.userName=?");
}
#Autowired
private ClientDetailsService clientDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll()
//.requestMatchers(CorsUtils::isCorsRequest).permitAll()
.antMatchers("/oauth/token").permitAll();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
#Bean
#Autowired
public TokenStoreUserApprovalHandler userApprovalHandler(TokenStore tokenStore){
TokenStoreUserApprovalHandler handler = new TokenStoreUserApprovalHandler();
handler.setTokenStore(tokenStore);
handler.setRequestFactory(new DefaultOAuth2RequestFactory(clientDetailsService));
handler.setClientDetailsService(clientDetailsService);
return handler;
}
#Bean
#Autowired
public ApprovalStore approvalStore(TokenStore tokenStore) throws Exception {
TokenApprovalStore store = new TokenApprovalStore();
store.setTokenStore(tokenStore);
return store;
}
}
MethodSecurityConfig Class:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
#SuppressWarnings("unused")
#Autowired
private SecurityConfig securityConfig;
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
WebAppInitializer Class:
public class WebAppInitializer implements WebApplicationInitializer{
#Override
public void onStartup(ServletContext container) throws ServletException {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(WebConfig.class);
container.addListener(new ContextLoaderListener(rootContext));
DelegatingFilterProxy filter = new DelegatingFilterProxy("springSecurityFilterChain");
DispatcherServlet dispatcherServlet = new DispatcherServlet(rootContext);
ServletRegistration.Dynamic registration = container.addServlet("dispatcherServlet", dispatcherServlet);
container.addFilter("springSecurityFilterChain", filter).addMappingForUrlPatterns(null, false, "/*");
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}
SimpleCorsFilter Class:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {
public SimpleCorsFilter() {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
If I include a second WebSecurityConfig class I do get a 403 code instead.
MyWebSecurity Class:
#Configuration
#EnableWebSecurity
#Order(-1)
public class MyWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll();
}
}
Any suggestions as to how to get past the pre-flight issue beyond waiting for the DATAREST-573 bug to be fixed?
UPDATE: I tried Benjamin's suggestion, but not sure I implemented the solution properly. I have edited my WebConfig class as folows but the pre-flight still fails. The filter still isn't getting applied.
package eng.lab.config;
import javax.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#EnableWebMvc
#Configuration
#ComponentScan(basePackages={"eng.lab"})
#Import({ApplicationContext.class})
#PropertySource("classpath:application.properties")
public class WebConfig extends WebMvcConfigurerAdapter{
//more custome rule beans
#Bean
public FilterRegistrationBean simpleCORSFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new SimpleCorsFilter());
registration.addUrlPatterns("/*");
//registration.addInitParameter("paramName", "paramValue");
registration.setName("simpleCorsFilter");
registration.setOrder(0);
return registration;
}
#Bean(name = "simpleCorsFilter")
public Filter simpleCorsFilter() {
return new SimpleCorsFilter();
}
/* #Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}*/
}
Your CORS filter is not registered. As this is a standard javax.servlet.Filter instance, you need to register it against a FilterRegistrationBean or declare it in your web.xml file.
You can refer to this SO question for more details: How to add a filter class in Spring Boot?

Spring OAuth2 additional permision

I need to add additional restriction to authenticate users. My User model has a field 'active'. It is false, when User registers, but not activate his account with hash from mail. Now, even if User is not active he gets access_token from Oauth.
How should I configure this?
I was thinking about SpringSecurityInterceptor, but I'm not sure about confusing Spring Security with OAuth2.
This is my SpringOAuth2.0 configuration:
#Configuration
public class OAuth2Configuration extends AuthorizationServerConfigurerAdapter {
private static final String RESOURCE_ID = "restservice";
#Configuration
#EnableResourceServer
protected static class ResourceServerConfiguration extends
ResourceServerConfigurerAdapter {
#Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources
.resourceId(RESOURCE_ID);
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatcher(new OrRequestMatcher(
new AntPathRequestMatcher("/rest/**")
))
.authorizeRequests()
.anyRequest().access("#oauth2.hasScope('read')");
}
}
#Configuration
#EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends
AuthorizationServerConfigurerAdapter {
private TokenStore tokenStore = new InMemoryTokenStore();
#Autowired
#Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
#Autowired
UserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(this.tokenStore)
.authenticationManager(this.authenticationManager)
.userDetailsService(userDetailsService)
.pathMapping("/oauth/token", "/rest/oauth/token");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients
.inMemory()
.withClient("clientapp")
.authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
.authorities("USER")
.scopes("read", "write", "trust")
.resourceIds(RESOURCE_ID)
.secret("123456");
}
}
}
And also Spring security
#Configuration
#Order(2147483640)
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
UserDetailsService userDetailsService;
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/user/**").authenticated()
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("email")
.passwordParameter("password")
.defaultSuccessUrl("/user/")
.successHandler(successHandler())
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.and()
.rememberMe()
.tokenRepository(persistentTokenRepository())
.tokenValiditySeconds(86400)
.and()
.csrf().disable();
}
#Bean
public AuthenticationSuccessHandler successHandler() {
return new UserLoginSuccessHandler();
}
#Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl();
tokenRepositoryImpl.setDataSource(dataSource);
return tokenRepositoryImpl;
}
#Bean
public SpringSecurityDialect securityDialect() {
return new SpringSecurityDialect();
}
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(new BCryptPasswordEncoder());
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
Any advice would be helpful.
If I understand you correctly, you don't want your authorization server to grant an access token for a user that is not activated?
You could let your UserDetailsService.loadUserByUsername throw a UsernameNotFoundException if the user exists, but is not activated.

Resources