Skip to content

Instantly share code, notes, and snippets.

@thomasdarimont
Created January 5, 2017 17:00
Show Gist options
  • Select an option

  • Save thomasdarimont/09c8c95dba3dde223ddaafff1ec36612 to your computer and use it in GitHub Desktop.

Select an option

Save thomasdarimont/09c8c95dba3dde223ddaafff1ec36612 to your computer and use it in GitHub Desktop.

Revisions

  1. thomasdarimont created this gist Jan 5, 2017.
    236 changes: 236 additions & 0 deletions App.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,236 @@
    package demo;

    import java.io.Serializable;
    import java.security.Principal;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.UUID;

    import javax.servlet.http.HttpSession;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.access.PermissionEvaluator;
    import org.springframework.security.access.annotation.Secured;
    import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
    import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
    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.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.annotation.AuthenticationPrincipal;
    import org.springframework.security.web.savedrequest.NullRequestCache;
    import org.springframework.session.ExpiringSession;
    import org.springframework.session.MapSessionRepository;
    import org.springframework.session.SessionRepository;
    import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
    import org.springframework.session.web.http.HeaderHttpSessionStrategy;
    import org.springframework.session.web.http.HttpSessionStrategy;
    import org.springframework.stereotype.Component;
    import org.springframework.util.CollectionUtils;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    import lombok.Data;
    import lombok.Value;
    import lombok.extern.slf4j.Slf4j;

    /**
    * <pre>
    * {@code
    * export BASE_URL=http://localhost:17777
    *
    * curl --noproxy localhost -u user:password -v $BASE_URL/api/auth
    * export AUTH_TOKEN=...
    * curl --noproxy localhost -H "x-auth-token: $AUTH_TOKEN" -v $BASE_URL/api/greet
    * curl --noproxy localhost -H "x-auth-token: $AUTH_TOKEN" -v -d "amount=42.0" $BASE_URL/api/order
    * curl --noproxy localhost -H "x-auth-token: $AUTH_TOKEN" -v -d "amount=1000.0" $BASE_URL/api/order
    *
    * curl --noproxy localhost -u admin:password -v $BASE_URL/api/auth
    * export AUTH_TOKEN=...
    * curl --noproxy localhost -H "x-auth-token: $AUTH_TOKEN" -v -d "amount=1000.0" $BASE_URL/api/order
    * }
    * </pre>
    *
    */
    @SpringBootApplication
    public class App {

    public static void main(String[] args) {
    SpringApplication.run(App.class, args);
    }
    }

    @Configuration
    @EnableSpringHttpSession
    class HttpSessionConfig {

    @Bean
    SessionRepository<ExpiringSession> inmemorySessionRepository() {
    return new MapSessionRepository();
    }

    @Bean
    HttpSessionStrategy httpSessionStrategy() {
    return new HeaderHttpSessionStrategy();
    }
    }

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    class SecurityConfig extends GlobalMethodSecurityConfiguration {

    @Autowired
    private DomainAwarePermissionEvaluator permissionEvaluator;

    @Autowired
    private ApplicationContext applicationContext;

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {

    DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
    expressionHandler.setPermissionEvaluator(permissionEvaluator);
    expressionHandler.setApplicationContext(applicationContext);

    return expressionHandler;
    }
    }

    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests() //
    .anyRequest().authenticated() //
    .and().requestCache().requestCache(new NullRequestCache()) //
    .and().httpBasic() //
    .and().csrf().disable();
    }

    @Autowired
    void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication() //
    .withUser("user").password("password").authorities("ROLE_USER") //
    .and() //
    .withUser("admin").password("password").authorities("ROLE_USER", "ROLE_ADMIN");
    }
    }

    @Slf4j
    @Component
    class DomainAwarePermissionEvaluator implements PermissionEvaluator {

    @Override
    public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {

    log.info("check permission '{}' for user '{}' for target '{}'", permission, authentication.getName(),
    targetDomainObject);

    if ("place-order".equals(permission)) {
    Order order = (Order) targetDomainObject;
    if (order.getAmount() > 500) {
    return hasRole("ROLE_ADMIN", authentication);
    }
    }

    return true;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType,
    Object permission) {
    return hasPermission(authentication, new DomainObjectReference(targetId, targetType), permission);
    }

    private boolean hasRole(String role, Authentication auth) {

    if (auth == null || auth.getPrincipal() == null) {
    return false;
    }

    Collection<? extends GrantedAuthority> authorities = auth.getAuthorities();

    if (CollectionUtils.isEmpty(authorities)) {
    return false;
    }

    return authorities.stream().filter(ga -> role.equals(ga.getAuthority())).findAny().isPresent();
    }

    @Value
    static class DomainObjectReference {
    private final Serializable targetId;
    private final String targetType;
    }
    }

    @RequestMapping("/api/auth")
    @RestController
    class AuthEndpoint {

    @GetMapping
    Map<String, Object> getToken(HttpSession session) {
    return Collections.singletonMap("session", session.getId());
    }
    }

    @Secured("ROLE_USER")
    @RequestMapping("/api/greet")
    @RestController
    class GreetingEndpoint {

    @GetMapping
    Map<String, Object> greet(@AuthenticationPrincipal Principal user) {

    Map<String, Object> map = new HashMap<>();
    map.put("user", user.getName());
    return map;
    }
    }

    @Secured("ROLE_USER")
    @RequestMapping("/api/order")
    @RestController
    class OrderEndpoint {

    @PostMapping
    @PreAuthorize("hasPermission(#order, 'place-order')")
    Map<String, Object> newOrder(Order order) {

    Map<String, Object> map = new HashMap<>();
    map.put("orderId", UUID.randomUUID());
    return map;
    }
    }

    @Data
    class Order {
    double amount;
    }

    @RequestMapping("/api/admin")
    @RestController
    class AdminEndpoint {

    @GetMapping
    @Secured("ROLE_ADMIN")
    Map<String, Object> manage(@AuthenticationPrincipal Principal user) {
    return Collections.singletonMap("user", user.getName());
    }
    }
    1 change: 1 addition & 0 deletions application.properties
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    server.port=17777
    68 changes: 68 additions & 0 deletions pom.xml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,68 @@
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>de.tdlabs</groupId>
    <artifactId>spring-boot-secure-rest-api-example</artifactId>
    <version>0.0.1.BUILD-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-boot-secure-rest-api-example</name>
    <description>Demo project for Spring Boot</description>

    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.4.3.RELEASE</version>
    <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
    </properties>

    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session</artifactId>
    </dependency>

    <dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
    </dependency>
    </dependencies>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>
    </project>