- Оригинальный адрес:Protecting a Spring Boot App With Apache Shiro
- Оригинальный автор:Brian Demers
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:lihanxiang
- Корректор:Mirosalva,HearFishle
Одна из вещей, которые мне больше всего нравятся в Apache Shiro, — это простота управления поведением авторизации приложения. Вы можете использовать модель управления доступом на основе ролей, чтобы назначать роли пользователям и назначать разрешения ролям. Это позволяет легко справиться с некоторым неизбежным поведением. Вам не нужно менять код, просто измените разрешения роли. В этом посте я хочу показать, как легко использовать программу Spring Boot, чтобы показать, как я справляюсь со следующими сценариями:
Ваш начальник (Верховный главнокомандующий) появляется за вашим столом и сообщает вам, что текущее приложение регистрации добровольца (солдата) требует разных разрешений для назначения разным категориям сотрудников.
- Офицеры могут регистрировать вновь добавленныхволонтер
- Подчиненные (такие люди, как вы и я) имеют право только читать волонтерские материалы
- организацияНикто извне не может получить доступволонтерданные
- Излишне говорить, что у босса есть все разрешения
Начните с приложения REST
Во-первых, взгляните на этоПример весенней загрузки. Это поможет вам управлять списком солдат из какой-нибудь точки доступа REST, которая выполняет CRUD-операции. вы будете использоватьApache Shiroдобавить аутентификацию и авторизацию ролей. Все коды загружены наGithub.
Чтобы использовать Apache Shiro, все, что вам нужно сделать, это использовать стартер Spring Boot, просто добавьте необходимые зависимости в файл pom (${shiro.version}
Требуется как минимум 1.4.0):
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>${shiro.version}</version>
</dependency>
Давайте посмотрим на код далее, изStormtrooperController
Для начала просто добавьте несколько аннотаций:
@RestController
@RequestMapping(path = "/troopers", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public class StormtrooperController {
private final StormtrooperDao trooperDao;
@Autowired
public StormtrooperController(StormtrooperDao trooperDao) {
this.trooperDao = trooperDao;
}
@GetMapping()
@RequiresRoles(logical = Logical.OR, value = {"admin", "officer", "underling"})
public Collection<Stormtrooper> listTroopers() {
return trooperDao.listStormtroopers();
}
@GetMapping(path = "/{id}")
@RequiresRoles(logical = Logical.OR, value = {"admin", "officer", "underling"})
public Stormtrooper getTrooper(@PathVariable("id") String id) throws NotFoundException {
Stormtrooper stormtrooper = trooperDao.getStormtrooper(id);
if (stormtrooper == null) {
throw new NotFoundException(id);
}
return stormtrooper;
}
@PostMapping()
@RequiresRoles(logical = Logical.OR, value = {"admin", "officer"})
public Stormtrooper createTrooper(@RequestBody Stormtrooper trooper) {
return trooperDao.addStormtrooper(trooper);
}
@PostMapping(path = "/{id}")
@RequiresRoles("admin")
public Stormtrooper updateTrooper(@PathVariable("id") String id, @RequestBody Stormtrooper updatedTrooper) throws NotFoundException {
return trooperDao.updateStormtrooper(id, updatedTrooper);
}
@DeleteMapping(path = "/{id}")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@RequiresRoles("admin")
public void deleteTrooper(@PathVariable("id") String id) {
trooperDao.deleteStormtrooper(id);
}
}
В приведенном выше блоке кода используйте Широ@RequiresRoles
Комментарии для указания ролей. вы увидите, что с логическимOR
чтобы дать разрешения любому с этой ролью. Это здорово, просто добавьте строку комментария, и ваш код готов.
На этом ваш код может остановиться, однако способ использования ролей не такой гибкий, и при использовании непосредственно в коде это приводит к жесткой привязке кода к этим именам.
больше не использовать роли
Представьте, что ваше приложение развернуто и работает, а через неделю вашбоссПодойдите к столу и попросите вас внести некоторые изменения:
- Командир должен иметь возможность обновлять профиль солдата
- Он чувствовал, что титул «Администратор» подходит большинству офицеров, но не подходит Великому Королю Демонов.
Ну, вы не думаете, что это сложно, просто внесите небольшое изменение в сигнатуру метода:
@GetMapping()
@RequiresRoles(logical = Logical.OR, value = {"emperor", "admin", "emperor", "officer", "underling"})
public Collection<Stormtrooper> listTroopers()
@GetMapping(path = "/{id}")
@RequiresRoles(logical = Logical.OR, value = {"emperor", "admin", "officer", "underling"})
public Stormtrooper getTrooper(@PathVariable("id") String id) throws NotFoundException
@PostMapping()
@RequiresRoles(logical = Logical.OR, value = {"emperor", "admin", "officer"})
public Stormtrooper createTrooper(@RequestBody Stormtrooper trooper)
@PostMapping(path = "/{id}")
@RequiresRoles(logical = Logical.OR, value = {"emperor", "admin", "officer"})
public Stormtrooper updateTrooper(@PathVariable("id") String id, @RequestBody Stormtrooper updatedTrooper) throws NotFoundException
@DeleteMapping(path = "/{id}")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@RequiresRoles(logical = Logical.OR, value = {"emperor", "admin"})
public void deleteTrooper(@PathVariable("id") String id)
После очередного раунда тестирования и развертывания ваша работа выполнена!
Подождите, сделав шаг назад, в простых случаях использования роли могут работать отлично, и этот тип изменений работает хорошо, но вы знаете, что в коде есть следующее изменение. Вместо того, чтобы каждый раз менять код под какое-то небольшое требование, лучше отделить роли от кода. Вместо этого можно использовать разрешения предоставления. Сигнатура вашего метода будет выглядеть так:
@GetMapping()
@RequiresPermissions("troopers:read")
public Collection<Stormtrooper> listTroopers()
@GetMapping(path = "/{id}")
@RequiresPermissions("troopers:read")
public Stormtrooper getTrooper(@PathVariable("id") String id) throws NotFoundException
@PostMapping()
@RequiresPermissions("troopers:create")
public Stormtrooper createTrooper(@RequestBody Stormtrooper trooper)
@PostMapping(path = "/{id}")
@RequiresPermissions("troopers:update")
public Stormtrooper updateTrooper(@PathVariable("id") String id, @RequestBody Stormtrooper updatedTrooper) throws NotFoundException
@DeleteMapping(path = "/{id}")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
@RequiresPermissions("troopers:delete")
public void deleteTrooper(@PathVariable("id") String id)
С помощью Широ@RequiresPermissions
Аннотация, можно удовлетворить первоначальные требования и новые требования без модификации кода. Единственное, что нужно сделать, это сопоставить разрешения с соответствующими ролями, которыми являются наши пользователи. Это можно сделать во внешней программе, например в базе данных, или в простом конфигурационном файле, как в данном случае.
Стоит отметить, что:В этом примере и имя пользователя, и пароль хранятся в виде открытого текста, что подходит для постов в блоге, но, строго говоря, вам нужноПравильноУправляйте своими паролями!
Для достижения первоначальных требований сопоставление ролей и разрешений выглядит следующим образом:
role.admin = troopers:*
role.officer = troopers:create, troopers:read
role.underling = troopers:read
Для последующих нужд просто добавьте в файл роль «император» и добавьте права «обновление» офицерам:
role.emperor = *
role.admin = troopers:*
role.officer = troopers:create,troopers:read,troopers:update
role.underling = troopers:read
Если синтаксис оператора авторизации кажется вам немного странным, вы можете получить его изПодстановочная авторизация для Apache Shiroдокументации для более глубокого понимания.
Апачи Широ и Весна
Мы рассмотрели зависимости Maven и контроллеры REST, но нашему приложению нужно еще одно.Realm
и механизм обработки исключений.
если ты виделSpringBootApp
класс, вы заметите, что некоторые из них неОбразецчто-то в.
@Bean
public Realm realm() {
// uses 'classpath:shiro-users.properties' by default
PropertiesRealm realm = new PropertiesRealm();
// Caching isn't needed in this example, but we can still turn it on
realm.setCachingEnabled(true);
return realm;
}
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
// use permissive to NOT require authentication, our controller Annotations will decide that
chainDefinition.addPathDefinition("/**", "authcBasic[permissive]");
return chainDefinition;
}
@Bean
public CacheManager cacheManager() {
// Caching isn't needed in this example, but we will use the MemoryConstrainedCacheManager for this example.
return new MemoryConstrainedCacheManager();
}
Во-первых, вы определяете Широ.Realm
. Область — это просто определенный DAO, в котором хранятся пользователи, и Широ поддерживает множество различных типов областей (активный каталог, LDAP, база данных, файл и т. д.).
см. далееShiroFilterChainDefinition
, вы настроили базовую аутентификацию, но не с помощью «разрешающей» опции, чтобы получить эту функциональность. Таким образом, ваша аннотация настраивает все. Вы можете использовать пути в стиле Ant для определенияРазрешения сопоставления URL-адресов, вместо использования аннотаций (или использования каких-либо других). Пример выглядит так:
chainDefinition.addPathDefinition("/troopers/**", "authcBasic, rest[troopers]");
При этом все/troopers
ресурсы, начинающиеся с карты, требуют базовой аутентификации и используютфильтр «остальное», который основан на методе HTTP-запроса с операцией CRUD, добавленной к строке разрешения. Например, HTTPGET
метод сопоставляется с «чтением», поэтому дляGET
Полная запрошенная строка разрешения:troopers:read
(как вы делаете с аннотациями).
Обработка исключений
Последняя часть кода — обработка исключений.
@ExceptionHandler(UnauthenticatedException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public void handleException(UnauthenticatedException e) {
log.debug("{} was thrown", e.getClass(), e);
}
@ExceptionHandler(AuthorizationException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public void handleException(AuthorizationException e) {
log.debug("{} was thrown", e.getClass(), e);
}
@ExceptionHandler(NotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public @ResponseBody ErrorMessage handleException(NotFoundException e) {
String id = e.getMessage();
return new ErrorMessage("Trooper Not Found: "+ id +", why aren't you at your post? "+ id +", do you copy?");
}
Первые два примера обработки исключений Shiro — это просто изменение кода состояния на 401 или 403. 401 — неверное или отсутствующее имя пользователя/пароль, 403 — потому что вошедший в систему пользователь не имеет доступа к ограниченным ресурсам. Наконец, вы будете иметь дело с 404NotFoundException
и возвращает сериализованный JSONErrorMessage
объект.
со всей силой!
Если вы объедините их или пойдете прямо отGitHubПереместите код, вы можете использоватьmvn spring-boot:run
чтобы запустить приложение. Как только он заработает, вы можете начать отправлять запросы!
$ curl http://localhost:8080/troopers
HTTP/1.1 401
Content-Length: 0
Date: Thu, 26 Jan 2017 21:12:41 GMT
WWW-Authenticate: BASIC realm="application"
Не забывайте, вам необходимо подтвердить свою личность!
$ curl --user emperor:secret http://localhost:8080/troopers
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Thu, 26 Jan 2017 21:14:17 GMT
Transfer-Encoding: chunked
[
{
"id": "FN-0128",
"planetOfOrigin": "Naboo",
"species": "Twi'lek",
"type": "Sand"
},
{
"id": "FN-1383",
"planetOfOrigin": "Hoth",
"species": "Human",
"type": "Basic"
},
{
"id": "FN-1692",
"planetOfOrigin": "Hoth",
"species": "Nikto",
"type": "Marine"
},
...
Один 404
Такова, что:
$ curl --user emperor:secret http://localhost:8080/troopers/TK-421
HTTP/1.1 404
Content-Type: application/json;charset=UTF-8
Date: Thu, 26 Jan 2017 21:15:54 GMT
Transfer-Encoding: chunked
{
"error": "Trooper Not Found: TK-421, why aren't you at your post? TK-421, do you copy?"
}
Узнайте больше об Apache Широ
В этом примере показано, как легко интегрировать Apache Shiro в приложение Spring Boot и как использовать разрешения для повышения гибкости ролей, и все это с помощью одной аннотации в контроллере.
Мы рады внести свой вклад в Apache Shiro и направить его в Okta. Мы с нетерпением ждем новых материалов Shiro от нашей команды, в том числе руководств Shiro для Okta и OAuth и способовволонтерДобавьте интерфейсный код AngularJS в приложение. Оставайтесь с нами, вы нужны Империи!
Если у вас есть какие-либо вопросы по этому примеру, отправьте их по адресуСписок пользователей для Apache Shiroили мойTwitterучетную запись, вы также можете оставить сообщение прямо в разделе комментариев ниже!
Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.
Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,продукт,дизайн,искусственный интеллекти другие поля, если вы хотите видеть больше качественных переводов, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.