Недавно я работал над фреймворком безопасности Shiro и нашел несколько статей в блогах в Интернете, но когда я реализовал его сам, я столкнулся с различными ямами, и мне нужна была различная информация, чтобы увидеть исходный код и различные тесты. Затем эта статья научит вас, как интегрировать Shiro в SpringBoot и избежать некоторых ловушек.На этот раз реализованы базовые права входа и роли.В следующих статьях также будут объясняться другие функции, такие как«Научите вас Широ + SpringBoot интегрировать JWT»
Прикрепите исходный код:Github.com/howie Yuan / есть ...
Пакет зависимостей
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
Таблица базы данных
Сохраняйте все простым, пользовательская таблица пользователей и таблица ролей ролей
Классы, связанные с Широ
Класс конфигурации Широ
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// setLoginUrl 如果不设置值,默认会自动寻找Web工程根目录下的"/login.jsp"页面 或 "/login" 映射
shiroFilterFactoryBean.setLoginUrl("/notLogin");
// 设置无权限时跳转的 url;
shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
// 设置拦截器
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//游客,开发权限
filterChainDefinitionMap.put("/guest/**", "anon");
//用户,需要角色权限 “user”
filterChainDefinitionMap.put("/user/**", "roles[user]");
//管理员,需要角色权限 “admin”
filterChainDefinitionMap.put("/admin/**", "roles[admin]");
//开放登陆接口
filterChainDefinitionMap.put("/login", "anon");
//其余接口一律拦截
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("Shiro拦截器工厂类注入成功");
return shiroFilterFactoryBean;
}
/**
* 注入 securityManager
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(customRealm());
return securityManager;
}
/**
* 自定义身份认证 realm;
* <p>
* 必须写这个类,并加上 @Bean 注解,目的是注入 CustomRealm,
* 否则会影响 CustomRealm类 中其他类的依赖注入
*/
@Bean
public CustomRealm customRealm() {
return new CustomRealm();
}
}
Уведомление: Импортированный класс SecurityManager должен бытьimport org.apache.shiro.mgt.SecurityManager;
Однако, если вы скопируете код, он будет импортирован по умолчанию.java.lang.SecurityManager
Здесь также есть небольшая яма, а остальные классы относятся к классам в пакете shiro.
Метод shirFilter в основном устанавливает некоторые важные URL-адреса перехода, такие как переход без входа в систему и без разрешения; и настройка перехвата разрешений для различных URL-адресов, таких как URL-адреса, начинающиеся с /user, требуют разрешения пользователя, а URL-адреса, начинающиеся с /admin, требуют администратора. разрешения и т. д.
Фильтр перехвата разрешений
При запуске веб-приложения Широ создаст несколько полезных экземпляров фильтров по умолчанию и автоматически сделает их доступными.Эти экземпляры фильтров по умолчанию определяются классом перечисления DefaultFilter.Конечно, мы также можем настроить примеры фильтров, которые будут рассмотрены в следующих статьях.
Filter | объяснять |
---|---|
anon | Никаких параметров, открытые разрешения, можно понимать как анонимных пользователей или туристов |
authc | Нет параметров, требуется аутентификация |
logout | Нет параметра, выйдите из системы, после выполнения он перейдет прямо кshiroFilterFactoryBean.setLoginUrl(); установить URL |
authcBasic | Параметр отсутствует, это означает httpBasic-аутентификацию |
user | Нет параметра, указывающего на то, что должен быть пользователь, проверка при входе не производится |
ssl | Нет параметров, указывающих на безопасный URL-запрос, протокол https |
perms[user] | Можно записать несколько параметров, что означает, что для передачи требуется одно или несколько разрешений.При наличии нескольких параметров напишите perms["user, admin"].При наличии нескольких параметров каждый параметр должен пройти для передачи. |
roles[admin] | Можно записать несколько параметров, что означает, что может передаваться только одна или несколько ролей. При наличии нескольких параметров напишите roles["admin, user"]. При наличии нескольких параметров каждый параметр должен пройти для передачи. |
rest[user] | Согласно запрошенному методу, он эквивалентен perms[пользователь:метод], где метод — это отправка, получение, удаление и т. д. |
port[8081] | Если запрошенный порт URL-адреса не 8081, перейдите к schema://serverName:8081?queryString, где schmal — это протокол http или https и т. д., serverName — это хост, который вы посещаете, 8081 — это порт порта, а queryString — это URL-адрес, который вы посещаете Параметр после ? |
Наиболее часто используемые: anon, authc, user, roles, perms и т. д.
Уведомление: anon, authc, authcBasic, user — первая группа фильтров аутентификации, perms, port, rest, roles, ssl — вторая группа фильтров авторизации.Чтобы пройти фильтр авторизации, вы должны сначала выполнить операцию аутентификации входа (т. , сначала После завершения аутентификации вы можете перейти к поиску авторизации), чтобы перейти ко второй группе авторизаторов (например, для доступа к URL-адресу, который требует разрешения ролей, если вы не вошли в систему, вы сразу перейдете кshiroFilterFactoryBean.setLoginUrl();
Установить URL)
Пользовательский класс области
Сначала нам нужно наследовать класс AuthorizingRealm, чтобы настроить нашу собственную область для наших пользовательских операций аутентификации удостоверений и полномочий. Не забудьте переопределить методы doGetAuthenticationInfo и doGetAuthorizationInfo (названия этих двух методов очень похожи, не поймите меня неправильно).
public class CustomRealm extends AuthorizingRealm {
private UserMapper userMapper;
@Autowired
private void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
/**
* 获取身份验证信息
* Shiro中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。
*
* @param authenticationToken 用户身份信息 token
* @return 返回封装了用户信息的 AuthenticationInfo 实例
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("————身份认证方法————");
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 从数据库获取对应用户名密码的用户
String password = userMapper.getPassword(token.getUsername());
if (null == password) {
throw new AccountException("用户名不正确");
} else if (!password.equals(new String((char[]) token.getCredentials()))) {
throw new AccountException("密码不正确");
}
return new SimpleAuthenticationInfo(token.getPrincipal(), password, getName());
}
/**
* 获取授权信息
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("————权限认证————");
String username = (String) SecurityUtils.getSubject().getPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//获得该用户角色
String role = userMapper.getRole(username);
Set<String> set = new HashSet<>();
//需要将 role 封装到 Set 作为 info.setRoles() 的参数
set.add(role);
//设置该用户拥有的角色
info.setRoles(set);
return info;
}
}
Два переписанных метода предназначены для реализации аутентификации личности и авторизации.В shiro есть операция входа в систему.Subject.login()
метод, когда мы передаем токен, который инкапсулирует имя пользователя и пароль в качестве параметров, он будет работать с этими двумя методами (не обязательно оба метода будут введены)
Метод doGetAuthorizationInfo будет использоваться только в том случае, если требуется аутентификация авторизации, например конфигурация в предыдущем классе конфигурации.filterChainDefinitionMap.put("/admin/**", "roles[admin]");
При вводе /admin вы введете метод doGetAuthorizationInfo для проверки разрешений, в то время как метод doGetAuthenticationInfo требует аутентификации (например, предыдущийSubject.login()
метод) для входа
Давайте поговорим о классе UsernamePasswordToken, мы можем получить имя пользователя и пароль при входе из этого объекта (логин будет использоватьnew UsernamePasswordToken(username, password);
), а получение имени пользователя или пароля имеет следующие методы
token.getUsername() //获得用户名 String
token.getPrincipal() //获得用户名 Object
token.getPassword() //获得密码 char[]
token.getCredentials() //获得密码 Object
Уведомление: Многие люди обнаружат, что интерфейс UserMapper и других классов нельзя внедрить через @Autowired, и при запуске программы будет сообщено об исключении NullPointerException.Существует много причин, таких как порядок загрузки Spring в Интернете, но на самом деле существует очень важный момент, на который всем стоит обратить внимание. , класс CustomRealm настраивается в shirosecurityManager.setRealm()
Он задается в методе, и многие пишут прямоsecurityManager.setRealm(new CustomRealm());
, это невозможно, вы должны использовать @Bean для внедрения MyRealm, вы не можете напрямую создавать новые объекты:
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(customRealm());
return securityManager;
}
@Bean
public CustomRealm customRealm() {
return new CustomRealm();
}
Причина также очень проста: как и вызов службы в контроллере, все они являются SpringBeans и сами по себе не могут быть новыми.
Конечно, ту же логику можно записать и так:
@Bean
public SecurityManager securityManager(CustomRealm customRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(customRealm);
return securityManager;
}
Затем просто добавьте аннотацию, например @Component, в класс CustomRealm.
Реализация функции
Все функции этой статьи реализованы интерфейсом, возвращающим данные json.
Назначить контроллер на основе разрешений URL
游客
@RestController
@RequestMapping("/guest")
public class GuestController{
@Autowired
private final ResultMap resultMap;
@RequestMapping(value = "/enter", method = RequestMethod.GET)
public ResultMap login() {
return resultMap.success().message("欢迎进入,您的身份是游客");
}
@RequestMapping(value = "/getMessage", method = RequestMethod.GET)
public ResultMap submitLogin() {
return resultMap.success().message("您拥有获得该接口的信息的权限!");
}
}
普通登陆用户
@RestController
@RequestMapping("/user")
public class UserController{
@Autowired
private final ResultMap resultMap;
@RequestMapping(value = "/getMessage", method = RequestMethod.GET)
public ResultMap getMessage() {
return resultMap.success().message("您拥有用户权限,可以获得该接口的信息!");
}
}
管理员
@RestController
@RequestMapping("/admin")
public class AdminController {
@Autowired
private final ResultMap resultMap;
@RequestMapping(value = "/getMessage", method = RequestMethod.GET)
public ResultMap getMessage() {
return resultMap.success().message("您拥有管理员权限,可以获得该接口的信息!");
}
}
Внезапно заметил, что в классе CustomRealm выбрасывается исключение AccountException, теперь создайте класс для перехвата исключения
@RestControllerAdvice
public class ExceptionController {
private final ResultMap resultMap;
@Autowired
public ExceptionController(ResultMap resultMap) {
this.resultMap = resultMap;
}
// 捕捉 CustomRealm 抛出的异常
@ExceptionHandler(AccountException.class)
public ResultMap handleShiroException(Exception ex) {
return resultMap.fail().message(ex.getMessage());
}
}
Конечно, есть еще LoginController для входа в систему и другой обработки.
@RestController
public class LoginController {
@Autowired
private ResultMap resultMap;
private UserMapper userMapper;
@RequestMapping(value = "/notLogin", method = RequestMethod.GET)
public ResultMap notLogin() {
return resultMap.success().message("您尚未登陆!");
}
@RequestMapping(value = "/notRole", method = RequestMethod.GET)
public ResultMap notRole() {
return resultMap.success().message("您没有权限!");
}
@RequestMapping(value = "/logout", method = RequestMethod.GET)
public ResultMap logout() {
Subject subject = SecurityUtils.getSubject();
//注销
subject.logout();
return resultMap.success().message("成功注销!");
}
/**
* 登陆
*
* @param username 用户名
* @param password 密码
*/
@RequestMapping(value = "/login", method = RequestMethod.POST)
public ResultMap login(String username, String password) {
// 从SecurityUtils里边创建一个 subject
Subject subject = SecurityUtils.getSubject();
// 在认证提交前准备 token(令牌)
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 执行认证登陆
subject.login(token);
//根据权限,指定返回数据
String role = userMapper.getRole(username);
if ("user".equals(role)) {
return resultMap.success().message("欢迎登陆");
}
if ("admin".equals(role)) {
return resultMap.success().message("欢迎来到管理员页面");
}
return resultMap.fail().message("权限错误!");
}
}