Skip to content

Configure Authorities#

Supported Methods#

  • In Spring Security the authorities of the user can be configured and validated using the following methods:
Method Description
hasAuthority() Accepts a single authority for which the endpoint will be configured and user will be validated against the single authority mentioned. Only users having the same authority configured can call the endpoint.
hasAnyAuthority() Accepts multiple authorities for which the endpoint will be configured and user will be validated against the authorities mentioned. Only users having any of the authority configured can call the endpoint.
access() Using Spring Expression Language (SpEL) it provides you unlimited possibilities for configuring authorities which are not possible which the above methods. We can use operators like OR, AND inside access() method.

Example Configurations#

Database Tables#

  • We will create the authorities table which will contain authorities name such as READ, WRITE, DELETE.
  • 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
-- authorities definition

CREATE TABLE `authorities` (
  `id` int NOT NULL AUTO_INCREMENT,
  `authority` varchar(255) NOT NULL,
  `customer_id` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `customer_id` (`customer_id`),
  CONSTRAINT `authorities_customer_constraint` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 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 READ, WRITE and DELETE. So this customer will have 3 authorities.
1
2
3
INSERT INTO worldbank.authorities (authority, customer_id) VALUES('READ', 2);
INSERT INTO worldbank.authorities (authority, customer_id) VALUES('WRITE', 2);
INSERT INTO worldbank.authorities (authority, customer_id) VALUES('DELETE', 2);
  • Next, we will use the SQL statements below to create a new customer and only set one authority READ for it.
1
2
3
4
5
INSERT INTO worldbank.customers
(id, email, password, `role`)
VALUES(3, 'john.wick@example.com', '$2a$12$V.A53NkiPnA45W44aRYi2OLwUbbu08aDoY409/SKY/bT7cdF1PpLO', 'user');

INSERT INTO worldbank.authorities (authority, customer_id) VALUES('READ', 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
  • authorities table:
id authority customer_id
6 READ 2
7 WRITE 2
8 DELETE 2
9 READ 3

Entity#

  • So after updating database, we will also extend our CustomerEntity with AuthorityEntity. So in CustomerEntity we will have a set of AuthorityEntity 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
package com.spring.security.spring.security.configure.authorities.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;

}
  • Then in we create the AuthorityEntity class as below:
AuthorityEntity.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
package com.spring.security.spring.security.configure.authorities.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;

@Entity
@Table(name = "authorities")
@Getter
@Setter
public class AuthorityEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

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

    private String authority;

}

Edit Authentication Provider#

  • Now, we are changing our database and Entities which are used for Authentication. In the Custom Authentication Provider, we create the list of GrantedAuthority as empty mean mean there are no Authority. Thus, we will update this code by getting the set of Authorities from the 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.authorities.config;

import com.spring.security.spring.security.configure.authorities.entity.AuthorityEntity;
import com.spring.security.spring.security.configure.authorities.entity.CustomerEntity;
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).getAuthorities()));
            } else {
                throw new BadCredentialsException("Invalid Password");
            }
        }
        throw new BadCredentialsException("No user registered with this details");
    }

    private List<GrantedAuthority> getGrantedAuthorities(Set<AuthorityEntity> authorityEntities) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorityEntities.forEach(a -> authorities.add(new SimpleGrantedAuthority(a.getAuthority())));
        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 Authorities. We will use hasAnyAuthority() or hasAuthority() method after antMatchers() methods. In details, every antMatchers()(api path pattern) will need Authorities which is matched as defined in hasAuthority() or hasAnyAuthority() 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.authorities.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").hasAnyAuthority("READ", "WRITE", "DELETE")
                .antMatchers("/v1/accounts/**").hasAnyAuthority("WRITE", "DELETE")
                .antMatchers("/v1/balance").hasAuthority("READ")
                .antMatchers("/v1/loan").hasAuthority("WRITE")
                .antMatchers("/v1/card").hasAuthority("DELETE")
                .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 DELETE authority can access the /v1/card api, then for /v1/user any customer user which has one in 3 authorities READ, WRITE, DELETE 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 READ authorization to access, so the user with email han.do@example.com can access this api because it has 3 authorities READ, WRITE and DELETE in database. The user with email john.wick@example.com can not access this api because it has only READ authority.
  • 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 3 authorities, READ, WRITE and DELETE.

 #zoom

 #zoom

See Also#

References#