Apache Shiro — это мощная и гибкая платформа безопасности с открытым исходным кодом, которая обеспечивает точную аутентификацию, авторизацию, управление сеансами, а также шифрование и другие распространенные процессы управления безопасностью в приложениях уровня предприятия. Основная цель Apache Shiro — сделать его простым в использовании и понятным. Иногда процесс управления безопасностью может быть очень сложным, что может стать головной болью для разработчиков, но это не обязательно. Фреймворки должны максимально маскировать сложность и предоставлять чистый и интуитивно понятный API, который упрощает работу разработчиков и обеспечивает безопасность их приложений. На этот раз мы поговорим о том, как использовать Shiro для реализации контроля разрешений в веб-приложениях Spring.
Функции
Apache Shiro — это комплексная платформа безопасности приложений с множеством функций. На следующем рисунке показаны наиболее важные функции в Широ:
Основная цель Широ — «четыре краеугольных камня безопасности приложений» — аутентификация, авторизация, управление сессиями и шифрование:- Аутентификация: также известная как «логин», чтобы подтвердить поведение владельца пользователя.
- Авторизация: процесс управления доступом, то есть определение того, какие пользователи могут получить доступ к какому контенту.
- Управление сеансами. Возможность управления сеансами для конкретных пользователей, даже в не веб-приложениях, является одной из сильных сторон Широ.
- Технология шифрования. Используйте алгоритмы шифрования для обеспечения безопасности данных и простоты их использования.
Архитектура
Из общего концептуального понимания архитектура Широ имеет три основных понятия: Subject (субъект, то есть пользователь), Security Manager (менеджер безопасности) и Realms (домен). Следующая диаграмма описывает взаимосвязь между этими компонентами:
Эти основные компоненты можно понимать следующим образом:- Субъект (subject): Субъект представляет собой совокупность данных, специфичных для пользователя, работающего в данный момент. Субъектом может быть человек или сторонняя служба, демон, задание cron или что-то в этом роде, то есть почти все, что взаимодействует с приложением.
- Менеджер безопасности: является ядром архитектуры Широ и играет роль, аналогичную «зонту», в основном отвечает за координацию различных внутренних компонентов для формирования сети безопасности.
- Realms: «мост» между Широ и данными безопасности приложений. Когда необходимо фактически взаимодействовать с данными, связанными с безопасностью, такими как учетные записи пользователей, для выполнения аутентификации и авторизации, Широ будет получать эти данные из Realms.
подготовка данных
В веб-приложениях контроль безопасности в основном включает в себя понятия ролей, ресурсов и разрешений (какие роли могут получить доступ к каким ресурсам).У пользователя может быть несколько ролей, и роль также может иметь доступ к нескольким ресурсам, то есть роли могут соответствовать для нескольких разрешений. Для реализации дизайна базы данных нам необходимо построить как минимум 5 таблиц: таблица пользователей, таблица ролей, таблица ресурсов, таблица ролей-ресурсов, таблица ролей пользователей, структура этих 5 таблиц следующая: пользовательская таблица:
id | username | password |
---|---|---|
1 | Чжан Сан | 123456 |
2 | Ли Си | 666666 |
3 | Ван Ву | 000000 |
Таблица ролей:
id | rolename |
---|---|
1 | администратор |
2 | управляющий делами |
3 | штат сотрудников |
Лист ресурсов:
id | resname |
---|---|
1 | /user/add |
2 | /user/delete |
3 | /compony/info |
Таблица ролей-ресурсов:
id | roleid | resid |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
3 | 2 | 3 |
Таблица ролей пользователей:
id | userid | roleid |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
3 | 1 | 3 |
Соответствующие классы POJO следующие:
/**
* 用户
*/
public class User {
private Integer id;
private String username;
private String password;
//getter & setter...
}
/**
* 角色
*/
public class Role {
private String id;
private String rolename;
}
/**
* 资源
*/
public class Resource {
private String id;
private String resname;
}
/**
* 角色-资源
*/
public class RoleRes {
private String id;
private String roleid;
private String resid;
}
/**
* 用户-角色
*/
public class UserRole {
private String id;
private String userid;
private String roleid;
}
Подробные инструкции по интеграции Spring с Shiro см. в моем блоге.«Интеграция Apache Shiro в приложения Spring».
Вот дополнение: зависимости Широ нужно вводить заранее, открыватьmvnrepository.com, ищем Shiro, нам нужны первые три зависимости, а именно Shiro-Core, Shiro-Web и Shiro-Spring, в качестве примера возьмем проект Maven, вpom.xml
середина<dependencies>
Добавьте следующие зависимости под узлом:
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
существуетapplication-context.xml
нужно настроить такshiroFilter
bean:
<!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!-- 登录页面 -->
<property name="loginUrl" value="/login"/>
<!-- 登录成功后的页面 -->
<property name="successUrl" value="/index"/>
<!-- 非法访问跳转的页面 -->
<property name="unauthorizedUrl" value="/403"/>
<!-- 权限配置 -->
<property name="filterChainDefinitions">
<value>
<!-- 无需认证即可访问的静态资源,还可以添加其他 url -->
/static/** = anon
<!-- 除了上述忽略的资源,其他所有资源都需要认证后才能访问 -->
/** = authc
</value>
</property>
</bean>
Далее вам нужно определить Realm, Пользовательский Realm интегрируется самAuthorizingRealm
своего рода:
public class MyRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
/**
* 验证权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
String loginName = SecurityUtils.getSubject().getPrincipal().toString();
if (loginName != null) {
String userId = SecurityUtils.getSubject().getSession().getAttribute("userSessionId").toString();
// 权限信息对象,用来存放查出的用户的所有的角色及权限
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 用户的角色集合
ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal();
info.setRoles(shiroUser.getRoles());
info.addStringPermissions(shiroUser.getUrlSet());
return info;
}
return null;
}
/**
* 认证回调函数,登录时调用
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
String username = (String) token.getPrincipal();
User user = new User();
sysuser.setUsername(username);
try {
List<SysUser> users = userService.findByNames(user);
List<String> roleList= userService.selectRoleNameListByUserId(users.get(0).getId());
if (users.size() != 0) {
String pwd = users.get(0).getPassword();
// 当验证都通过后,把用户信息放在 session 里
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("userSession", users.get(0));
session.setAttribute("userSessionId", users.get(0).getId());
session.setAttribute("userRoles", org.apache.commons.lang.StringUtils.join(roleList,","));
return new SimpleAuthenticationInfo(username,users.get(0).getPassword());
} else {
// 没找到该用户
throw new UnknownAccountException();
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return null;
}
/**
* 更新用户授权信息缓存.
*/
public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
super.clearCachedAuthorizationInfo(principals);
}
/**
* 更新用户信息缓存.
*/
public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
super.clearCachedAuthenticationInfo(principals);
}
/**
* 清除用户授权信息缓存.
*/
public void clearAllCachedAuthorizationInfo() {
getAuthorizationCache().clear();
}
/**
* 清除用户信息缓存.
*/
public void clearAllCachedAuthenticationInfo() {
getAuthenticationCache().clear();
}
/**
* 清空所有缓存
*/
public void clearCache(PrincipalCollection principals) {
super.clearCache(principals);
}
/**
* 清空所有认证缓存
*/
public void clearAllCache() {
clearAllCachedAuthenticationInfo();
clearAllCachedAuthorizationInfo();
}
}
Наконец, определите контроллер входа пользователя, чтобы принимать запрос пользователя на вход:
@Controller
public class UserController {
/**
* 用户登录
*/
@PostMapping("/login")
public String login(@Valid User user,BindingResult bindingResult,RedirectAttributes redirectAttributes){
try {
if(bindingResult.hasErrors()){
return "login";
}
//使用权限工具进行认证,登录成功后跳到 shiroFilter bean 中定义的 successUrl
SecurityUtils.getSubject().login(new UsernamePasswordToken(user.getUsername(), user.getPassword()));
return "redirect:index";
} catch (AuthenticationException e) {
redirectAttributes.addFlashAttribute("message","用户名或密码错误");
return "redirect:login";
}
}
/**
* 注销登录
*/
@GetMapping("/logout")
public String logout(RedirectAttributes redirectAttributes ){
SecurityUtils.getSubject().logout();
return "redirect:login";
}
}