{{announcement.body}}
{{announcement.title}}

Securing Your Microservices With a Declarative Model — Part Three

DZone 's Guide to

Securing Your Microservices With a Declarative Model — Part Three

In this article, we'll learn Adding Method Access Rules using the Spring Security OAuth2 example.

· Microservices Zone ·
Free Resource

In this example, we'll learn Adding Method Access Rules using the Spring Security OAuth2 example. This tutorial is a continuation of my previous article https://dzone.com/articles/securing-your-microservices-with-a-declarative-mod-1.

Spring Security provides method level security using @PreAuthorize and @PostAuthorize annotations. This is expression-based access control.
The @PreAuthorize can check for authorization before entering the method. The @PreAuthorize authorizes the basis of the role or the argument which is passed to the method.
The @PostAuthorize checks for authorization after method execution. The @PostAuthorize authorizes based on logged in roles, return object by the method, and passed argument to the method. For the returned object spring security provides built-in keyword i.e. returnObject

Here we have used @PreAuthorize  annotation on  getTollData()  method. The # is used to access the argument of the method.   "#oauth2.hasScope('toll_read') and hasAuthority('ROLE_OPERATOR')"  checks if the user has the appropriate scope to test this method also checks if the user has  hasAuthority  or possess the required role to access this method. 

Auth Server

ServiceConfig.java

Java
 




x
19


1
package com.example.demo;
2
 
          
3
import org.springframework.context.annotation.Configuration;
4
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
5
import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter;
6
 
          
7
@Configuration
8
public class ServiceConfig extends GlobalAuthenticationConfigurerAdapter {
9
 
          
10
    @Override
11
    public void init(AuthenticationManagerBuilder auth) throws Exception {
12
        auth.inMemoryAuthentication()
13
            .withUser("alpha").password("{noop}pass1").roles("USER")
14
            .and()
15
            .withUser("beta").password("{noop}pass2").roles("USER", "OPERATOR")
16
            .and()
17
            .withUser("javaHelper").password("{noop}javaHelpersecret").roles("USER", "OPERATOR");
18
    }
19
}



DsSpringCloudM4AuthserverApplication.java

Java
 




xxxxxxxxxx
1
26


1
package com.example.demo;
2
 
          
3
import java.security.Principal;
4
 
          
5
import org.springframework.boot.SpringApplication;
6
import org.springframework.boot.autoconfigure.SpringBootApplication;
7
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
8
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
9
import org.springframework.web.bind.annotation.GetMapping;
10
import org.springframework.web.bind.annotation.RestController;
11
 
          
12
@SpringBootApplication
13
@EnableAuthorizationServer
14
@EnableResourceServer
15
@RestController
16
public class DsSpringCloudM4AuthserverApplication {
17
 
          
18
    public static void main(String[] args) {
19
        SpringApplication.run(DsSpringCloudM4AuthserverApplication.class, args);
20
    }
21
 
          
22
    @GetMapping(value = "/user")
23
    public Principal user(Principal user) {
24
        return user;
25
    }
26
}



Pom.xml

Java
 




xxxxxxxxxx
1
78


1
<?xml version="1.0" encoding="UTF-8"?>
2
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4
    <modelVersion>4.0.0</modelVersion>
5
    <parent>
6
        <groupId>org.springframework.boot</groupId>
7
        <artifactId>spring-boot-starter-parent</artifactId>
8
        <version>2.2.6.RELEASE</version>
9
        <relativePath /> <!-- lookup parent from repository -->
10
    </parent>
11
    <groupId>com.example</groupId>
12
    <artifactId>ds-spring-cloud-m4-authserver</artifactId>
13
    <version>0.0.1-SNAPSHOT</version>
14
    <name>ds-spring-cloud-m4-authserver</name>
15
    <description>Demo project for Spring Boot</description>
16
 
          
17
    <properties>
18
        <java.version>1.8</java.version>
