Created
January 5, 2017 17:00
-
-
Save thomasdarimont/09c8c95dba3dde223ddaafff1ec36612 to your computer and use it in GitHub Desktop.
Revisions
-
thomasdarimont created this gist
Jan 5, 2017 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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()); } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1 @@ server.port=17777 This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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>