├── .gitignore ├── Procfile ├── src └── main │ ├── java │ └── com │ │ └── stormpath │ │ └── shiro │ │ └── samples │ │ └── springboot │ │ ├── controllers │ │ ├── NotFoundException.java │ │ └── StormtrooperController.java │ │ ├── common │ │ ├── model │ │ │ ├── ErrorMessage.java │ │ │ └── Stormtrooper.java │ │ └── dao │ │ │ ├── StormtrooperDao.java │ │ │ └── DefaultStormtrooperDao.java │ │ └── SpringBootApp.java │ └── resources │ ├── shiro-users.properties │ ├── application.properties │ └── logback.xml ├── app.json ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.iml 3 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: java $JAVA_OPTS -Dserver.port=$PORT -jar target/*.jar 2 | -------------------------------------------------------------------------------- /src/main/java/com/stormpath/shiro/samples/springboot/controllers/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.stormpath.shiro.samples.springboot.controllers; 2 | 3 | public class NotFoundException extends Exception { 4 | 5 | public NotFoundException(String id) { 6 | super(id); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/shiro-users.properties: -------------------------------------------------------------------------------- 1 | user.root = secret,admin 2 | user.emperor = secret,emperor 3 | user.officer = secret,officer 4 | user.jcoder = secret,underling 5 | user.guest = secret 6 | 7 | role.emperor = * 8 | role.admin = troopers:* 9 | role.officer = troopers:create, troopers:read, troopers:update 10 | role.underling = troopers:read -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ApacheShiroSpringBootExample", 3 | "description": "An example of using Apache Shiro Spring-Boot application.", 4 | "keywords": [ 5 | "shiro", 6 | "servlet", 7 | "spring", 8 | "java" 9 | ], 10 | "repository": "https://github.com/stormpath/shiro-spring-boot-example", 11 | "success_url": "/troopers" 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Apache Shiro Spring-Boot Example 2 | ================================ 3 | 4 | This example can be run using the Apache Maven command: `mvn spring-boot:run` 5 | 6 | Then browse or use cURL to: http://localhost:8080/troopers 7 | 8 | ``` bash 9 | curl --user emperor:secret http://localhost:8080/troopers 10 | ``` 11 | 12 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy) 13 | -------------------------------------------------------------------------------- /src/main/java/com/stormpath/shiro/samples/springboot/common/model/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package com.stormpath.shiro.samples.springboot.common.model; 2 | 3 | public class ErrorMessage { 4 | 5 | private String error; 6 | 7 | public ErrorMessage(String error) { 8 | this.error = error; 9 | } 10 | 11 | public String getError() { 12 | return error; 13 | } 14 | 15 | public void setError(String error) { 16 | this.error = error; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one 3 | # or more contributor license agreements. See the NOTICE file 4 | # distributed with this work for additional information 5 | # regarding copyright ownership. The ASF licenses this file 6 | # to you under the Apache License, Version 2.0 (the 7 | # "License"); you may not use this file except in compliance 8 | # with the License. You may obtain a copy of the License at 9 | # 10 | # http://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, 13 | # software distributed under the License is distributed on an 14 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | # KIND, either express or implied. See the License for the 16 | # specific language governing permissions and limitations 17 | # under the License. 18 | # 19 | 20 | shiro.loginUrl = /login.html 21 | 22 | # Let Shiro Manage the sessions 23 | shiro.userNativeSessionManager = true 24 | 25 | # disable URL session rewriting 26 | shiro.sessionManager.sessionIdUrlRewritingEnabled = false -------------------------------------------------------------------------------- /src/main/java/com/stormpath/shiro/samples/springboot/common/dao/StormtrooperDao.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Stormpath, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.stormpath.shiro.samples.springboot.common.dao; 17 | 18 | import com.stormpath.shiro.samples.springboot.common.model.Stormtrooper; 19 | 20 | import java.util.Collection; 21 | 22 | /** 23 | * Example CRUD DAO interface. 24 | */ 25 | public interface StormtrooperDao { 26 | 27 | Collection listStormtroopers(); 28 | 29 | Stormtrooper getStormtrooper(String id); 30 | 31 | Stormtrooper addStormtrooper(Stormtrooper stormtrooper); 32 | 33 | Stormtrooper updateStormtrooper(String id, Stormtrooper stormtrooper); 34 | 35 | boolean deleteStormtrooper(String id); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 24 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/main/java/com/stormpath/shiro/samples/springboot/common/model/Stormtrooper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Stormpath, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.stormpath.shiro.samples.springboot.common.model; 17 | 18 | public class Stormtrooper { 19 | 20 | private String id; 21 | private String planetOfOrigin; 22 | private String species; 23 | private String type; 24 | 25 | public Stormtrooper() { 26 | // empty to allow for bean access 27 | } 28 | 29 | public Stormtrooper(String id, String planetOfOrigin, String species, String type) { 30 | this.id = id; 31 | this.planetOfOrigin = planetOfOrigin; 32 | this.species = species; 33 | this.type = type; 34 | } 35 | 36 | public String getId() { 37 | return id; 38 | } 39 | 40 | public void setId(String id) { 41 | this.id = id; 42 | } 43 | 44 | public String getPlanetOfOrigin() { 45 | return planetOfOrigin; 46 | } 47 | 48 | public void setPlanetOfOrigin(String planetOfOrigin) { 49 | this.planetOfOrigin = planetOfOrigin; 50 | } 51 | 52 | public String getSpecies() { 53 | return species; 54 | } 55 | 56 | public void setSpecies(String species) { 57 | this.species = species; 58 | } 59 | 60 | public String getType() { 61 | return type; 62 | } 63 | 64 | public void setType(String type) { 65 | this.type = type; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/stormpath/shiro/samples/springboot/controllers/StormtrooperController.java: -------------------------------------------------------------------------------- 1 | package com.stormpath.shiro.samples.springboot.controllers; 2 | 3 | import com.stormpath.shiro.samples.springboot.common.dao.StormtrooperDao; 4 | import com.stormpath.shiro.samples.springboot.common.model.Stormtrooper; 5 | import org.apache.shiro.authz.annotation.RequiresPermissions; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.web.bind.annotation.DeleteMapping; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.ResponseStatus; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | import java.util.Collection; 19 | 20 | @RestController 21 | @RequestMapping(path = "/troopers", 22 | produces = MediaType.APPLICATION_JSON_UTF8_VALUE) 23 | public class StormtrooperController { 24 | 25 | private final StormtrooperDao trooperDao; 26 | 27 | @Autowired 28 | public StormtrooperController(StormtrooperDao trooperDao) { 29 | this.trooperDao = trooperDao; 30 | } 31 | 32 | @GetMapping() 33 | @RequiresPermissions("troopers:read") 34 | public Collection listTroopers() { 35 | return trooperDao.listStormtroopers(); 36 | } 37 | 38 | @GetMapping(path = "/{id}") 39 | @RequiresPermissions("troopers:read") 40 | public Stormtrooper getTrooper(@PathVariable("id") String id) throws NotFoundException { 41 | 42 | Stormtrooper stormtrooper = trooperDao.getStormtrooper(id); 43 | if (stormtrooper == null) { 44 | throw new NotFoundException(id); 45 | } 46 | return stormtrooper; 47 | } 48 | 49 | @PostMapping() 50 | @RequiresPermissions("troopers:create") 51 | public Stormtrooper createTrooper(@RequestBody Stormtrooper trooper) { 52 | 53 | return trooperDao.addStormtrooper(trooper); 54 | } 55 | 56 | @PostMapping(path = "/{id}") 57 | @RequiresPermissions("troopers:update") 58 | public Stormtrooper updateTrooper(@PathVariable("id") String id, @RequestBody Stormtrooper updatedTrooper) throws NotFoundException { 59 | 60 | return trooperDao.updateStormtrooper(id, updatedTrooper); 61 | } 62 | 63 | 64 | @DeleteMapping(path = "/{id}") 65 | @ResponseStatus(value = HttpStatus.NO_CONTENT) 66 | @RequiresPermissions("troopers:delete") 67 | public void deleteTrooper(@PathVariable("id") String id) { 68 | trooperDao.deleteStormtrooper(id); 69 | } 70 | 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/stormpath/shiro/samples/springboot/SpringBootApp.java: -------------------------------------------------------------------------------- 1 | package com.stormpath.shiro.samples.springboot; 2 | 3 | import com.stormpath.shiro.samples.springboot.common.dao.DefaultStormtrooperDao; 4 | import com.stormpath.shiro.samples.springboot.common.dao.StormtrooperDao; 5 | import com.stormpath.shiro.samples.springboot.common.model.ErrorMessage; 6 | import com.stormpath.shiro.samples.springboot.controllers.NotFoundException; 7 | import org.apache.shiro.authz.AuthorizationException; 8 | import org.apache.shiro.authz.UnauthenticatedException; 9 | import org.apache.shiro.cache.CacheManager; 10 | import org.apache.shiro.cache.MemoryConstrainedCacheManager; 11 | import org.apache.shiro.realm.Realm; 12 | import org.apache.shiro.realm.text.PropertiesRealm; 13 | import org.apache.shiro.realm.text.TextConfigurationRealm; 14 | import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition; 15 | import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import org.springframework.boot.SpringApplication; 19 | import org.springframework.boot.autoconfigure.SpringBootApplication; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.http.HttpStatus; 22 | import org.springframework.web.bind.annotation.ControllerAdvice; 23 | import org.springframework.web.bind.annotation.ExceptionHandler; 24 | import org.springframework.web.bind.annotation.ResponseBody; 25 | import org.springframework.web.bind.annotation.ResponseStatus; 26 | 27 | @ControllerAdvice 28 | @SpringBootApplication 29 | public class SpringBootApp { 30 | 31 | private static Logger log = LoggerFactory.getLogger(SpringBootApp.class); 32 | 33 | public static void main(String[] args) { 34 | SpringApplication.run(SpringBootApp.class, args); 35 | } 36 | 37 | @ExceptionHandler(UnauthenticatedException.class) 38 | @ResponseStatus(HttpStatus.UNAUTHORIZED) 39 | public void handleException(UnauthenticatedException e) { 40 | log.debug("{} was thrown", e.getClass(), e); 41 | } 42 | 43 | @ExceptionHandler(AuthorizationException.class) 44 | @ResponseStatus(HttpStatus.FORBIDDEN) 45 | public void handleException(AuthorizationException e) { 46 | log.debug("{} was thrown", e.getClass(), e); 47 | } 48 | 49 | @ExceptionHandler(NotFoundException.class) 50 | @ResponseStatus(HttpStatus.NOT_FOUND) 51 | public @ResponseBody ErrorMessage handleException(NotFoundException e) { 52 | String id = e.getMessage(); 53 | return new ErrorMessage("Trooper Not Found: "+ id +", why aren't you at your post? "+ id +", do you copy?"); 54 | } 55 | 56 | @Bean 57 | protected StormtrooperDao stormtrooperDao() { 58 | return new DefaultStormtrooperDao(); 59 | } 60 | 61 | @Bean 62 | public Realm realm() { 63 | 64 | // uses 'classpath:shiro-users.properties' by default 65 | PropertiesRealm realm = new PropertiesRealm(); 66 | 67 | // Caching isn't needed in this example, but we can still turn it on 68 | realm.setCachingEnabled(true); 69 | return realm; 70 | } 71 | 72 | @Bean 73 | public ShiroFilterChainDefinition shiroFilterChainDefinition() { 74 | DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition(); 75 | // use permissive to NOT require authentication, our controller Annotations will decide that 76 | chainDefinition.addPathDefinition("/**", "authcBasic[permissive]"); 77 | return chainDefinition; 78 | } 79 | 80 | @Bean 81 | public CacheManager cacheManager() { 82 | // Caching isn't needed in this example, but we will use the MemoryConstrainedCacheManager for this example. 83 | return new MemoryConstrainedCacheManager(); 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/stormpath/shiro/samples/springboot/common/dao/DefaultStormtrooperDao.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Stormpath, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.stormpath.shiro.samples.springboot.common.dao; 17 | 18 | import com.stormpath.shiro.samples.springboot.common.model.Stormtrooper; 19 | 20 | import java.security.SecureRandom; 21 | import java.util.Collection; 22 | import java.util.Collections; 23 | import java.util.Map; 24 | import java.util.Random; 25 | import java.util.TreeMap; 26 | 27 | 28 | /** 29 | * Dummy DAO that will generate 50 random Stormtroopers upon creation. 30 | */ 31 | public final class DefaultStormtrooperDao implements StormtrooperDao { 32 | 33 | final static private String[] trooperTypes = {"Basic", "Space", "Aquatic", "Marine", "Jump", "Sand"}; 34 | final static private String[] planetsList = {"Coruscant", "Tatooine", "Felucia", "Hoth", "Naboo", "Serenno"}; 35 | final static private String[] speciesList = {"Human", "Kel Dor", "Nikto", "Twi'lek", "Unidentified"}; 36 | final static private Random RANDOM = new SecureRandom(); 37 | 38 | final private Map trooperMap = Collections.synchronizedSortedMap(new TreeMap()); 39 | 40 | public DefaultStormtrooperDao() { 41 | for (int i = 0; i < 50; i++) { 42 | addStormtrooper(randomTrooper()); 43 | } 44 | } 45 | 46 | @Override 47 | public Collection listStormtroopers() { 48 | return Collections.unmodifiableCollection(trooperMap.values()); 49 | } 50 | 51 | @Override 52 | public Stormtrooper getStormtrooper(String id) { 53 | return trooperMap.get(id); 54 | } 55 | 56 | @Override 57 | public Stormtrooper addStormtrooper(Stormtrooper stormtrooper) { 58 | if (stormtrooper.getId() == null || stormtrooper.getId().trim().isEmpty()) { 59 | stormtrooper.setId(generateRandomId()); 60 | } 61 | trooperMap.put(stormtrooper.getId(), stormtrooper); 62 | return stormtrooper; 63 | } 64 | 65 | @Override 66 | public Stormtrooper updateStormtrooper(String id, Stormtrooper stormtrooper) { 67 | // we are just backing with a map, so just call add. 68 | return addStormtrooper(stormtrooper); 69 | } 70 | 71 | @Override 72 | public boolean deleteStormtrooper(String id) { 73 | return trooperMap.remove(id) != null; 74 | } 75 | 76 | 77 | /////////////////////////////////// 78 | // Dummy data generating below // 79 | /////////////////////////////////// 80 | 81 | private static Stormtrooper randomTrooper(String id) { 82 | String planet = planetsList[RANDOM.nextInt(planetsList.length)]; 83 | String species = speciesList[RANDOM.nextInt(speciesList.length)]; 84 | String type = trooperTypes[RANDOM.nextInt(trooperTypes.length)]; 85 | 86 | return new Stormtrooper(id, planet, species, type); 87 | } 88 | 89 | private static String generateRandomId() { 90 | // HIGH chance of collisions, but, this is all for fun... 91 | return "FN-" + String.format("%04d", RANDOM.nextInt(9999)); 92 | } 93 | 94 | private static Stormtrooper randomTrooper() { 95 | return randomTrooper(generateRandomId()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 20 | 21 | 22 | 23 | 4.0.0 24 | com.stormpath.example 25 | samples-spring-boot 26 | 0.1.0-SNAPSHOT 27 | Apache Shiro :: Samples :: Spring-Boot 28 | 29 | 30 | 31 | 32 | 1.4.1.RELEASE 33 | 1.4.0 34 | 35 | 1.8 36 | 1.8 37 | UTF-8 38 | 39 | 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-maven-plugin 45 | ${spring-boot.version} 46 | 47 | 48 | 49 | repackage 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.apache.shiro 61 | shiro-spring-boot-web-starter 62 | ${shiro.version} 63 | 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-autoconfigure 69 | ${spring-boot.version} 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-configuration-processor 74 | ${spring-boot.version} 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | --------------------------------------------------------------------------------