19
        <spring-cloud.version>Hoxton.SR3</spring-cloud.version>
20
        <maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
21
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
22
    </properties>
23
 
          
24
    <dependencies>
25
        <dependency>
26
            <groupId>org.springframework.boot</groupId>
27
            <artifactId>spring-boot-starter-security</artifactId>
28
        </dependency>
29
        <dependency>
30
            <groupId>org.springframework.boot</groupId>
31
            <artifactId>spring-boot-starter-web</artifactId>
32
        </dependency>
33
        <dependency>
34
            <groupId>org.springframework.cloud</groupId>
35
            <artifactId>spring-cloud-starter-oauth2</artifactId>
36
        </dependency>
37
 
          
38
        <dependency>
39
            <groupId>org.springframework.boot</groupId>
40
            <artifactId>spring-boot-starter-test</artifactId>
41
            <scope>test</scope>
42
            <exclusions>
43
                <exclusion>
44
                    <groupId>org.junit.vintage</groupId>
45
                    <artifactId>junit-vintage-engine</artifactId>
46
                </exclusion>
47
            </exclusions>
48
        </dependency>
49
        <dependency>
50
            <groupId>org.springframework.security</groupId>
51
            <artifactId>spring-security-test</artifactId>
52
            <scope>test</scope>
53
        </dependency>
54
    </dependencies>
55
 
          
56
    <dependencyManagement>
57
        <dependencies>
58
            <dependency>
59
                <groupId>org.springframework.cloud</groupId>
60
                <artifactId>spring-cloud-dependencies</artifactId>
61
                <version>${spring-cloud.version}</version>
62
                <type>pom</type>
63
                <scope>import</scope>
64
            </dependency>
65
        </dependencies>
66
    </dependencyManagement>
67
 
          
68
    <build>
69
        <plugins>
70
            <plugin>
71
                <groupId>org.springframework.boot</groupId>
72
                <artifactId>spring-boot-maven-plugin</artifactId>
73
            </plugin>
74
        </plugins>
75
    </build>
76
 
          
77
</project>
78
 
          



Start the Server.

Secure Service

TollUsage.java

Java
 




xxxxxxxxxx
1
17


1
public class TollUsage {
2
    public String Id;
3
    public String stationId;
4
    public String licensePlate;
5
    public String timestamp;
6
 
          
7
    public TollUsage() {
8
    }
9
 
          
10
    public TollUsage(String id, String stationid, String licenseplate, String timestamp) {
11
        this.Id = id;
12
        this.stationId = stationid;
13
        this.licensePlate = licenseplate;
14
        this.timestamp = timestamp;
15
    }
16
 
          
17
}



SecurityConfig.java

Java
 




xxxxxxxxxx
1
16


1
package com.example.demo;
2
 
          
3
import org.springframework.context.annotation.Configuration;
4
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
5
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
6
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
7
import org.springframework.security.oauth2.provider.expression.OAuth2MethodSecurityExpressionHandler;
8
 
          
9
@Configuration
10
@EnableGlobalMethodSecurity(prePostEnabled = true)
11
public class SecurityConfig extends GlobalMethodSecurityConfiguration {
12
    
13
    @Override
14
    protected MethodSecurityExpressionHandler createExpressionHandler() {
15
        return new OAuth2MethodSecurityExpressionHandler();
16
    }
17
}



TollUsageController.java

Java
 




xxxxxxxxxx
1
37


