Shiro详细教程
作者 :喜欢编程的代先生
B站 :https://space.bilibili.com/401031438/
Gitee: https://gitee.com/dl19121630/
一、安全框架简介
1.常用的安全框架
- Shiro : Apache Shiro是一个功能强大并且易用的Java安全框架 (小而简单)
- Spring Security : 基于Spring的一个安全框架,依赖Spring
- OAuth2 : 第三方授权登录
2.Shiro 介绍
Shiro是一个功能强大的而且非常灵活的开源框架,可以干净的处理身份验证,授权,企业会话管理和加密。
我们可以使用Shiro进行以下的操作
- 验证用户的身份,也就是我们常说的登录验证功能。
- 对用户执行访问控制,例如:确定是否为用户分配了特定的安全角色,确定是否允许用户做某些事情等。
- 使用Shiro,我们可以在任何环境中使用Session API。
- 单点登录的功能。
- 启用“记住我”服务以进行用户关联,而无需登录。
2.1 Shiro的核心功能
-
Anthentication 认证,验证用户是否有相应的身份—登录认证;
-
Authorization 授权,即权限验证;对已经通过认证的用户检查是否具有某个权限或者角色,从而控制是否能够进行某种操作;
-
Session Managment 会话管理,用户在认证成功之后创建会话,在没有退出之前,当前用户的所有信息都会保存在这个会话中;可以是普通的JavaSE应用,也可以是web应用;
-
Cryptography 加密,对敏感信息进行加密处理,shiro就提供这种加密机制;
-
支持的特性:
- Web Support — Shiro提供了过滤器,可以通过过滤器拦截web请求来处理web应用的访问控制
- Caching 缓存支持,shiro可以缓存用户信息以及用户的角色权限信息,可以提高执行效率
- Concurrency shiro支持多线程应用
- Testing 提供测试支持
- Run As 允许一个用户以另一种身份去访问
- Remeber Me 记住我
-
说明:Shiro是一个安全框架,不提供用户、权限的维护(用户的权限管理需要我们自己去设计)
2.2 Shiro的核心组件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ze8cIQuN-1622364165026)(D:\桌面\Shiro\image\image-20200423155316468.png)] |
---|
- Shiro三大核心组件:Subject、Security Manager、Realms
- Subject,表示待认证和授权的用户
- Security Manager,它是Shiro框架的核心,Shiro就是通过Security Manager来进行内部实例的管理,并通过它来提供安全管理的各种服务。
- Authenticator,认证器
- Anthorizer,授权器
- SessionManager,会话管理器
- CacheManager,缓存管理器
- Realm,相当于Shiro进行认证和授权的数据源,充当了Shiro与安全数据之间的“桥梁”或者“连接器”。也就是说,当对用户进行认证(登录)和授权(访问控制)验证时,Shiro会用应用配置的Realm中查找用户及其权限信息。
二、安全框架的使用
1. 基于JavaSE使用Shiro
1.1 创建maven项目
1.2 导入Shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.7.1</version>
</dependency>
1.3 创建Shiro配置文件
- 第一步:在resource目录下面创建名为shiro.ini的文件
- 第二步:在文件中完成
[users]
zhangsan=123456,seller
lisi=666666,ckmgr
admin=222222,admin
[roles]
admin=*
seller=order-add,order-del,order-list
ckmgr=ck-add,ck-del,ck-list
1.4 Shiro的基本使用
public class TestShiro {
public static void main( String[] args )
{
Scanner scan = new Scanner(System.in);
System.out.println("请输入帐号:");
String username = scan.nextLine();
System.out.println("请输入密码:");
String password = scan.nextLine();
//第一步:创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//第二步,给安全管理器对象设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//第三步:给安全工具类设置安全管理器对象
SecurityUtils.setSecurityManager(securityManager);
//第四步:创建 Subject 主体
Subject subject = SecurityUtils.getSubject();
//第五步,创建令牌
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
try {
System.out.println("subject.login(token)执行之前的认证状态:"+subject.isAuthenticated());
//第六步,用户认证
subject.login(token);
System.out.println("subject.login(token)执行之后的认证状态:"+subject.isAuthenticated());
}catch (UnknownAccountException e){
System.out.println("用户名不存在");
}catch (IncorrectCredentialsException e){
System.out.println("密码不正确");
}
}
}
2.springboot整合Shiro
2.1 创建springboot应用
下面只是罗列一些重要的代码部分,全部的项目源码已经在gitee发布,需要的可以自己去下载,顺便点个星星哈!
源码地址:https://gitee.com/dl19121630/springboot-shrio-01.git
2.2 导入依赖
<!-- shiro启动器 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.7.1</version>
</dependency>
<!-- thymeleaf模板引擎 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.3 创建ShiroConfig配置类
/**
* Created with IntelliJ IDEA.
* 作者: DL代先生
* 日期: 2021/5/28
* 时间: 20:09
* 内容: Shiro与springboot整合教程!
* 描述: shiro的配置
*/
@Configuration
public class ShiroConfig {
/**
* 创建自定义的Realm
* @return
*/
@Bean
public UserRealm getUserRealm(){
//创建自定义的Realm
UserRealm userRealm = new UserRealm();
//创建凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置凭证匹配器采用md5算法进行加密
credentialsMatcher.setHashAlgorithmName("md5");
//设置Hash散列次数
credentialsMatcher.setHashIterations(1024);
//给Realm设置凭证匹配器
userRealm.setCredentialsMatcher(credentialsMatcher);
return userRealm;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm realm){
//创建默认的web安全管理器对象
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//给安全管理器对象设置realm
securityManager.setRealm(realm);
//返回安全管理器对象
return securityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager securityManager){
//创建Shiro的过滤器
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
//设置安全管理器
shiroFilter.setSecurityManager(securityManager);
//设置未认证的时候的访问页面
shiroFilter.setLoginUrl("/page/unLogin");
//设置未授权的时候的访问页面
shiroFilter.setUnauthorizedUrl("/page/unauthorized");
//创建一个map集合
LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
//登录页面可以匿名访问
linkedHashMap.put("/page/login","anon");
//主页面授权之后可以访问
linkedHashMap.put("/page/index","authc");
//设置具有对应权限才可以访问的资源perms[user:delete]
linkedHashMap.put("/page/studentDelete","perms[sys:student:delete]");
linkedHashMap.put("/page/studentUpdate","perms[sys:student:update]");
linkedHashMap.put("/page/studentAdd","perms[sys:student:add]");
shiroFilter.setFilterChainDefinitionMap(linkedHashMap);
return shiroFilter;
}
}
2.4 创建自定义的Realm
/**
* Created with IntelliJ IDEA.
* 作者: DL代先生
* 日期: 2021/5/28
* 时间: 20:08
* 内容: Shiro与springboot整合教程!
* 描述: 自定义Realm
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private UserRoleService userRoleService;
@Autowired
private RoleService roleService;
@Autowired
private RolePermissionService rolePermissionService;
@Autowired
private PermissionService permissionService;
@Override
public String getName() {
return "UserRealm";
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Session session = SecurityUtils.getSubject().getSession(false);
User user = (User) session.getAttribute("user");
UserRole userRole = userRoleService.selectAllUserRoles(user.getUserId());
Role role = roleService.selectRoleById(userRole.getRoleId());
List<RolePermission> rolePermissions = rolePermissionService.selectAllRolePermissions(role.getRoleId());
List<Integer> idLists = new ArrayList<>();
for (RolePermission rolePermission : rolePermissions) {
idLists.add(rolePermission.getPermissionId());
}
List<String> permissions = permissionService.selectAllPermissions(idLists);
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//封装角色
simpleAuthorizationInfo.addRole(role.getRoleName());
//封装权限
simpleAuthorizationInfo.setStringPermissions(new HashSet<>(permissions));
return simpleAuthorizationInfo;
}
/**
* 获得认证的时候需要的安全数据,数据是从数据库中获取的
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
String principal = (String) authenticationToken.getPrincipal();
User user = userService.selectUserByAccount(principal);
if (user != null){
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
principal,//当前用的登录的账户名
user.getUserPassword(),//数据库中存在的密码
ByteSource.Util.bytes(user.getUserSalt()),//盐
getName());//当前Realm的名称
Session session = SecurityUtils.getSubject().getSession();
session.setAttribute("user",user);
return simpleAuthenticationInfo;
}
return null;
}
}
2.5 全局异常配置类
/**
* Created with IntelliJ IDEA.
* 作者: DL代先生
* 日期: 2021/5/28
* 时间: 21:21
* 内容: Shiro与springboot整合教程!
* 描述: 全局异常处理类
*/
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler
public String doException(Exception e){
if (e instanceof UnknownAccountException){
//当前抛出的异常是 用户名不存在
return "redirect:/page/userNotExist";
}
if (e instanceof IncorrectCredentialsException){
//当前捕获的异常是 密码不正确
return "redirect:/page/wrongPassword";
}
if (e instanceof AuthorizationException){
//当前捕获的异常是 没有权限
return "redirect:/page/unauthorized";
}
if (e instanceof AuthenticationException){
//当前捕获的异常是 没有进行认证
return "redirect:/page/unLogin";
}
return "error";
}
}
2.6 随机字符串工具类
/**
* Created with IntelliJ IDEA.
* 作者: DL代先生
* 日期: 2021/5/29
* 时间: 20:01
* 内容: Shiro与springboot整合教程!
* 描述: 获取随机字符串
*/
public class RandomStringUtils {
/**
* 获取指定长度的随机字符串
* @param n
* @return
*/
public static String getString(int n){
StringBuffer stringBuffer = new StringBuffer();
String string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
char[] charArray = string.toCharArray();
Random random = new Random();
int i = 0;
while (i < n){
int nextInt = random.nextInt(61);
stringBuffer.append(charArray[nextInt]);
i++;
}
return stringBuffer.toString();
}
}
2.7 数据库的设计
--
-- 用户表的结构设计 `user`
--
CREATE TABLE `user` (
`user_id` int(10) NOT NULL COMMENT '用户ID',
`user_account` varchar(255) DEFAULT NULL COMMENT '用户账户',
`user_password` varchar(255) DEFAULT NULL COMMENT '账户密码',
`user_salt` varchar(255) DEFAULT NULL COMMENT '盐'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
-- 用户表中的存的数据 `user`
--
INSERT INTO `user` (`user_id`, `user_account`, `user_password`, `user_salt`) VALUES
(1, 'admin', 'f0317d36b2295ada0b35b20b4c484210', 'hjY4n'),
(2, 'dl19121630', 'f0317d36b2295ada0b35b20b4c484210', 'hjY4n');
--
-- 角色表的结构 `role`
--
CREATE TABLE `role` (
`role_id` int(10) NOT NULL COMMENT '角色ID',
`role_name` varchar(255) DEFAULT NULL COMMENT '角色名称'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
-- 角色表中的数据 `role`
--
INSERT INTO `role` (`role_id`, `role_name`) VALUES
(1, 'admin'),
(2, 'student');
--
-- 用户角色表的结构 `user_role`
--
CREATE TABLE `user_role` (
`user_id` int(10) DEFAULT NULL COMMENT '用户ID',
`role_id` int(10) DEFAULT NULL COMMENT '角色ID'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
-- 用户角色表中的数据 `user_role`
--
INSERT INTO `user_role` (`user_id`, `role_id`) VALUES
(1, 1),
(2, 2);
--
-- 权限表的结构 `permission`
--
CREATE TABLE `permission` (
`permission_id` int(10) NOT NULL COMMENT '权限ID',
`permission_name` varchar(255) DEFAULT NULL COMMENT '权限名称'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
-- 权限表中的数据 `permission`
--
INSERT INTO `permission` (`permission_id`, `permission_name`) VALUES
(1, 'sys:student:add'),
(2, 'sys:student:delete'),
(3, 'sys:student:update'),
(4, 'sys:student:select');
--
-- 角色权限表的结构 `role_permission`
--
CREATE TABLE `role_permission` (
`role_id` int(10) DEFAULT NULL COMMENT '角色ID',
`permission_id` int(10) DEFAULT NULL COMMENT '权限ID'
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
--
-- 角色权限表中的数据 `role_permission`
--
INSERT INTO `role_permission` (`role_id`, `permission_id`) VALUES
(1, 1),
(1, 2),
(1, 3),
(1, 4),
(2, 4);
3.Shiro的标签的使用
当用户认证进入到主页面之后,需要显示用户信息及当前用户的权限信息;
Shiro就提供了一套标签用于在页面来进行权限数据的呈现
3.1 Shrio标签的配置
-
Shiro提供了可供JSP页面使用的标签以及Thymeleaf中的标签
-
JSP页面中引用
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
-
Thymeleaf模板中引用
-
在pom.xml文件中导入thymeleaf模板对shrio标签支持的依赖
<dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency>
-
在ShiroConfig中配置Shiro的方言
@Configuration public class ShiroConfig { @Bean public ShiroDialect getShiroDialect(){ return new ShiroDialect(); } //... 下面的配置省略, }
-
在Thymeleaf模板中引入Shiro的命名空间
<html xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> ... </html>
-
-
3.2 常用的Shiro的标签
-
guest:判断用户是否是游客身份,如果是游客身份则显示此标签里面的内容
<shiro:guest> 欢迎游客访问,<a href="login.html">登录</a> </shiro:guest>
-
user:判断用户是否是认证身份,如果是认证身份则显示此标签里面的内容
-
principal:获取当前登录的用户名
<shiro:user> 用户[<shiro:principal/>]欢迎您!<a href="logout.html">退出</a> </shiro:user>
-
hasRole:判断用户是否拥有指定的角色,如果具有指定的角色则显示此标签里面的内容
<shiro:hasRole name="admin">管理人员</shiro:hasRole>
-
hasPermission:判断用户是否拥有指定的权限,如果具有指定的权限则显示此标签里面的内容
<shiro:hasPermission name="sys:student:delete"><li><a href="#">删除学生</a></li></shiro:hasPermission>
-
authenticated:判断用户是否是登录状态,如果是在登录状态则显示此标签里面内容。
<shiro:authenticated> 你已经认证通过啦,欢迎您:<shiro:principal/> </shiro:authenticated>
-
notAuthenticated:用户处于未登录的状态的时候显示此标签里面的内容,记住我功能,也算是未登录。
<shiro:notAuthenticated> 游客您好,<a href="@">请登录!</a> </shiro:notAuthenticated>
3.3 Shiro的其他的标签介绍
当name的参数有多个的时候,以逗号进行分割;
例如
<shiro:hasAllPermissions name="sys:student:delete,sys:student:update"></shiro:hasAllPermissions>
标签名称 | 标签的作用 |
---|---|
lacksRole | 当前用户没有任何角色则显示标签体中的内容 |
hasAnyRoles | 当前用户拥有任何一个角色则显示标签体中的内容 |
hasAllRoles | 当前用户拥有指定的所有角色则显示标签体中的内容 |
lacksPermission | 当前用户拥有任何一个权限则显示标签体中的内容 |
hasAnyPermissions | 当前用户拥有任何一个权限则显示标签体中的内容 |
hasAllPermissions | 当前用户拥有指定的所有权限则显示标签体中的内容,一个或多个角色和权限的在项目中会经常使用 |
4.Shiro的密码加密
Shrio对密码的加密规则我们可以自己定义,在真实的项目开发过程中,我们通常使用的是BASE64和MD5两种加密的方式。
- BASE64:可反编码的加密方式(对称性加密方式)
- 明文 ----- 密文
- 密文 ----- 明文
- MD5:不可逆的加密方式(非对称性加密方式)
- 明文 ----- 密文
4.1 Shiro加密方式的配置
用户在注册是的时候要对用户的密码进行加密
Md5Hash md5Hash = new Md5Hash(password,salt,1024); System.out.println("加密之后的密码:"+ md5Hash.toHex());
@Configuration
public class ShiroConfig {
//...
@Bean
public HashedCredentialsMatcher getHashedCredentialsMatcher(){
//创建凭证匹配器
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//设置凭证匹配器采用md5算法进行加密
credentialsMatcher.setHashAlgorithmName("md5");
//设置Hash散列次数
credentialsMatcher.setHashIterations(1024); //此处的循环次数要与用户注册是密码加密次数一致
return credentialsMatcher;
}
//自定义Realm
@Bean
public UserRealm getUserRealm(HashedCredentialsMatcher credentialsMatcher){
//创建自定义的Realm
UserRealm userRealm = new UserRealm();
//给Realm设置凭证匹配器
userRealm.setCredentialsMatcher(credentialsMatcher);
return userRealm;
}
//... 其余配置省略
}
5. Shiro的退出功能
-
我们可以选择在Shiro的过滤器中进行配置,配置指定的logout路径
linkedHashMap.put("/exit","logout");
-
然后在页面的退出按钮上,跳转到logout对应的url
<a href="exit">退出</a>
-
当然,我们也可以采用手动编码的方式实现退出的功能,我们只需要调用subject的logout方法即可
Subject subject = SecurityUtils.getSubject(); subject.logout();
6.Shiro注解方式授权
-
在ShiroConfig中配置Spring对Shiro的注解的支持
@Configuration public class ShiroConfig { @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator autoProxyCreator = new DefaultAdvisorAutoProxyCreator(); autoProxyCreator.setProxyTargetClass(true); return autoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor( DefaultWebSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } //其余代码省略..... }
-
在请求的控制其中,添加注解权限
@Controller @RequestMapping("customer") public class CustomerController { @RequestMapping("list") //如果没有 sys:customer:find 权限,则不允许执行此方法 @RequiresPermissions("sys:customer:find") public String list(){ System.out.println("----------->查询客户信息"); return "customer_list"; } }
-
通过全局异常处理,指定权限不足时的页面跳转
/** * Created with IntelliJ IDEA. * 作者: DL代先生 * 日期: 2021/5/28 * 时间: 21:21 * 内容: Shiro与springboot整合教程! * 描述: 全局异常处理类 */ @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler public String doException(Exception e){ if (e instanceof UnknownAccountException){ //当前抛出的异常是 用户名不存在 return "redirect:/page/userNotExist"; } if (e instanceof IncorrectCredentialsException){ //当前捕获的异常是 密码不正确 return "redirect:/page/wrongPassword"; } if (e instanceof AuthorizationException){ //当前捕获的异常是 没有权限 return "redirect:/page/unauthorized"; } if (e instanceof AuthenticationException){ //当前捕获的异常是 没有进行认证 return "redirect:/page/unLogin"; } return "error"; } }
7.Shiro的缓存使用
7.1 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
7.2 配置缓存策略
在resources目录下创建一个xml文件(ehcache.xml)
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" dynamicConfig="false">
<!-- 这个路径自己更改 -->
<diskStore path="C:\TEMP" />
<cache name="users" timeToLiveSeconds="300" maxEntriesLocalHeap="1000"/>
<defaultCache name="defaultCache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
maxElementsOnDisk="100000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"/>
<!--缓存淘汰策略:当缓存空间比较紧张时,我们要存储新的数据进来,就必然要删除一些老的数据
LRU 最近最少使用
FIFO 先进先出
LFU 最少使用
-->
</ehcache>
7.3 加入缓存管理
@Configuration
public class ShiroConfig {
@Bean
public EhCacheManager getEhCacheManager(){
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
return ehCacheManager;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myRealm);
securityManager.setCacheManager(getEhCacheManager());
return securityManager;
}
//其余代码省略......
}
8. session管理
Shiro进行认证和授权是基于session实现的,Shiro包含了对session的管理
-
如果我们需要对session进行管理,我们可以:
- 自定义session管理器
- 将自定义的session管理器设置给SecurityManager
-
在ShiroConfig中配置自定的SessionManager:
@Configuration public class ShiroConfig { @Bean public DefaultWebSessionManager getDefaultWebSessionManager(){ DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); System.out.println("----------"+sessionManager.getGlobalSessionTimeout()); // 1800000 //配置sessionManager sessionManager.setGlobalSessionTimeout(5*60*1000); return sessionManager; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm); securityManager.setCacheManager(getEhCacheManager()); securityManager.setSessionManager(getDefaultWebSessionManager()); return securityManager; } //其余代码省略........ }
9.RememberMe
9.1 设置记住我可访问的url
linkedHashMap.put("/page/index","user");
权限名称 | 作用 |
---|---|
anon | 表示未认证可访问的url |
user | 表示记住我可访问的url(已认证也可以访问) |
authc | 表示已认证可访问的url |
perms | 表示必须具备指定的权限才可访问 |
logout | 表示指定退出的url |
9.2 配置RememberMe管理器
@Configuration
public class ShiroConfig {
@Bean
public CookieRememberMeManager cookieRememberMeManager(){
CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
//cookie必须设置name
SimpleCookie cookie = new SimpleCookie("rememberMe");
cookie.setMaxAge(30*24*60*60);
rememberMeManager.setCookie(cookie);
return rememberMeManager;
}
@Bean
public DefaultWebSecurityManager getDefaultWebSecurityManager(UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
securityManager.setCacheManager(getEhCacheManager());
securityManager.setSessionManager(getDefaultWebSessionManager());
//设置remember管理器
securityManager.setRememberMeManager(cookieRememberMeManager());
return securityManager;
}
}
9.3 登录的时候设置token记住我
-
前端页面
<form action="/user/login" method="post"> <p>帐号:<input type="text" name="userName"/></p> <p>密码:<input type="text" name="userPwd"/></p> <p>记住我:<input type="checkbox" name="rememberMe"/></p> <p><input type="submit" value="登录"/></p> </form>
-
controller层
@Controller @RequestMapping("user") public class UserController { @Resource private UserServiceImpl userService; @RequestMapping("login") public String login(String userName,String userPwd,boolean rememberMe){ try { userService.checkLogin(userName,userPwd,rememberMe); System.out.println("------登录成功!"); return "index"; } catch (Exception e) { System.out.println("------登录失败!"); return "login"; } } //... }
-
service层
@Service public class UserServiceImpl { public void checkLogin(String userName, String userPwd,boolean rememberMe) throws Exception { //Shiro进行认证 ——入口 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(userName,userPwd); //设置记住我 token.setRememberMe(rememberMe); subject.login(token); } }
10. 多Realm的配置
10.1 链式处理方式
- 多个realm依次进行认证
10.2 分支处理方式
- 根据不同的条件从多个Realm中选择一个进行认证处理
10.3 多个Realm 链式处理
-
首先,定义多个Realm
-
UserRealm
public class UserRealm extends AuthorizingRealm { @Override public String getName() { return "UserRealm"; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){ //执行授权的操作,代码这里省略 return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //执行认证的操作,代码这里省略 return info; } }
-
ManagerRealm
public class ManagerRealm extends AuthorizingRealm { @Override public String getName() { return "ManagerRealm"; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){ //执行授权的操作,代码这里省略 return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //执行认证的操作,代码这里省略 return info; } }
-
在ShiroConfig.java中为SecurityManager配置多个Realm
@Configuration public class ShiroConfig { @Bean public UserRealm userRealm(){ UserRealm userRealm = new UserRealm(); return userRealm; } @Bean public ManagerRealm managerRealm(){ ManagerRealm managerRealm = new ManagerRealm(); return managerRealm; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //securityManager中配置多个realm Collection<Realm> realms = new ArrayList<>(); realms.add(userRealm()); realms.add(managerRealm()); securityManager.setRealms(realms); return securityManager; } //... }
-
-
认证策略
当为一个应用程序配置两个或多个
Realm
时,ModularRealmAuthenticator
依赖于内部AuthenticationStrategy
组件来确定认证尝试成功或失败的条件。例如,如果只有一个
Realm
认证AuthenticationToken
成功,而其他所有认证都失败,则该认证尝试是否被视为成功?还是所有Realm
都必须成功进行身份验证才能将整体尝试视为成功?或者,如果某个Realm
成功通过身份验证,是否有必要进一步咨询其他Realm
?一种AuthenticationStrategy
使基于应用程序的需要作出适当的决定。Shiro有3种具体的
AuthenticationStrategy
实现方式:AuthenticationStrategy 描述 AtLeastOneSuccessfulStrategy 如果一个(或多个)Realm成功认证,则整个尝试都被视为成功。如果没有成功进行身份验证,则尝试将失败。 FirstSuccessfulStrategy 仅使用从第一个成功通过身份验证的Realm返回的信息。所有其他Realm将被忽略。如果没有成功通过身份验证,则尝试将失败 AllSuccessfulStrategy 所有配置的Realm都必须成功进行身份验证,才能将整体尝试视为成功。如果任何人未成功通过身份验证,则尝试将失败。
10.4 多个Realm 分支处理
在这种模式下配置的Shiro,会选择性的执行!
有时候我们在开发过程中可能会碰到一种需求。举个例子来说:现在有两种用户,一个是管理员用户,一个是普通用户,我们需要对这两个用户进行权限管理,这个时候,我们就需要分别配置ManagerRealm和UserRealm,但是如果我们按照链式的方式直接配置的话,这两个都会执行,这显然不是我们想要的,我们想要的是,如果当前登录的是管理员,我们只执行ManagerRealm,如果登录的是用户,我们只执行UserRealm。这个时候,我们就要重写Shiro的一些东西。
-
实现案例 用户不同身份登录执行不同的Realm
-
自定义Realm(UserRealm\ManagerRealm)
- 当在登录页面选择“普通用户”登录,则执行UserRealm的认证
- 当在登录页面选择“管理员”登录,则执行ManagerRealm的认证
-
Realm的声明及配置
-
自定义UsernamePasswordToken
public class MyUsernamePasswordToken extends UsernamePasswordToken { private String loginRole; public MyUsernamePasswordToken(String username,String password,String loginRole){ super(username,password); this.loginRole = loginRole; } public String getLoginRole() { return loginRole; } public void setLoginRole(String loginRole) { this.loginRole = loginRole; } }
-
自定义认证器 ModularRealmAuthenticator
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator{ /** * 重写认证的方法 * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { //获得全部的Realms Collection<Realm> realms = this.getRealms(); //将 AuthenticationToken 强行转为自定义的 MyUsernamePasswordToken MyUsernamePasswordToken myUsernamePasswordToken = (MyUsernamePasswordToken) authenticationToken; //获得登录的角色 String loginRole = myUsernamePasswordToken.getLoginRole(); //创建一个新的集合用来存储对应角色的 Realm Collection<Realm> typeRealms = new ArrayList<>(); for(Realm realm : realms){ if (realm.getName().startsWith(loginRole)){ typeRealms.add(realm); } } //判断如果是单个realm 则执行单个的认证方法,如果是多个,执行多个的认证方法 if (typeRealms.size() == 1){ return this.doSingleRealmAuthentication(typeRealms.iterator().next(),myUsernamePasswordToken); }else { return this.doMultiRealmAuthentication(typeRealms,myUsernamePasswordToken); } } }
-
配置自定义认证器
@Configuration public class ShiroConfig { @Bean public UserRealm userRealm(){ UserRealm userRealm = new UserRealm(); return userRealm; } @Bean public ManagerRealm managerRealm(){ ManagerRealm managerRealm = new ManagerRealm(); return managerRealm; } @Bean public MyModularRealmAuthenticator myModularRealmAuthenticator(){ MyModularRealmAuthenticator myModularRealmAuthenticator = new MyModularRealmAuthenticator(); return myModularRealmAuthenticator; } @Bean public DefaultWebSecurityManager getDefaultWebSecurityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //配置自定义认证器(放在realms设置之前) securityManager.setAuthenticator(myModularRealmAuthenticator()); //securityManager中配置多个realm Collection<Realm> realms = new ArrayList<>(); realms.add(userRealm()); realms.add(managerRealm()); securityManager.setRealms(realms); return securityManager; } //其余代码省略。。。。。 }
-
喜欢的点赞收藏哈!
DL代先生