目录
Shiro
概述
shiro是什么
与 SpringSecurity 的对比
基本功能
介绍
原理
基本使用
环境搭建
引入依赖
ini文件
登录认证
登录认证概念
登录认证基本流程
代码实现
角色授权
授权概念
授权方式
授权流程
代码实现
Shiro使用MD5加密,Md5Hash类
自定义登陆认证,继承AuthenticatingRealm类
Shiro整合springboot
环境准备(依赖、yml、sql、entity/mapper/service/serviceImpl)
登录认证实现
service编写获取用户信息方法
自定义登陆认证,realm
编写shiro配置类
controller编写登录方法
启动测试
前端页面,shiro整合Thymeleaf
多个 realm 的认证策略设置
实现原理
代码实现
remember me
代码实现
退出登陆
代码实现
授权、角色认证
后端接口服务注解
授权验证-获取角色进行验证
授权验证-获取权限进行验证
前端页面授权验证
Apache Shiro 是一个功能强大且易于使用的 Java 安全(权限)框架。Shiro 可以完成:认证、授权、加密、会话管理、与 Web 集成、缓存 等。借助 Shiro 您可以快速轻松地保护任何应用程序——从最小的移动应用程序到最大的 Web 和企业应用程序。
自 2003 年以来,框架格局发生了相当大的变化,因此今天仍然有很多系统在使用 Shiro。这与 Shiro 的特性密不可分。
Shiro架构(外部)
从外部来看 Shiro ,即从应用程序角度的来观察如何使用Shiro 完成工作
shiro架构(内部)
shiro核心包和通用日志包
org.apache.shiro shiro-core 1.9.0 commons-logging commons-logging 1.2
resources目录下创建ini文件:
[users]
zhangsan=1234
lisi=123
四步走:
@Testvoid login(){
// 1.初始化获取SecurityManagerIniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);
// 2.获取subject对象Subject subject = SecurityUtils.getSubject();
// 3.创建token对象,web应用用户名密码从页面传递UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "1234");
// 4.完成登录。try{subject.login(usernamePasswordToken);System.out.println("登陆成功");}catch(UnknownAccountException e){e.printStackTrace();System.out.println("用户不存在");}catch(IncorrectCredentialException e){e.printStackTrace();System.out.println("密码错误");}catch(AuthenticationException e){e.printStackTrace();System.out.println("其他原因登录失败");}}
主体(Subject)
、资源(Resource)
、权限 (Permission)
、角色(Role)
。编程式:
subject.hasRole("admin")
注解式:
@RequiresRoles("admin")
JSP/GSP 标签:
添加角色和权限:
一般情况下会赋予用户角色而不是权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限
@Testvoid testLogin(){// 1.初始化获取SecurityManagerIniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);
// 2.获取subject对象Subject subject = SecurityUtils.getSubject();
// 3.创建token对象,web应用用户名密码从页面传递UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("zhangsan", "z3");
// 4.完成登录subject.login(usernamePasswordToken);System.out.println(usernamePasswordToken);//判断角色System.out.println("当前subject是否拥有此角色:"+subject.hasRole("role1"));
//判断权限方法1System.out.println("当前subject是否拥有此权限:"+subject.isPermitted("user:insert")); //判断权限方法2,无此权限直接抛出权限异常AuthenticationException subject.checkPermission("user:update");
实际系统开发中,一些敏感信息需要进行加密,比如说用户的密码。Shiro 内嵌很多常用的加密算法,比如 MD5 加密。Shiro 可以很简单的使用信息加密。
MD5为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。MD5与对称和非对称加密算法不同,这两种密码是防止信息被窃取,而摘要算法的目标是用于证明原文的完整性。
虽然在04年被证明可以被破解,因此不适用于安全性认证,如SSL公开密钥认证或是数字签名等用途,但是MD5可以通过多次加密确保安全性。
@Test
void testMD5(){
//密码明文String t= "z3";String password = t;
//使用md5加密Md5Hash md5Hash = new Md5Hash(password);
//md5Hash.toHex()是转成16进制字符串System.out.println("md5加密 = " + md5Hash.toHex()); //a61d1457beb4684e254ce60379c8ae7b
//带盐的md5加密。盐就是在密码明文后拼接字符串,然后再加密。Md5Hash saltMd5Hash = new Md5Hash(password, "salt");System.out.println("带盐的md5加密 = " + saltMd5Hash);
//带盐的多次md5加密。为了避免被破解,可以多次迭代加密Md5Hash saltMd5Hash3 = new Md5Hash(password, "salt", 3);System.out.println("带盐的3次md5加密 = " + saltMd5Hash3);//使用Md5Hash的父类SimpleHash加密。SimpleHash simpleHash = new SimpleHash("MD5", password, "salt", 3);System.out.println("父类的三次带盐加密 = " + simpleHash);assert simpleHash.equals(saltMd5Hash3);
}
Shiro 默认的登录认证是不带加密的,如果想要实现加密认证需要自定义登录认证, 自定义 Realm。
Realm:Realm提供待验证数据的比对值,即安全数据源,可以理解为数据的源头,可以是数据库,文件等。 Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource
登录认证基本流程
- 收集用户身份/凭证,即如用户名/密码
- 调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException异常,根据异常提示用户错误信息;否则登录成功
- 登录认证。创建自定义的 Realm 类,继承AuthenticatingRealm类,实现 doGetAuthenticationInfo() 方法
获取进行对比的信息,认证逻辑还是按照Shiro的底层认证逻辑完成认证:
public class MyRealm extends AuthenticatingRealm {/***自定义的登录认证方法,Shiro的login方法底层会调用该类的认证方法完成登录认证*需要配置自定义的realm生效,在ini文件中配置,或者Springboot中配置*该方法只是获取进行对比的信息,认证逻辑还是按照Shiro的底层认证逻辑完成认证* @param token 令牌* @return {@link AuthenticationInfo}* @throws AuthenticationException 身份验证异常*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1 获取身份信息(用户名)String principal = token.getPrincipal().toString();// 2 获取凭证信息(密码)String credentials = new String((char[]) token.getCredentials());System.out.println("认证用户信息:" + principal+"---"+ credentials); //认证用户信息:zhangsan---z3// 3 比对数据库里查到的用户名和token里获得的用户名。这里用“zhangsan”假装是数据库查到的用户名if("zhangsan".equals(principal)) {String t= "7174f64b13022acd3c56e2781e098a5f";String pwd = t;//"zhangsan"三次带盐md5加密后的密文return new SimpleAuthenticationInfo(token.getPrincipal(),pwd, //数据库里查到的密码ByteSource.Util.bytes("salt"), //shiro工具类获取md5加盐信息principal);}
// 4 创建封装校验逻辑对象return null;}
}
添加配置信息,让shiro知晓你使用的是自定义的Realm
#在shiro.ini中添加配置信息
[main]md5CredentialsMatcher=org.apache.shiro.authc.cre
dential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3
myrealm=com.atguigu.shirotest.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1, role2
lisi=l4
[roles]
role1=user:insert,user:select
启动测试:
数据库表
在shirodb数据库下创建表:
create table user
(id bigint auto_increment comment '编号'primary key,name varchar(30) null comment '用户名',pwd varchar(50) null comment '密码',rid bigint null comment '角色编号'
)comment '用户表' charset = utf8;
org.springframework.boot spring-boot-starter-parent 2.2.1.RELEASE
org.apache.shiro shiro-spring-boot-web-starter 1.9.0 com.baomidou mybatis-plus-boot-starter 3.0.5 mysql mysql-connector-java 5.1.46 org.projectlombok lombok org.springframework.boot spring-boot-starter-thymeleaf org.springframework.boot spring-boot-starter-web
# mybatis配置
mybatis-plus:configuration:# 日志log-impl: org.apache.ibatis.logging.stdout.StdOutImplmapper-locations: classpath:mapper/*.xmlspring:
# 数据库配置datasource:type: com.zaxxer.hikari.HikariDataSourcedriver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/shirodb?characterEncoding=utf-8&useSSL=falsepassword: passwordusername: username
# controller的@ResponseBody将方法返回对象转换后的json格式jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8 #东八区shiro:
# 登录接口loginUrl: /myController/login
@SpringBootApplication
@MapperScan("com.fate.shiro.mapper")
public class ShiroApplication {public static void main(String[] args) {SpringApplication.run(ShiroApplication.class, args);}
}
实体类与mapper/service
mapper继承BaseMapper,serverice继承IService。
@Service
public class UserServiceImpl extends ServiceImplimplements UserService{/*** 根据用户名得到用户信息** @param name 名字* @return {@link User}*/@Overridepublic User getUserInfoByName(String name) {LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();queryWrapper.eq(User::getName,name);return baseMapper.selectOne(queryWrapper);}
}
在realm包下:
Realm:Realm提供待验证数据的比对值,即安全数据源,可以理解为数据的源头,可以是数据库,文件等。 Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource
注意:
这里继承的是 AuthorizingRealm类 ,之前基本使用里继承的是AuthenticatingRealm类。
AuthorizingRealm是AuthenticatingRealm的子类,在自定义登录方法的基础上,多了一个自定义授权的方法。
@Component
public class MyRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;/*** 自定义授权** @param principals 权限* @return {@link AuthorizationInfo}*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {return null;}/*** 自定义身份验证** @param token 令牌* @return {@link AuthenticationInfo}* @throws AuthenticationException 身份验证异常*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1. 获取传来token的用户身份信息(用户名)。Principal译为当事人、校长、首要的String name = token.getPrincipal().toString();
// 2. 调用业务层获取用户信息User user = userService.getUserInfoByName(name);
// 3. 如果根据token里的用户名从数据库里查到了数据,将数据封装返回if (user != null) {return new SimpleAuthenticationInfo(token.getPrincipal(), user.getPwd(), //数据库的密码ByteSource.Util.bytes("salt"), //shiro工具类获取md5加盐信息token.getPrincipal().toString());}return null;}
}
@Slf4j
@Configuration
public class ShiroConfig {@Autowiredprivate MyRealm myRealm;//配置SecurityManager。SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且其管理着所有Subject;可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC中DispatcherServlet的角色@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager() {
// 1. 创建DefaultWebSecurityManager对象DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
// 2. 创建加密对象,配置相关属性HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 2.1 加密,md5迭代三次matcher.setHashAlgorithmName("md5");matcher.setHashIterations(3);
// 3. 将加密对象存储到myRealm中myRealm.setCredentialsMatcher(matcher);
// 4. 将myRealm存入DefaultWebSecurityManager对象defaultWebSecurityManager.setRealm(myRealm);
// 5. 返回DefaultWebSecurityManagerlog.info("DefaultWebSecurityManager 初始化成功");return defaultWebSecurityManager;}//配置shiro内置过滤器拦截范围@Beanpublic DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();
// 设置无需认证即可访问的资源。defaultShiroFilterChainDefinition.addPathDefinition("/myController/userLogin","anon");defaultShiroFilterChainDefinition.addPathDefinition("/myController/login","anon");
// 设置需要认证的拦截范围defaultShiroFilterChainDefinition.addPathDefinition("/**","authc");return defaultShiroFilterChainDefinition;}}
@RestController
@RequestMapping("myController")
public class MyController {/*** 登录* @param username 用户名* @param password 密码* @return {@link String}*/@GetMapping("userLogin")public String login(@RequestParam("username") String username, @RequestParam("password") String password,HttpSession
session){//1.获取主体对象subjectSubject subject = SecurityUtils.getSubject();//2.封装用户名和密码到tokenUsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);//3.调用subject.login()登录验证try{subject.login(usernamePasswordToken);session.setAttribute("user",token.getPrincipal().toString()); return "登陆成功";}catch (Exception e) {e.printStackTrace();return "登陆失败";}}
}
数据库里存的也是“张三” 和md5三次加密后的“z3”。
编写登录页面login.html
Title
Shiro 登录认证
添加登陆成功后的主页面main界面
Title
Shiro 登录认证后主页面
登录用户为:
controller登录方法添加session、添加跳转页面方法
//跳转登录页面
@GetMapping("login")
public String login(){
//跳转到login页面,因为没有@ResponseBody,所以会把返回值当成页面名称去查找,如果没有回报404错误return "login";
}
//登录认证
@GetMapping("userLogin")
public String userLogin(String name, String pwd, HttpSession
session){
//1 获取 Subject 对象Subject subject = SecurityUtils.getSubject();
//2 封装请求数据到 token 对象中AuthenticationToken token = new UsernamePasswordToken(name,pwd);
//3 调用 login 方法进行登录认证try {subject.login(token);session.setAttribute("user",token.getPrincipal().toString());
//跳转到main页面return "main";} catch (AuthenticationException e) { e.printStackTrace();System.out.println("登录失败"); return "登录失败";}
}
启动测试:
当应用程序配置多个 Realm 时,例如:用户名密码校验、手机号验证码校验等等。 Shiro 的 模块化认证器ModularRealmAuthenticator 会使用内部的AuthenticationStrategy 组件判断认证是成功还是失败。
AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问 4 次(这 4 次交互所需的任何必要的状态将被作为方法参数):
认证策略的另外一项工作就是聚合所有 Realm 的结果信息封装至一个AuthenticationInfo 实例中,并将此信息返回,以此作为 Subject 的身份信息。
Shiro 中定义了 3 种认证策略的实现:
AuthenticationStrategy class | 描述 |
---|---|
AtLeastOneSuccessfulStrategy | 只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功 |
FirstSuccessfulStrategy | 第一个 Realm 验证成功,整体认证将视为成功,且后续 Realm 将被忽略 |
AllSuccessfulStrategy | 所有 Realm 成功,认证才视为成功 |
ModularRealmAuthenticator
内置的认证策略默认实现是 AtLeastOneSuccessfulStrategy
方式。可以通过配置修改策略
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
//1 创建 defaultWebSecurityManager 对象DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2 创建认证对象,并设置认证策略ModularRealmAuthenticator modularRealmAuthenticator = new
ModularRealmAuthenticator();modularRealmAuthenticator.setAuthenticationStrategy(new
AllSuccessfulStrategy());defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator)
;
//3 封装 myRealm 集合List list = new ArrayList<>(); list.add(myRealm);list.add(myRealm2);
//4 将 myRealm 存入 defaultWebSecurityManager 对象defaultWebSecurityManager.setRealms(list);
//5 返回return defaultWebSecurityManager;
}
Shiro 提供了记住我(RememberMe)的功能,比如访问一些网站时,关闭了浏览器, 下次再打开时还是能记住你是谁, 下次访问时无需再登录即可访问。
基本流程
修改配置类
@Configuration
public class ShiroConfig {@Autowiredprivate MyRealm myRealm;//配置 SecurityManager@Beanpublic DefaultWebSecurityManager defaultWebSecurityManager() {
//1 创建 defaultWebSecurityManager 对象DefaultWebSecurityManager defaultWebSecurityManager = newDefaultWebSecurityManager();
//2 创建加密对象,并设置相关属性 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//2.1 采用 md5 加密matcher.setHashAlgorithmName("md5");
//2.2 迭代加密次数matcher.setHashIterations(3);
//3 将加密对象存储到 myRealm 中myRealm.setCredentialsMatcher(matcher);
//4 将 myRealm 存入 defaultWebSecurityManager 对象 defaultWebSecurityManager.setRealm(myRealm);
//4.5 设置 rememberMedefaultWebSecurityManager.setRememberMeManager(rememberMeManager());
//5 返回return defaultWebSecurityManager;}//cookie 属性设置public SimpleCookie rememberMeCookie() {SimpleCookie cookie = new SimpleCookie("rememberMe");
//设置跨域
//cookie.setDomain(domain);cookie.setPath("/");cookie.setHttpOnly(true);cookie.setMaxAge(30 * 24 * 60 * 60);return cookie;}//创建 Shiro 的 cookie 管理对象public CookieRememberMeManager rememberMeManager() {CookieRememberMeManager cookieRememberMeManager = newCookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());return cookieRememberMeManager;}//配置 Shiro 内置过滤器拦截范围@Beanpublic DefaultShiroFilterChainDefinitionshiroFilterChainDefinition() {DefaultShiroFilterChainDefinition definition = newDefaultShiroFilterChainDefinition();
//设置不认证可以访问的资源definition.addPathDefinition("/myController/userLogin", "anon");definition.addPathDefinition("/myController/login", "anon");
//设置需要进行登录认证的拦截范围definition.addPathDefinition("/**", "authc");
//添加存在用户的过滤器(rememberMe) definition.addPathDefinition("/**", "user");return definition;}
}
修改controller
@GetMapping("userLogin")public String userLogin(String name, String pwd, @RequestParam(defaultValue ="false") boolean rememberMe, HttpSession session) {
//1 获取 Subject 对象Subject subject = SecurityUtils.getSubject();
//2 封装请求数据到 token 对象中AuthenticationToken token = new UsernamePasswordToken(name, pwd, rememberMe);
//3 调用 login 方法进行登录认证try {subject.login(token);session.setAttribute("user", token.getPrincipal().toString());return "main";} catch (AuthenticationException e) {e.printStackTrace();System.out.println("登录失败");return "登录失败";}}//登录认证验证 rememberMe@GetMapping("userLoginRm")public String userLogin(HttpSession session) {session.setAttribute("user", "rememberMe");return "main";}
改造login页面
Title
Shiro 登录认证
用户登录后,配套的有登出操作。直接通过Shiro过滤器即可实现登出
Shiro 登录认证后主页面
登录用户为:
登出
@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition definition = new
DefaultShiroFilterChainDefinition();
//设置不认证可以访问的资源
definition.addPathDefinition("/myController/userLogin","anon");
definition.addPathDefinition("/myController/login","anon");
//配置登出过滤器
definition.addPathDefinition("/logout","logout");
//设置需要进行登录认证的拦截范围
definition.addPathDefinition("/**","authc");
//添加存在用户的过滤器(rememberMe)
definition.addPathDefinition("/**","user");
return definition;
}
用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进判
这个工具就是Realm的doGetAuthorizationInfo方法进行判断。触发权限判断的有两种:
通过给接口服务方法添加注解可以实现权限校验,可以加在控制器方法上,也可以加
在业务方法上,一般加在控制器方法上。常用注解如下:
@RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated()
@RequiresUser
验证用户是否被记忆:
登录认证成功subject.isAuthenticated()为true
登录后被记忆subject.isRemembered()为true
@RequiresGuest
验证是否是一个guest的请求,是否是游客的请求
此时subject.getPrincipal()为null
@RequiresRoles
验证subject是否有相应角色,有角色访问方法,没有则会抛出异常
AuthorizationException。
例如:@RequiresRoles(“aRoleName”)
void someMethod();
只有subject有aRoleName角色才能访问方法someMethod()
@RequiresPermissions
验证subject是否有相应权限,有权限访问方法,没有则会抛出异常
AuthorizationException。
例如:
@RequiresPermissions (“file:read”,”wite:aFile.txt”)
void someMethod();
subject必须同时含有file:read和wite:aFile.txt权限才能访问方someMethod()
修改MyRealm
@Override
protected AuthorizationInfo
doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("进入自定义授权方法");
//1 创建对象,存储当前登录的用户的权限和角色SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//2 存储角色info.addRole("admin");
//返回return info;
}
添加数据库表
CREATE TABLE `role` (`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', `name` VARCHAR(30) DEFAULT NULL COMMENT '角色名',`desc` VARCHAR(50) DEFAULT NULL COMMENT '描述',`realname` VARCHAR(20) DEFAULT NULL COMMENT '角色显示名', PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色表';CREATE TABLE `role_user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`uid` BIGINT(20) DEFAULT NULL COMMENT '用户 id',
`rid` BIGINT(20) DEFAULT NULL COMMENT '角色 id',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色用户映射表';
mapper
@Repository
public interface UserMapper extends BaseMapper {
@Select("SELECT NAME FROM role WHERE id IN (SELECT rid FROM
role_user WHERE uid=(SELECT id FROM USER WHERE NAME=#{principal}))")
List getUserRoleInfoMapper(@Param("principal") String
principal);
}
service
@Override
public List getUserRoleInfo(String principal) {
return userMapper.getUserRoleInfoMapper(principal);
}
MyRealm 方法改造
@Override
protected AuthorizationInfo
doGetAuthorizationInfo(PrincipalCollection principalCollection) {System.out.println("进入自定义授权方法");
//获取当前用户身份信息
String principal =
principalCollection.getPrimaryPrincipal().toString();
//调用接口方法获取用户的角色信息
List roles = userService.getUserRoleInfo(principal);
System.out.println("当前用户角色信息:"+roles);
//创建对象,存储当前登录的用户的权限和角色
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//存储角色
}
info.addRoles(roles);
//返回
return info;
}
添加数据库表
CREATE TABLE `permissions` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`name` VARCHAR(30) DEFAULT NULL COMMENT '权限名',
`info` VARCHAR(30) DEFAULT NULL COMMENT '权限信息',
`desc` VARCHAR(50) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='权限表';CREATE TABLE `role_ps` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号',
`rid` BIGINT(20) DEFAULT NULL COMMENT '角色 id',
`pid` BIGINT(20) DEFAULT NULL COMMENT '权限 id',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色权限映射表';
mapper
@Select({
""
})
List getUserPermissionInfoMapper(@Param("roles")List
roles);
service
@Override
public List getUserPermissionInfo(List roles) {
return userMapper.getUserPermissionInfoMapper(roles);
}
MyRealm
//自定义授权方法:获取当前登录用户权限信息,返回给 Shiro 用来进行授权对比
@Override
protected AuthorizationInfo
doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("进入自定义授权方法");
//获取当前用户身份信息
String principal =
principalCollection.getPrimaryPrincipal().toString();//调用接口方法获取用户的角色信息
List roles = userService.getUserRoleInfo(principal);
System.out.println("当前用户角色信息:"+roles);
//调用接口方法获取用户角色的权限信息
List permissions =
userService.getUserPermissionInfo(roles);
System.out.println("当前用户权限信息:"+permissions);
//创建对象,存储当前登录的用户的权限和角色
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//存储角色
info.addRoles(roles);
//存储权限信息
info.addStringPermissions(permissions);
//返回
return info;
}
controller
//登录认证验证权限
@RequiresPermissions("user:delete")
@GetMapping("userPermissions")
@ResponseBody
public String userLoginPermissions() {
System.out.println("登录认证验证权限");
return "验证权限成功";
}
main.html
Shiro 登录认证后主页面
登录用户为:
登出
测试授权-角色验证
测试授权-权限验证
添加依赖
com.github.theborakompanioni
thymeleaf-extras-shiro
2.0.0
配置类
用于解析 thymeleaf 中的 shiro:相关属性
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
Thymeleaf 中常用的 shiro:属性
guest 标签
用户没有身份验证时显示相应信息,即游客访问信息。
user 标签
用户已经身份验证/记住我登录后显示相应的信息。
authenticated 标签
用户已经身份验证通过,即 Subject.login 登录成功,不是记住我登录的。
notAuthenticated 标签
用户已经身份验证通过,即没有调用 Subject.login 进行登录,包括记住我自动登录的
也属于未进行身份验证。
principal 标签
相当于((User)Subject.getPrincipals()).getUsername()。
lacksPermission 标签
如果当前 Subject 没有权限将显示 body 体内容。
hasRole 标签
如果当前 Subject 有角色将显示 body 体内容。
hasAnyRoles 标签
如果当前 Subject 有任意一个角色(或的关系)将显示 body 体内容。
lacksRole 标签
如果当前 Subject 没有角色将显示 body 体内容。
hasPermission 标签
如果当前 Subject 有权限将显示 body 体内容