1
package com.example.demo;
2
 
          
3
import java.util.ArrayList;
4
import java.util.List;
5
 
          
6
import org.springframework.beans.factory.annotation.Autowired;
7
import org.springframework.boot.autoconfigure.security.oauth2.resource.ResourceServerProperties;
8
import org.springframework.context.annotation.Bean;
9
import org.springframework.security.access.prepost.PreAuthorize;
10
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
11
import org.springframework.web.bind.annotation.GetMapping;
12
import org.springframework.web.bind.annotation.RestController;
13
 
          
14
@RestController
15
public class TollUsageController {
16
    @Autowired
17
    private ResourceServerProperties sso;
18
    
19
    @Bean
20
    public ResourceServerTokenServices myUserInfoTokenServices() {
21
        return new CustomUserInfoTokenServices(sso.getUserInfoUri(), sso.getClientId());
22
    }
23
    
24
    @GetMapping("/tolldata")
25
    @PreAuthorize("#oauth2.hasScope('toll_read') and hasAuthority('ROLE_OPERATOR')")
26
    public List<TollUsage> getTollData() {
27
        TollUsage instance1 = new TollUsage("200", "station150", "B65GT1W", "2016-09-30T06:31:22");
28
        TollUsage instance2 = new TollUsage("201", "station119", "AHY673B", "2016-09-30T06:32:50");
29
        TollUsage instance3 = new TollUsage("202", "station150", "ZN2GP0", "2016-09-30T06:37:01");
30
        
31
        ArrayList<TollUsage> tolls = new ArrayList<>();
32
        tolls.add(instance1);
33
        tolls.add(instance2);
34
        tolls.add(instance3);
35
        return tolls;
36
    }
37
}



CustomUserInfoTokenServices.java

Java
 




xxxxxxxxxx
1
137


1
package com.example.demo;
2
 
          
3
import java.util.Collection;
4
import java.util.Collections;
5
import java.util.HashSet;
6
import java.util.LinkedHashSet;
7
import java.util.List;
8
import java.util.Map;
9
import java.util.Set;
10
 
          
11
import org.apache.commons.logging.Log;
12
import org.apache.commons.logging.LogFactory;
13
import org.springframework.boot.autoconfigure.security.oauth2.resource.AuthoritiesExtractor;
14
import org.springframework.boot.autoconfigure.security.oauth2.resource.FixedAuthoritiesExtractor;
15
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
16
import org.springframework.security.core.AuthenticationException;
17
import org.springframework.security.core.GrantedAuthority;
18
import org.springframework.security.oauth2.client.OAuth2RestOperations;
19
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
20
import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
21
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
22
import org.springframework.security.oauth2.common.OAuth2AccessToken;
23
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
24
import org.springframework.security.oauth2.provider.OAuth2Authentication;
25
import org.springframework.security.oauth2.provider.OAuth2Request;
26
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
27
 
          
28
public class CustomUserInfoTokenServices implements ResourceServerTokenServices {
29
 
          
30
    protected final Log logger = LogFactory.getLog(getClass());
31
 
          
32
    private static final String[] PRINCIPAL_KEYS = new String[] { "user", "username",
33
            "userid", "user_id", "login", "id", "name" };
34
 
          
35
    private final String userInfoEndpointUrl;
36
 
          
37
    private final String clientId;
38
 
          
39
    private OAuth2RestOperations restTemplate;
40
 
          
41
    private String tokenType = DefaultOAuth2AccessToken.BEARER_TYPE;
42
 
          
43
    private AuthoritiesExtractor authoritiesExtractor = new FixedAuthoritiesExtractor();
44
 
          
45
    public CustomUserInfoTokenServices(String userInfoEndpointUrl, String clientId) {
46
        this.userInfoEndpointUrl = userInfoEndpointUrl;
47
        this.clientId = clientId;
48
    }
49
 
          
50
    public void setTokenType(String tokenType) {
51
        this.tokenType = tokenType;
52
    }
53
 
          
54
    public void setRestTemplate(OAuth2RestOperations restTemplate) {
55
        this.restTemplate = restTemplate;
56
    }
57
 
          
58
    public void setAuthoritiesExtractor(AuthoritiesExtractor authoritiesExtractor) {
59
        this.authoritiesExtractor = authoritiesExtractor;
60
    }
61
 
          
62
    @Override
63
    public OAuth2Authentication loadAuthentication(String accessToken)
64
            throws AuthenticationException, InvalidTokenException {
65
        Map<String, Object> map = getMap(this.userInfoEndpointUrl, accessToken);
66
        if (map.containsKey("error")) {
67
            this.logger.debug("userinfo returned error: " + map.get("error"));
68
            throw new InvalidTokenException(accessToken);
69
        }
70
        return extractAuthentication(map);
71
    }
72
 
          
73
    private OAuth2Authentication extractAuthentication(Map<String, Object> map) {
74
        Object principal = getPrincipal(map);
75
        OAuth2Request request = getRequest(map);
76
        List<GrantedAuthority> authorities = this.authoritiesExtractor
77
                .extractAuthorities(map);
78
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
79
                principal, "N/A", authorities);
80
        token.setDetails(map);
81
        return new OAuth2Authentication(request, token);
82
    }
