权限管理
- 权限管理:控制不同身份的用户拥有不同的权限。
- 实现权限管理的方法
- 基于主页的权限管理:不同身份的用户对应不同的主页(index.html)。例如在用户表中除了账号密码字段外,还有一个state字段标记不同身份的用户,在用户登录时根据state字段的不同显示不同的主页。适用于权限管理比较单一,用户少,每类用户权限固定。
- 基于用户和权限的管理:包含用户表,用户权限表,系统权限表。可以实现权限的动态分配,但是不够灵活。
- 基于角色的访问控制(RBAC:Role-Based Access Control):包含用户表、用户角色表、角色表、角色权限表、权限表。
安全框架
- 常用的安全框架:
- shiro
- spring security
- OAuth2
JavaSE应用中shiro的基本使用
- 创建Maven项目
- 导入shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
- 创建shiro配置文件:在resource目录下创建名为shiro.ini的文件,在文件中完成用户、角色、权限的配置
;定义用户
[users]
lisi=123456,seller
zhangsan=543210,manager
admin=131452,admin
;定义角色
[roles]
admin=*
seller=order-add,order-del,order-list
manager=order-add,order-del,order-list
- 认证授权
String userName = "admin";
String passWord = "131452";
// 创建安全管理器
DefaultSecurityManager securityManager = new DefaultSecurityManager();
// 创建Realm
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
// 将realm设置给安全管理器
securityManager.setRealm(iniRealm);
// 将安全管理器设置给SecurityUtils工具
SecurityUtils.setSecurityManager(securityManager);
// 通过SecurityUtils获取subject对象
Subject subject = SecurityUtils.getSubject();
// 认证流程
// 1.将账号密码封装到token对象中
UsernamePasswordToken token = new UsernamePasswordToken(userName, passWord);
// 2.通过subject对象调用login方法进行认证
boolean flag = false;
try {
subject.login(token);
flag = true;
} catch (AuthenticationException exception) {
flag = false;
}
System.out.println(flag ? "登录成功":"登录失败");
// 授权流程
// 判断是否有某个角色
System.out.println(subject.hasRole("manager")); //false
// 判断是否有某个权限
System.out.println(subject.isPermitted("order-del"));//true
springboot整合shiro,使用IniRealm
- 导入依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
- shiro配置
@Configuration
public class ShiroConfig {
@Bean
public IniRealm getRealm() {
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
return iniRealm;
}
@Bean
public DefaultWebSecurityManager getSecurityManager(Realm iniRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(iniRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
filter.setSecurityManager(securityManager);
// 设置shiro的拦截规则
// anno:匿名用户可以访问
// authc:认证用户可以访问
// user:使用remember me的用户可以访问
// perms:拥有对应权限可以访问
// role:拥有对应的角色才可以访问
HashMap<String, String> filterMap = new HashMap<>();
filterMap.put("/","anon");
filterMap.put("/login.html","anon");
filterMap.put("/regist.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/regist","anon");
filterMap.put("/static/**","anon");
filterMap.put("/**","authc");
filter.setFilterChainDefinitionMap(filterMap);
filter.setLoginUrl("/login.html");
// 设置未授权访问的页面路径
filter.setUnauthorizedUrl("/login.html");
return filter;
}
}
- Shiro认证流程的关键:
- subject调用login方法,将包含用户名和密码的token传递给SecurityManager
- SecurityManager就会调用认证器(Authenticator)进行认证。
- Authenticator将token传递给绑定的Realm,在Realm中进行用户的认证检查;如果认证通过则正常执行,否则抛出认证异常。
@Service
public class UserServiceImpl implements UserService{
@Override
public void checkLogin(String userName, String password) throws Exception {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
subject.login(token);
}
}
springboot整合shiro,使用JdbcRealm
- 导入依赖
- shiro配置
@Configuration
public class ShiroConfig {
@Bean
public JdbcRealm getJdbcRealm(DataSource dataSource) {
JdbcRealm jdbcRealm = new JdbcRealm();
// jdbcRealm自行从数据库中查询用户、权限、角色等信息
jdbcRealm.setDataSource(dataSource);
// jdbcRealm默认开启认证功能,需要手动开启授权功能
jdbcRealm.setPermissionsLookupEnabled(true);
return jdbcRealm;
}
@Bean
public DefaultWebSecurityManager webSecurityManager(JdbcRealm jdbcRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(jdbcRealm);
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
filter.setSecurityManager(securityManager);
// 设置shiro的拦截规则
// anno:匿名用户可以访问
// authc:认证用户可以访问
// user:使用remember me的用户可以访问(认证用户也可以访问)
// perms:拥有对应权限可以访问
// role:拥有对应的角色才可以访问
// logout:表示指定退出的URL
HashMap<String, String> filterMap = new HashMap<>();
filterMap.put("/","anon");
filterMap.put("/login.html","anon");
filterMap.put("/regist.html","anon");
filterMap.put("/user/login","anon");
filterMap.put("/user/regist","anon");
filterMap.put("/static/**","anon");
filterMap.put("/**","authc");
filter.setFilterChainDefinitionMap(filterMap);
filter.setLoginUrl("/login.html");
// 设置未授权访问的页面路径
filter.setUnauthorizedUrl("/login.html");
return filter;
}
}
shiro的标签
1.shiro的标签使用
shiro提供了可以供jsp使用的标签以及Thymeleaf使用的标签。
1.JSP中使用shiro的标签
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
2.Thymeleaf模板中使用shiro的标签
- 在pom.xml中导入thymeleaf模板对shiro标签支持的依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.1.0</version>
</dependency>
- 在shiro配置中配置shiro的方言
@Configuration
public class ShiroConfig {
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
}
- thymeleaf模板中引入shiro的命名空间
<html xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
</html>
2.常用的shiro标签
- guest:判断用户是否为游客身份,如果是游客身份则显示此标签的内容(根据subject的状态判断)
<shiro:guest>
欢迎游客访问
</shiro:guest>
- user:判断用户是否是认证身份,如果是认证身份则显示此标签内容
- principal:获取当前已经登录的用户的用户名
<shiro:user>
用户[<shiro:principal/>]欢迎您!
</shiro:user>
- authenticated/notAuthenticated
- hasRole:判断已登录用户是否有角色
<shiro:hasRole name="admin">超级管理员</shiro:hasRole>
- hasPermission:判断已登录用户是否有权限
<shiro:hasPermission name="sys:c:delete">
<a href="#">出库</a>
</shiro:hasPermission>
自定义Realm实现权限管理
上例中使用shiro内置的Realm,数据表结构需要满足JdbcRealm的要求。自定义Realm实现权限管理,数据表结构是自己定义的,需要实现自己的Dao。Dao中主要完成根据用户名查询用户信息用于认证;根据用户名查询用户的角色列表,根据用户名查询用户的权限列表用于授权管理。
- 数据库设计
- Dao实现
- 根据用户名查询用户信息
- 根据用户名查询角色列表
- 根据用户名查询权限列表
- 整合shiro
- 导入依赖
- shiro配置
- 自定义Realm
- 创建一个类继承AuthorizingRealm类
- 重写
doGetAuthorizationInfo
方法和doGetAuthenticationInfo
方法(分别用于获取授权数据和认证数据) - 重写
getName
方法返回当前Realm的一个自定义名称
加密
在项目开发中通常使用BASE64和MD5编码方式。BASE64是一种可以反编码的编码方式,MD5是一种不可逆的编码方式。
1.shiro中的加密处理
如果数据库表中用户的密码存储的是密文,则使用shiro提供的加密功能,对输入的密码进行加密后再进行认证。
- shiro配置中增加如下内容
@Configuration
public class ShiroConfig {
@Bean
public HashedCredentialsMatcher getHashedCredentialsMatcher() {
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 加密方式
matcher.setHashAlgorithmName("md5");
// hash次数,这里的循环次数要和用户注册时密码的加密次数一致
matcher.setHashIterations(1);
return matcher;
}
@Bean
public JdbcRealm getJdbcRealm(DataSource dataSource, HashedCredentialsMatcher matcher) {
JdbcRealm jdbcRealm = new JdbcRealm();
// jdbcRealm自行从数据库中查询用户、权限、角色等信息
jdbcRealm.setDataSource(dataSource);
// jdbcRealm默认开启认证功能,需要手动开启授权功能
jdbcRealm.setPermissionsLookupEnabled(true);
jdbcRealm.setCredentialsMatcher(matcher);
return jdbcRealm;
}
//....
}
- shiro加密
// 注册的时候需要对密码进行加密处理
String userPwd = "123456";
Md5Hash md5Hash = new Md5Hash(userPwd);
- shiro中加盐加密,如果密码进行了加盐处理则自定义Realm在返回认证数据时需要返回盐。
String userPwd = "123456";
// 加盐
int num = new Random().nextInt(1000);
Md5Hash md5Hash = new Md5Hash(userPwd,num+"");
- shiro中加盐加密,多次hash
String userPwd = "123456";
// 加盐
int num = new Random().nextInt(1000);
// 多次加密
Md5Hash md5Hash = new Md5Hash(userPwd,num+"", 3);
// 或者
SimpleHash hash = new SimpleHash("md5", userPwd, num+"", 3);
shiro中退出登录功能的实现
- 在shiro中的过滤器中增加如下配置
filterMap.put("/exit", "logout");
- 在页面的退出按钮上,跳转到logout对应的url上
<p><a href="/exit">"退出登录"</a></p>
shiro中授权管理
- 权限控制通常有两类做法:
- 不同身份的用户登录,显示不同的操作菜单,没有权限的菜单不显示。
- 对所有用户显示所有菜单,当用户点击菜单以后再验证当前用户是否有此权限,如果没有则提示权限不足。
- 授权管理
- 过滤器授权
// 设置访问delete.html需要有sys:c:delete这个权限名 filterMap.put("/delete.html", "perms[sys:c:delete]"); // 设置未授权访问的页面路径 filter.setUnauthorizedUrl("/login.html");
-
使用
@RequiresPermissions
注解。- 前提需要配置spring对shiro注解的支持
@Configuration public class ShiroConfig { @Bean public DefaultAdvisorAutoProxyCreator getProxyCreator() { DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator(); proxyCreator.setProxyTargetClass(true); return proxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor getAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } // ... }
- 测试
// 如果没有sys:c:delete这个权限则不允许执行该方法 @RequiresPermissions({"sys:c:delete"}) @RequestMapping("/test.html") public String test() { return "delete"; }
- 通过全局异常处理当后台抛出AuthorizationException异常时跳转到友好的提示页面。
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler public String doException(Exception exception) { if (exception instanceof AuthorizationException) { return "lesspermission"; } return null; } }
-
手动授权
@RequestMapping("/test.html") public String test() { Subject subject = SecurityUtils.getSubject(); if (subject.isPermitted("sys:c:delete")) { return "delete"; } else return "lesspermission"; }
shiro中使用缓存
使用Shiro进行权限管理过程中,每次授权都会访问Realm中的doGetAuthorizationlnfo方法查询当前用户的角色及权限信息,如果系统的用户量比较大则会对数据库造成比较大的压力。Shiro支持缓存以降低对数据库的访问压力(缓存的是授权信息)
- 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.3.11.RELEASE</version>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.5.0</version>
</dependency>
- 配置缓存策略:在resources目录下新建ehcache.xml配置文件
- 加入缓存管理
@Configuration
public class ShiroConfig {
@Bean
public EhCacheManager getEhCacheCacheManager() {
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return cacheManager;
}
@Bean
public DefaultWebSecurityManager webSecurityManager(JdbcRealm jdbcRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(jdbcRealm);
// 将缓存管理器设置给SecurityManager
securityManager.setCacheManager(getEhCacheCacheManager());
return securityManager;
}
//....
}
shiro中的session管理
- shiro进行认证和授权是基于session实现的,shiro包含了对session的管理。如果我们需要对session进行管理,则需要
- 自定义session管理器:默认的session过期时间为30分钟
@Bean public DefaultWebSessionManager getSessionManager() { DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); // 配置sessionManager sessionManager.setGlobalSessionTimeout(15 * 1000); // 设置session过期时间为15秒 return sessionManager; }
- 将自定义的session管理器设置给SecurityManager
@Bean public DefaultWebSecurityManager webSecurityManager(JdbcRealm jdbcRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(jdbcRealm); // 将缓存管理器设置给SecurityManager securityManager.setCacheManager(getEhCacheCacheManager()); securityManager.setSessionManager(getSessionManager()); return securityManager; }
shiro中的remember me
- 将用户对页面访问的权限分为三个级别:
- 未认证可以访问的页面
- 记住我可以访问的页面
- 已认证可以访问的页面
- shiro中实现remember me
- 在过滤器中设置“记住我”才可以访问的URL
// 设置shiro的拦截规则 // anno:匿名用户可以访问 // authc:认证用户可以访问 // user:使用remember me的用户可以访问 // perms:拥有对应权限可以访问 // role:拥有对应的角色才可以访问 HashMap<String, String> filterMap = new HashMap<>(); filterMap.put("/index.html","user");
- 在shiro配置中配置基于cookie的rememberMe管理器
@Bean public CookieRememberMeManager cookieRememberMeManager() { CookieRememberMeManager rememberMeManager = new CookieRememberMeManager(); SimpleCookie cookie = new SimpleCookie("remCookie"); // 设置cookie的存活时间 cookie.setMaxAge(1 * 24 * 60 * 60); rememberMeManager.setCookie(cookie); return rememberMeManager; } @Bean public DefaultWebSecurityManager webSecurityManager(JdbcRealm jdbcRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(jdbcRealm); // 设置rememberMe管理器 securityManager.setRememberMeManager(cookieRememberMeManager()); // 将缓存管理器设置给SecurityManager //securityManager.setCacheManager(getEhCacheCacheManager()); securityManager.setSessionManager(getSessionManager()); return securityManager; }
- 登录认证时设置token“记住我”
@Service public class UserServiceImpl implements UserService{ @Override public void checkLogin(String userName, String password, boolean rememberMe) throws Exception { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(userName, password); token.setRememberMe(rememberMe); subject.login(token); } }
shiro中多realm配置
1.使用场景
当使用shiro进行权限管理,数据来自不同的数据源时,可以给SecurityManager配置多个Realm
2.多个Realm的处理方式
- 链式处理:多个Realm依次进行认证。
- 分支处理:根据不同的条件从多个Realm中选择一个进行认证处理。
3.多Realm配置(链式处理)
4.多Realm配置(分支处理)
- 实现案例:不同身份的用户(一个字段标识登录类型)登录执行不同的Realm
- 自定义Realm:例如NormalUserRealm、AdminRealm。当在登陆页面选择普通用户登录,则执行NormalUserRealm的认证;当在登陆页面选择管理员用户登录,则执行AdminRealm的认证;
- shiro配置:将Realm交给spring容器管理,设置给SecurityManager
- 自定义Token,因为UsernamePasswordToken不能存放自定义属性(登录类型)。因此自定义Token继承自UsernamePasswordToken
- 自定义认证器:因为ModularRealmAuthenticator的doAuthenticate方法会执行所有Realm的认证。因此自定义认证器继承ModularRealmAuthenticator,重写doAuthenticate方法
- 配置自定义认证器:securityManager设置自定义认证器
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)