I'm learning Spring Security. I have my login system ready and I want to add roles. I've seen many tutorials and docs about it and I couldn't find what I'm looking for.
I don't want to create an extra table for Roles, because my table user has a column named "type" and I want to use it for authorization. The value of that column can be "person", "teacher" or "organization". So, I want to based the role system on that column, not in a OneToMany o ManyToMany relationship with a table named "role".
How can I configure that?
Thanks
UPDATED
I forgot, I'm using Spring Data. This is the code I'm using
#Configuration
#EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
private AuthenticationProvider authenticationProvider;
#Autowired
#Qualifier("daoAuthenticationProvider")
public void setAuthenticationProvider(AuthenticationProvider authenticationProvider) {
this.authenticationProvider = authenticationProvider;
}
#Bean
public PasswordEncoder passwordEncoder(BCryptPasswordEncoder passwordEncoder){
return passwordEncoder;
}
#Bean
public DaoAuthenticationProvider daoAuthenticationProvider(BCryptPasswordEncoder passwordEncoder,
UserDetailsService userDetailsService){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
return daoAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/h2-console").disable()
.authorizeRequests().antMatchers("/").authenticated()
.antMatchers("/console/**").permitAll()
.antMatchers("/static/**").permitAll()
.antMatchers("/profile").hasAuthority("PERSON")
.and().formLogin().loginPage("/login").permitAll()
.and().exceptionHandling().accessDeniedPage("/login")
.and().logout().permitAll()
http.headers().frameOptions().disable();
}
#Autowired
public void configureAuthManager(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
authenticationManagerBuilder
.jdbcAuthentication().authoritiesByUsernameQuery("select type from users where username = ?").and()
.authenticationProvider(authenticationProvider);
}
}
You can define a UserDetailsService with a PasswordEncoder in Java Config like following:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired private PersonRepository personRepository;
#Override
#Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(username -> {
Person person = personRepository.findByUsername(username);
if (person == null) throw new UsernameNotFoundException("Invalid user");
return new User(person.getUsername(),
person.getPassword(),
Collections.singleton(new SimpleGrantedAuthority(person.getType())));
})
.passwordEncoder(passwordEncoder())
}
// Rest of the configuration
}
In the above example, i supposed you have a PersonRespository that has access to your user information. With this UserDetailsService you won't need your AuthenticationProvider. Also, User resides in org.springframework.security.core.userdetails package.
Related
This is a follow up question to this question.
I migrated my Vaadin 20 application to 21 to use view-based access control. The Annotations #PermitAll and #AnonymousAllowed are working fine. However when I try to restrict a route to a specific user role with #RolesAllowed I can't get access to this site (being logged in with a user who has this role).
Is there some special code required to get Vaadin to recognize the roles of my authenticated user?
Role restricted page:
#Component
#Route(value = "admin", layout = MainLayout.class, absolute = true)
#RolesAllowed("admin")
#UIScope
public class AdminView ...
SecurityConfig
#EnableWebSecurity
#Configuration
public class SecurityConfiguration extends VaadinWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
setLoginView(http, LoginView.class, "/login");
}
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
web.ignoring().antMatchers("/images/**");
}
}
The roles you pass into #RolesAllowed are case-sensitive and should match the roles you have in Spring Security. Most likely in your case, you want to use #RolesAllowed({"ROLE_ADMIN"}). You can read more in the docs here https://vaadin.com/docs/v21/flow/integrations/spring/view-based-access-control/#annotating-the-view-classes
After a lot of debugging, I found the problem, the implementation of the getAuthorities() Function in my implementation of UserDetails.java was incorrect. A working dummy version with one role looks something like this:
#Override
#JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of( new SimpleGrantedAuthority("ROLE_" + "admin"));
}
Important was to add "ROLE_" in front of the actual role name. Then I can use #RolesAllowed("admin") in the view class.
When making post request to login this error shows up telling me that userService is null.
When I create an object of UserService instead of autowiring it it passes it but tell me that the repository called in userService is null. The repository is autowired and i cannot instanciate it because its an interface.
Here is the service class:
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
UserService userService;
#Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
return new MyUserDetails(userService.getByUsernameOrEmail(s));
}
}
And this is the security configuration class:
Also I am creating an object of MyUserService because spring cannot autowire it telling me that no bean have such name.
#Configuration
#EnableWebSecurity
public class UserSercurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new MyUserDetailsService());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.anyRequest().hasRole(UserType.ADMIN.toString())
.and().formLogin();
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
You cannot instantiate method or variables in your repository as it is an interface but you can autowire your repository class to use your method declared in userRepository interface, you have to autowire your repository class.You can do it this way as I have done in the below code.
Your service class should be like this:
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users user = userRepository.getUserByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("Could not find user");
}
return new MyUserDetails(user);
}
And your repository should be like this:
#Repository
public interface UserRepository extends JpaRepository<Users, Long> {
#Query("SELECT u FROM Users u WHERE u.name = ?1")
public Users getUserByUsername(String username);
}
And also autowire UserDetailsService in your configuration class and pass the instance userDetailsService in your configure(AuthenticationManagerBuilder auth) method, UserDetailsService provides you instance of your MyUserDetailService class.
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Bean
public PasswordEncoder getPasswordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.and()
.formLogin().permitAll()
.defaultSuccessUrl("/", true)
.and()
.logout().permitAll()
.logoutSuccessUrl("/");
}
Try to implement in this way, If this is solution of your question please let me know and if you still have doubt feel free to ask.
I have multiproject Spring application.
Project A - responsible for LDAP authentication
Project B - responsible for Database authentication
Project MAIN - can use both of them or one of them.
If we use only Project A - we have LDAP auth
If we use only Project B - we have JDBC auth
If we use both of them - first goes LDAP auth, if it failures, then goes JDBC auth. And if Project B is included, it adds some filters
Project MAIN does not have #Configuration file, but Projects A and B has it.
Project A #Configuration
#Configuration
#EnableWebSecurity
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**адрес сервера LDAP*/
#Value("${ldap.server}")
private String ldapServer;
/**номер порта LDAP сервера*/
#Value("${ldap.port}")
private int ldapPort;
/**домен для LDAP*/
#Value("${ldap.suffix}")
private String suffix;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(adAuthProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests().antMatchers("/**").authenticated()
.and()
.csrf().disable();
}
/**провайдер для аутентификации через LDAP*/
#Bean
public ActiveDirectoryLdapAuthenticationProvider adAuthProvider() {
String ldapUrl = String.format("ldap://%s:%s", ldapServer, ldapPort);
ActiveDirectoryLdapAuthenticationProvider adAuthProvider = new
ActiveDirectoryLdapAuthenticationProvider(suffix, ldapUrl);
adAuthProvider.setConvertSubErrorCodesToExceptions(true);
adAuthProvider.setUseAuthenticationRequestCredentials(true);
return adAuthProvider;
}
}
and Project B Configuration file.
#Configuration
#EnableWebSecurity
public class ECommonConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(jdbcAuthProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests().antMatchers("/**").authenticated()
.and()
.csrf().disable();
http.addFilterAt(ldapAuthenticationFilter(), LDAPAuthenticationFilter.class);
http.authorizeRequests().antMatchers("/**").access("#requestAuthorization.checkRequestPermissions(authentication, request)");
}
/**провайдер для аутентификации через базу данных*/
#Bean
public DaoAuthenticationProvider jdbcAuthProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
/**бин для шифрования паролей*/
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**бин для фильтра проверки наличия LDAP-пользователя в базе данных*/
#Bean
public LDAPAuthenticationFilter ldapAuthenticationFilter() throws Exception {
return new LDAPAuthenticationFilter(authenticationManager());
}
#Bean
#Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
/**бин для инициализации базы данных по умолчанию - описание параметров подключения к БД в файле application.yml*/
#Bean
public DataSource dataSource() {
return datasourceConnectionManager().getDataSource("test");
}
/**бин создания менеджера подключения к нескольким базам данных*/
#Bean
public DatasourceConnectionManager datasourceConnectionManager() {
return new DatasourceConnectionManager();
}
}
I need these two configurations works together or only one oh them
To combine this 2 ways of authentication you can create a custom authentication provider ( more details here: https://www.baeldung.com/spring-security-authentication-provider )
The implementation of the auth provider would look something like this:
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
private ActiveDirectoryLdapAuthenticationProvider ldapAuthenticationProvider;
private DaoAuthenticationProvider daoAuthenticationProvider;
// env variable to help you choose which auth provider should be enabled
#Value("${ldap.enabled}")
private int ldapEnabled;
// env variable to help you choose which auth provider should be enabled
#Value("${daoAuth.enabled}")
private int daoAuthEnabled;
#Autowired
public CustomAuthenticationProvider(ActiveDirectoryLdapAuthenticationProvider ldapAuthenticationProvider, DaoAuthenticationProvider daoAuthenticationProvider) {
this.ldapAuthenticationProvider = ldapAuthenticationProvider;
this.daoAuthenticationProvider = daoAuthenticationProvider;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication);
// if both enabled then first try with ldap, if not successful try with dao
if (ldapEnabled && daoAuthEnabled ) {
Authentication authenticate = ldapAuthenticationManager.authenticate(authentication);
if(!authenticate.isAuthenticated()) {
authenticate = ldapAuthenticationManager.authenticate(authentication);
}
return authenticate;
}
// if only ldap enabled
if(ldapEnabled) {
return ldapAuthenticationManager.authenticate(authentication);
}
// if only dao enabled
return daoAuthenticationProvider.authenticate(authentication);
}
#Override
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
}
You can use Spring profiling for this. Just Add #Profile annotation along with name on the configuration class as shown below.
Configuration for ProjectA
#Profile("ProjectA")
#Configuration
#EnableWebSecurity
#Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
And Configuration for ProjectB
#Profile("ProjectB")
#Configuration
#EnableWebSecurity
public class ECommonConfig extends WebSecurityConfigurerAdapter {
...
Then at the time of execution of application you can specify active profile by passing following parameter to java.
#In case of need of only ProjectA then
-Dspring.profiles.active=ProjectA
#In case of need of only ProjectB then
-Dspring.profiles.active=ProjectB
#In case of need of both projects then
-Dspring.profiles.active=ProjectA,ProjectB
Same thing you can define in application.properties file with required profile
spring.profiles.active=ProjectA,ProjectB
This way you can dynamically decide which Project configuration should be included.
I am very new to spring and it is my first attempt at spring security with oauth2. I have implemented OAuth2 with spring security and I do get the access token and the refresh token. However, while sending the refresh token to get the new access token I got "o.s.s.o.provider.endpoint.TokenEndpoint - IllegalStateException, UserDetailsService is required."
The solution to similar problem by other users appeared to be attaching UserDetailsService with the endpoint.
So I did the same and now when I try to send the request to with grant_type: refresh_token and refresh_token: THE TOKEN along with the client id and secret, I get an error that the user was not found.
Please refer the WebSecurityConfiguration class below:
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService customUserDetailsService;
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean ();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService)
.passwordEncoder(encoder());
}
#Override
protected void configure (HttpSecurity http) throws Exception {
http.csrf().disable()
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/login**")
.permitAll()
.anyRequest()
.authenticated();
}
public PasswordEncoder encoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Please refer the AuthorizationServerConfiguration class below:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Autowired
private CustomUserDetailsService userDetailsService;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore());
.userDetailsService(userDetailsService);
}
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
}
Please refer the ResourceServerConfiguration class below:
#Configuration
#EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter{
#Autowired
DataSource dataSource;
#Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource);
}
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId("scout").tokenStore(tokenStore());
}
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests (). antMatchers ("/oauth/token", "/oauth/authorize **").permitAll();
// .anyRequest (). authenticated ();
http.requestMatchers (). antMatchers ("/api/patients/**") // Deny access to "/ private"
.and (). authorizeRequests ()
.antMatchers ("/api/patients/**"). access ("hasRole ('PATIENT')")
.and (). requestMatchers (). antMatchers ("/api/doctors/**") // Deny access to "/ admin"
.and (). authorizeRequests ()
.antMatchers ("/api/doctors/**"). access ("hasRole ('DOCTOR')");
}
}
The CustomUserDetailsService class for reference if required:
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UsersRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<Users> usersOptional = userRepository.findByEmail(email);
Users user = null;
if(usersOptional.isPresent()) {
System.out.println(usersOptional.isPresent());
user = usersOptional.get();
}else {
throw new RuntimeException("Email is not registered!");
}
return new CustomUserDetails(user);
}
}
As I think, the server should only check for the validity of the refresh token as we don't pass the user details with refresh token. So I don't know why it requires the userDetails in the first place.
Please help and guide if I am missing something!
Thanks in advance.
I don't sure. But as I see your code in WebSecurityConfiguration could wired default InMemoryUserDetailsManager UserDetailsService .That could be reason why you have 2 different provider. In one you write, from the other you read users. Please try change your code as I show below and let me know if it help:
Was:
#Autowired
private UserDetailsService customUserDetailsService;
My vision how should be:
#Autowired
private CustomUserDetailsService customUserDetailsService;
In my web application, I have to use Spring Security and get the user's authentication details using LDAP and the user's authorization details using JDBC. The user submits a form and I get the username and password from it.
How Do I get the username and password in my WebSecurityConfig file?
How do I configure the authorization and authentication?
My WebSecurityConfig:
#Configuration
#EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().authenticated().and().formLogin()
.loginPage("/").permitAll();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.ldapAuthentication().userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups").contextSource(contextSource());
}
#Bean
public DefaultSpringSecurityContextSource contextSource() {
DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(
"ldap://mjkoldc-03.red.com");
contextSource.setUserDn("mj\\" + email);
contextSource.setPassword(password);
contextSource.setReferral("follow");
contextSource.afterPropertiesSet();
return contextSource;
}
}
Previously I was getting details using LDAPTemplate:
LdapQuery query = query().base("dc=metaljunction,dc=com")
.attributes("GivenName", "sn", "mail", "MobilePhone")
.where("ObjectClass").is("user").and("SamAccountName")
.is(email);
If you what to authentificate agains LDAP and Autorize (get user roles from JDBC) you should implement LdapAuthoritiesPopulator.
public class CustomAuthoritiesPopulator implements LdapAuthoritiesPopulator {
#Override
public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
Collection<GrantedAuthority> gas = new HashSet<GrantedAuthority>();
gas.add(new SimpleGrantedAuthority("ADMIN"));
return gas;
}
}
and add it to your SecurityConfig
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.ldapAuthoritiesPopulator(new CustomAuthoritiesPopulator())
.userSearchFilter("yourfilter")
.contextSource(contextSource());
}
}
Like this all users authentificated via LDAP will automatically get "ROLE_ADMIN".