83
 
          
84
    private Object getPrincipal(Map<String, Object> map) {
85
        for (String key : PRINCIPAL_KEYS) {
86
            if (map.containsKey(key)) {
87
                return map.get(key);
88
            }
89
        }
90
        return "unknown";
91
    }
92
 
          
93
    @SuppressWarnings({ "unchecked" })
94
    private OAuth2Request getRequest(Map<String, Object> map) {
95
        Map<String, Object> request = (Map<String, Object>) map.get("oauth2Request");
96
 
          
97
        String clientId = (String) request.get("clientId");
98
        Set<String> scope = new LinkedHashSet<>(request.containsKey("scope") ?
99
                (Collection<String>) request.get("scope") : Collections.<String>emptySet());
100
 
          
101
        return new OAuth2Request(null, clientId, null, true, new HashSet<>(scope),
102
                null, null, null, null);
103
    }
104
 
          
105
    @Override
106
    public OAuth2AccessToken readAccessToken(String accessToken) {
107
        throw new UnsupportedOperationException("Not supported: read access token");
108
    }
109
 
          
110
    @SuppressWarnings({ "unchecked" })
111
    private Map<String, Object> getMap(String path, String accessToken) {
112
        this.logger.info("Getting user info from: " + path);
113
        try {
114
            OAuth2RestOperations restTemplate = this.restTemplate;
115
            if (restTemplate == null) {
116
                BaseOAuth2ProtectedResourceDetails resource = new BaseOAuth2ProtectedResourceDetails();
117
                resource.setClientId(this.clientId);
118
                restTemplate = new OAuth2RestTemplate(resource);
119
            }
120
            OAuth2AccessToken existingToken = restTemplate.getOAuth2ClientContext()
121
                    .getAccessToken();
122
            if (existingToken == null || !accessToken.equals(existingToken.getValue())) {
123
                DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(
124
                        accessToken);
125
                token.setTokenType(this.tokenType);
126
                restTemplate.getOAuth2ClientContext().setAccessToken(token);
127
            }
128
            return restTemplate.getForEntity(path, Map.class).getBody();
129
        }
130
        catch (Exception ex) {
131
            this.logger.info("Could not fetch user details: " + ex.getClass() + ", "
132
                    + ex.getMessage());
133
            return Collections.<String, Object>singletonMap("error",
134
                    "Could not fetch user details");
135
        }
136
    }
137
}



DsSpringCloudM4SecurecliApplication.java

Java
 




xxxxxxxxxx
1


1
@SpringBootApplication
2
@EnableResourceServer
3
public class DsSpringCloudM4SecurecliApplication {
4
 
          
5
    public static void main(String[] args) {
6
        SpringApplication.run(DsSpringCloudM4SecurecliApplication.class, args);
7
    }
8
}



Start the server.

Testing

Make the POST  http://localhost:9000/services/oauth/token  

localhost

localhost

Now check the fine-grain access —

Having "Invalid Scope" for that user 

localhost

"invalid authority"

Having an "Invalid Authority" to the user.

localhost screenshot

localhost screenshot

Topics:
auth server ,java ,microservices ,spring security oauth ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}