Skip to content

Configure Roles#

Supported Methods#

  • In Spring Security the roles of the user can be configured and validated using the following methods.
Method Description
hasRole() Accepts a single role name for which the endpoint will be configured and the user will be validated against the single role mentioned. Only users having the same role configured can call the endpoint
hasAnyRole() Accepts multiple roles for which the endpoint will be configured and user will be validated against the roles mentioned. Only users having any of the role configured can call the endpoint
access() Using Spring Expression Language (SpEL) it provides you unlimited possibilities for configuring roles which are not possible with the above methods. We can use operators like OR, AND inside access() method
  • ROLE_prefix only to be used while configuring the role in database. But when we configure the roles, we do it only by its name.
  • access() method can be used not only for configuring authorization based on authority or role but also with any special requirements. For example, we can confgiure access based on the country of the user or current time/date.

Example Configuration#

Database Table#

  • We will create the roles table which will contain roles name such as ROLE_USER, ROLE_ADMIN.
  • Next, we will extend the customers table which will link to the authorities table. So every customer will have one or many authorities.
  • User the SQL statement below to create authorities table.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-- roles definition

CREATE TABLE `roles` (
  `id` int NOT NULL AUTO_INCREMENT,
  `role` varchar(255) DEFAULT NULL,
  `customer_id` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `FKsxov1oh17vqdkhuxdiwpspo8i` (`customer_id`),
  CONSTRAINT `FKsxov1oh17vqdkhuxdiwpspo8i` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
  • Now for the current customer with email han.do@example.com in the customers table we will use the SQL statements below to set some authorities such as ROLE_USER and ROLE_ADMIN. So this customer will have 2 roles.
1
2
INSERT INTO worldbank.roles (role, customer_id) VALUES('ROLE_USER', 2);
INSERT INTO worldbank.roles (role, customer_id) VALUES('ROLE_ADMIN', 2);
  • Next, we will use the SQL statements below to insert a ROLE_USER for the customer with email john.wick@example.com.
1
INSERT INTO worldbank.roles (role, customer_id) VALUES('ROLE_USER', 3);
  • So, after all our table will have data as below:
  • customers table
id email password role
1 duc.nguyen@example.com 12345 admin
2 han.do@example.com $2a$12$V.A53NkiPnA45W44aRYi2OLwUbbu08aDoY409/SKY/bT7cdF1PpLO admin
3 john.wick@example.com $2a$12$V.A53NkiPnA45W44aRYi2OLwUbbu08aDoY409/SKY/bT7cdF1PpLO user
  • roles table
id role customer_id
1 ROLE_ADMIN 2
3 ROLE_USER 2
2 ROLE_USER 3

Entity#

  • So after updating database, we will also extend our CustomerEntity with RoleEntity. So in CustomerEntity we will have a set of RoleEntity with @OneToMany relationship.
CustomerEntity.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.spring.security.spring.security.configure.roles.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.Set;

@Entity
@Table(name = "customers")
@Getter
@Setter
public class CustomerEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;
    private String email;
    private String password;
    private String role;

    @OneToMany(mappedBy = "customer", fetch = FetchType.EAGER)
    private Set<AuthorityEntity> authorities;

    @OneToMany(mappedBy = "customer", fetch = FetchType.EAGER)
    private Set<RoleEntity> roles;

}
  • Then in we create the RoleEntity class as below:
RoleEntity.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.spring.security.spring.security.roles.authorities.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Table(name = "roles")
@Getter
@Setter
public class RoleEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "customer_id")
    private CustomerEntity customer;

    private String role;

}

Edit Authentication Provider#

  • In the Configure Authorities, we create the list of GrantedAuthority from Authorities which are loaded from authorities table in the database. Thus, we will update this code by getting the set of Roles from the table roles in database through CustomerEntity and map them to List<GrantedAuthority>.
  • The CustomerAuthenticationProvider will be changed as below:
CustomerAuthenticationProvider.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.spring.security.spring.security.configure.roles.config;

import com.spring.security.spring.security.configure.authorities.entity.CustomerEntity;
import com.spring.security.spring.security.configure.authorities.entity.RoleEntity;
import com.spring.security.spring.security.configure.authorities.repository.CustomerRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Component
public class CustomerAuthenticationProvider implements AuthenticationProvider {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomerAuthenticationProvider.class);

    @Autowired
    private CustomerRepository customerRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        LOGGER.info("CustomerAuthenticationProvider is triggered");
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();
        List<CustomerEntity> customerEntities = this.customerRepository.findByEmail(username);
        if (customerEntities.size() > 0) {
            if (passwordEncoder.matches(password, customerEntities.get(0).getPassword())) {
                return new UsernamePasswordAuthenticationToken(username, password,
                        this.getGrantedAuthorities(customerEntities.get(0).getRoles()));
            } else {
                throw new BadCredentialsException("Invalid Password");
            }
        }
        throw new BadCredentialsException("No user registered with this details");
    }

    private List<GrantedAuthority> getGrantedAuthorities(Set<RoleEntity> roleEntities) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        roleEntities.forEach(a -> authorities.add(new SimpleGrantedAuthority(a.getRole())));
        return authorities;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return authentication.equals(UsernamePasswordAuthenticationToken.class);
    }
}

Authorization Configuration#

  • In the ProjectSecurityConfig, we will update configure() method for applying Roles. We will use hasAnyRole() or hasRole() method after antMatchers() methods. In details, every antMatchers()(api path pattern) will need Roles which is matched as defined in hasRole() or hasAnyRole() to access.
  • Our configurations will look like below:
ProjectSecurityConfig.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.spring.security.spring.security.configure.roles.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

@Configuration
public class ProjectSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     *
     * contact: Not Secure
     * notice: Not Secure
     * balance: Secure
     * Card: Secure
     * Loan: Secure
     * Account: Secure
     *
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf()
                .ignoringAntMatchers("/v1/user")
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and().cors()
                .and().authorizeRequests()
                .antMatchers("/v1/user").hasAnyRole("USER", "ADMIN")
                .antMatchers("/v1/accounts/**").hasRole("USER")
                .antMatchers("/v1/balance").hasRole("USER")
                .antMatchers("/v1/loan").hasRole("ADMIN")
                .antMatchers("/v1/card").hasRole("ADMIN")
                .antMatchers("/v1/contact").permitAll()
                .antMatchers("/v1/notice").permitAll()
                .and().formLogin()
                .and().httpBasic();
    }

    @Bean
    protected CorsConfigurationSource corsConfigurationSource() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setAllowedOriginPatterns(Collections.singletonList("*"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowCredentials(true);
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
  • So now, only customer user with ROLE_ADMIN role can access the /v1/loan api, then for /v1/user any customer user which has one in 2 roles ROLE_ADMIN, ROLE_USER, can access this api.

Testing#

  • So, let's start our spring security application and call api v1/loan for testing, so this api will need the user with ROLE_ADMIN authorization to access, so the user with email han.do@example.com can access this api because it has 2 roles ROLE_ADMIN and ROLE_USER in database. The user with email john.wick@example.com can not access this api because it has only ROLE_READ role.
  • Using email han.do@example.com we can access v1/loan successfully.

 #zoom

  • Then using john.wick@example.com we will get the error code 403 forbidden.

 #zoom

  • Now, let's try to call api /v1/user, we expect that 2 users above can access this api because this api requires one of 2 roles, ROLE_ADMIN, ROLE_USER.

 #zoom

 #zoom

See Also#

References#