Created
January 5, 2017 17:00
-
-
Save thomasdarimont/09c8c95dba3dde223ddaafff1ec36612 to your computer and use it in GitHub Desktop.
Simple AuthN & AuthZ example with Spring Boot / Security / Session
This file contains 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 characters
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 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 characters
server.port=17777 |
This file contains 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 characters
<?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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment