Изучите широко используемую структуру безопасности shiro сегодня
Сначала изучите принцип реализации сиро, чтобы облегчить последующее обучение.
сиро (фреймворк безопасности Java)
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以
快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
软件名称 Apache Shiro 开发商 Apache 性质 Java安全框架
Основная функция Три основных компонента: Subject, SecurityManager и Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户
(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为
是Shiro的“用户”概念。Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来
提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控
制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。从这个意义上讲,Realm实质上是一个安全相关的DAO:它
封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(
或)授权。配置多个Realm是可以的,但是至少需要一个。 Shiro内置了可以连接大量安全数据源(又名目录)的Realm,
如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表
自定义数据源的自己的Realm实现。
Анализ принципа сиро: Ядром shiro является фильтр в спецификации сервлета Java.При настройке перехватчика цепочка перехватчиков используется для перехвата запроса, и если доступ разрешен, он передается. Обычно перехватчики настраиваются для входа и выхода из системы. При входе в систему вызовите subject.login(token), токен — это информация для аутентификации пользователя, которая в это время будет аутентифицирована в методе doGetAuthenticationInfo в Realm. В это время информация об аутентификации, предоставленная пользователем, будет сравниваться с информацией об аутентификации, хранящейся в базе данных.Если они непротиворечивы, доступ разрешен, и файл cookie этого сеанса будет установлен в браузере, а информация о сеансе будет храниться на стороне сервера. При выходе вызов subject.logout() очистит информацию о сеансе.
Введение в основные концепции сиро: Фильтр:
1. AnonymousFilter: любой может получить доступ к URL-адресу, измененному этим фильтром, даже без аутентификации авторизации.
2.FormAuthenticationFilter: URL-адрес, измененный этим фильтром, проверит запрошенный URL-адрес. Если он не пройдет, он будет перенаправлен обратно на loginurl
3.BasicHttpAuthenticationFilter: URL-адрес, измененный этим фильтром, требует, чтобы пользователь прошел аутентификацию. Если он не проходит, для аутентификации потребуется передать информацию об авторизации.
4. LogoutFilter: URL-адрес, измененный этим фильтром, после получения запроса URL-адреса он немедленно вызовет тему для выхода и перенаправит на redirectUrl.
5.NoSessionCreationFilter: URL-адрес, измененный этим фильтром, не создаст никакой сессии.
6.PermissionAuthorizationFilter: перехватчик разрешений для проверки наличия у пользователя соответствующих разрешений.
7. Фильтр портов: перехватчик портов, вместо доступа к URL-адресу через указанный порт он автоматически перенаправляет порт на указанный порт.
8. HttpMethodPermissionFilter: перехватчик остальных стилей, настройка режима доступа остальных
9.RolesAuthorizationFilter: перехватчик ролей, если вы не вошли в систему, он перейдет на loginurl, если не авторизован, он перейдет на неавторизованный URL
10. SslFilter: перехватчик HTTPS, доступ к которому должен осуществляться через HTTPS.
11.UserFilter: перехватчик пользователя, требует, чтобы пользователь прошел аутентификацию или запомнил меня.
Инструкции по настройке перехвата:
anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数
roles:例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。
authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证
ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查
注:anon,authcBasic,auchc,user是认证过滤器,
perms,roles,ssl,rest,port是授权过滤器
Контроль разрешений Широ предназначен только для обеспечения контроля разрешений ресурсов.Чтобы реализовать контроль разрешений бизнес-данных, он должен быть связан с нашим конкретным бизнес-кодом.Позже я поделюсь идеей решения моей собственной компании, когда у меня будет время.
Вышеизложенное кратко представляет сиро, а затем переходит к основной теме.
Добавьте необходимые зависимости
<!-- spring-data-jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!--模板引擎,访问静态资源-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- shiro 相关包 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro+redis缓存插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>2.4.2.1-RELEASE</version>
</dependency>
<!--shiro与thymelef的集成插件-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
Файл yml добавляет небольшую конфигурацию.Здесь следует отметить, что jpa находится на том же уровне, что и предыдущий источник данных.
#通过jpa 生成数据库表
spring:
jpa:
hibernate:
ddl-auto: update
show-sql: true
thymeleaf:
cache: false
prefix: classpath:/templates/
suffix: .html
encoding: UTF-8
content-type: text/html
mode: HTML5
Дизайн базы данных Общее управление разрешениями будет реализовано в этих пяти таблицах (таблица пользователей, таблица ролей, промежуточная таблица ролей пользователей, таблица разрешений, промежуточная таблица разрешений ролей).
1. Пользовательская таблица:
@Entity //实体类的注解
@Table(name="sys_user")
public class SysUser implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String userName;
private String passWord;
private int userEnable;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassWord() {
return passWord;
}
public void setPassWord(String passWord) {
this.passWord = passWord;
}
public int getUserEnable() {
return userEnable;
}
public void setUserEnable(int userEnable) {
this.userEnable = userEnable;
}
}
2. Ролевая таблица
@Entity //实体类的注解
@Table(name="sys_role")
public class SysRole implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String roleName;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
}
3. Промежуточная таблица ролей пользователей
@Entity //实体类的注解
@Table(name="sys_user_role")
public class SysUserRole implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int userId;
private int roleId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getUserId() {
return userId;
}
public void setUserId(int userId) {
this.userId = userId;
}
public int getRoleId() {
return roleId;
}
public void setRoleId(int roleId) {
this.roleId = roleId;
}
}
4. Таблица разрешений
@Entity //实体类的注解
@Table(name="sys_permission")
public class SysPermission implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String userName;
private String resUrl;
private String userType;
private String parentId;
private String userSort;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getResUrl() {
return resUrl;
}
public void setResUrl(String resUrl) {
this.resUrl = resUrl;
}
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
public String getParentId() {
return parentId;
}
public void setParentId(String parentId) {
this.parentId = parentId;
}
public String getUserSort() {
return userSort;
}
public void setUserSort(String userSort) {
this.userSort = userSort;
}
}
5. Промежуточная таблица разрешений ролей
@Entity //实体类的注解
@Table(name="sys_role_permission")
public class SysRolePermission implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private int roleId;
private int permissionId;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getRoleId() {
return roleId;
}
public void setRoleId(int roleId) {
this.roleId = roleId;
}
public int getPermissionId() {
return permissionId;
}
public void setPermissionId(int permissionId) {
this.permissionId = permissionId;
}
}
В основном структура пяти таблиц такова: если есть проблема, она будет изменена, когда придет время: строится класс сущности, а сначала строится база данных. В настоящее время перезапуск проекта может помочь нам создать таблицу в базе данных.
класс конфигурации Широ
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 没有登陆的用户只能访问登陆页面
shiroFilterFactoryBean.setLoginUrl("/auth/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/auth/index");
// 未授权界面; ----这个配置了没卵用,具体原因想深入了解的可以自行百度
shiroFilterFactoryBean.setUnauthorizedUrl("/auth/err");
//自定义拦截器
Map<String, Filter> filtersMap = new LinkedHashMap<String, Filter>();
shiroFilterFactoryBean.setFilters(filtersMap);
// 权限控制map.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/auth/login", "anon");
filterChainDefinitionMap.put("/auth/logout", "logout");
filterChainDefinitionMap.put("/auth/kickout", "anon");
//filterChainDefinitionMap.put("/book/**", "authc,perms[book:list],roles[admin]");
//filterChainDefinitionMap.put("/**", "authc,kickout");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myShiroRealm());
// 自定义缓存实现 使用redis
securityManager.setCacheManager(cacheManager());
// 自定义session管理 使用redis
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
*
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* 配置shiro redisManager
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost("localhost");
redisManager.setPort(6379);
redisManager.setExpire(1800);// 配置缓存过期时间
redisManager.setTimeout(0);
// redisManager.setPassword(password);
return redisManager;
}
/**
* Session Manager
* 使用的是shiro-redis开源插件
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
/***
* 授权所用配置
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/***
* 使授权注解起作用不如不想配置可以在pom文件中加入
* <dependency>
*<groupId>org.springframework.boot</groupId>
*<artifactId>spring-boot-starter-aop</artifactId>
*</dependency>
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro生命周期处理器
*
*/
@Bean
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
Пользовательский мир:
public class MyShiroRealm extends AuthorizingRealm {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
//如果项目中用到了事物,@Autowired注解会使事物失效,可以自己用get方法获取值
@Autowired
private SysRoleService roleService;
@Autowired
private UserService userService;
/**
* 认证信息.(身份验证) : Authentication 是用来验证用户身份
*
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
logger.info("---------------- 执行 Shiro 凭证认证 ----------------------");
UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
String name = token.getUsername();
String password = String.valueOf(token.getPassword());
SysUser user = new SysUser();
user.setUserName(name);
user.setPassWord(password);
// 从数据库获取对应用户名密码的用户
SysUser userList = userService.getUser(user);
if (userList != null) {
// 用户为禁用状态
if (userList.getUserEnable() != 1) {
throw new DisabledAccountException();
}
logger.info("---------------- Shiro 凭证认证成功 ----------------------");
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userList, //用户
userList.getPassWord(), //密码
getName() //realm name
);
return authenticationInfo;
}
throw new UnknownAccountException();
}
/**
* 授权
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("---------------- 执行 Shiro 权限获取 ---------------------");
Object principal = principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
if (principal instanceof SysUser) {
SysUser userLogin = (SysUser) principal;
Set<String> roles = roleService.findRoleNameByUserId(userLogin.getId());
authorizationInfo.addRoles(roles);
Set<String> permissions = userService.findPermissionsByUserId(userLogin.getId());
authorizationInfo.addStringPermissions(permissions);
}
logger.info("---- 获取到以下权限 ----");
logger.info(authorizationInfo.getStringPermissions().toString());
logger.info("---------------- Shiro 权限获取成功 ----------------------");
return authorizationInfo;
}
/**
* 清除所有用户授权信息缓存.
*/
public void clearCachedAuthorizationInfo(String principal) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
clearCachedAuthorizationInfo(principals);
}
/**
* 清除所有用户授权信息缓存.
*/
public void clearAllCachedAuthorizationInfo() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache != null) {
for (Object key : cache.keys()) {
cache.remove(key);
}
}
}
/**
* @Description: TODO 清楚缓存的授权信息
* @return void 返回类型
*/
public void clearAuthz(){
this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
}
}
Вставьте несколько данных в базу данных, чтобы начать тестирование.
Возьмите предыдущий код для тестирования, сначала настройте пути и правила разрешений, которые необходимо отфильтровать в конфигурации.
Доступ к этому параметру возможен только при входе в систему, и браузер может получить к нему прямой доступ.
Получается, что я перескочил на настроенную ранее страницу входа, то есть это разрешение работает, меняем на анон, перезагружаемся и пробуем, собственно, в запустившейся консоли мы видим информацию о фильтре shiroFilter , который фильтруется /*Посетив предыдущую ссылку, я обнаружил, что могу нормально получить к ней доступ, и нашел предыдущие тестовые данные.
Попробуйте изменить разрешения роли.Ранее мы установили две роли «admin» и «test» для пользователя.Если роль «demo» не установлена, запрос также должен быть перехвачен.
Конечно, я снова выпрыгнул на страницу входа в систему, удалил «демо» и обнаружил, что я могу нормально запросить данные.
На самом деле кроме правила фильтра в профиле широ можно еще добавить права аннотаций на контролере кстати эффект тот же
Место, обрамленное на рисунке, можно поставить И или ИЛИ, то есть при задании нескольких ролей достаточно удовлетворить все или одну из них
Детализация настройки разрешений на основе ролей все еще относительно грубая, и ее можно настроить более точно для каждой функции.В настоящее время используется таблица разрешений.
Я также использовал предыдущий тест, чтобы установить два разрешения. Разрешения, которые мы установили в базе данных, — «book:*». Тест показал, что проблем нет и их можно запросить.
На самом деле, помимо этого метода настройки, вы также можете использовать аннотации для настройки метода настройки, аналогичного параметрам роли.
Таким образом, настройка разрешений практически одинакова, также бывает ситуация, когда вам нужно управлять разрешениями кнопок на странице, что на самом деле относительно просто.
Используйте html для тестирования под тимелефом, нужный jar импортирован выше, настройте ShiroDialect в конфиге широ, указанный выше файл конфигурации также настроен, остальное - внедрить xmlns в шапку страницы
<html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
Поместите две кнопки на страницу, настройте разные разрешения, измените разрешения в базе данных на «книга: список» и посмотрите на эффект.
<tr>
<td colspan="2">
<button shiro:hasPermission="book:list" type="reset">
重置
</button>
<button shiro:hasPermission="book:add" type="button" onclick="submit1()">
提交
</button>
</td>
</tr>
Обнаружено, что могут отображаться только кнопки с разрешениями, а просмотр исходного кода страницы обнаруживает, что кнопки без разрешений вообще не генерируются на странице.
Подводя итог, после понимания приведенного выше ряда концепций и конфигураций основное использование shiro не должно вызвать проблем.Далее я изучу, как shiro выполняет единый вход.