一、权限管理
1.1 什么是权限管理
- 基本上只要涉及到用户参数的系统都要进行权限管理,使用权限管理实现了对用户访问系统的控制,不同的用户访问不同的资源。按照安全规则或者安全策略控制用户访问资源,而且只能访问被授权的资源
- 权限管理包括认证和授权两部分,当用户访问资源时先对其进行身份的认证,认证通过后即可访问已经授权的资源。
1.2 身份认证
- 用来判断一个用户是否合法的处理过程。用过用户输入的用户名或者口令来和系统中存储进行比较,从而认证用户的身份是否正确
对于身份认证,也就是之前做的的登录
1.3 授权
- 用来控制认证过后的用户可以访问哪些资源。用户身份认证后需要给该用户分配可访问的资源,如果没有某个资源的权限,那么将无法访问
二、Shiro架构
2.1 Shiro的理解
是一个功能强大且易实现的Java安全框架,使用Shiro可以执行认证、授权、加密和会话管理。使用Shiro中提供的API可以快速轻松的保护任何程序
Shiro不依赖于WEB,即使是一个测试程序也能够使用Shiro中的功能
2.2 Shiro的体系
官方图示:
原理:
Subject
- 表示主体,外部应用和Subject进行交互。Subject中记录了当前操作用户,这个用户可以是一个发送请求的用户,也可以是一个运行的程序
- Subject在Shiro是一个接口,定义了很多认证授权的相关方法,外部程序通过Subject进行认证授权,Subject又是通过SecurityManager安全管理器就行认证管理
SecurityManager
- 表示安全管理器,是Shiro中的核心,用来协调其托管的组件,以保证它们能够顺利协同工作。
- 可以进行会话管理等
Authenticator
- 表示身份认证器,负责执行和响应用户的身份认证尝试的组件。当用户进行登录操作时,此组件进行处理
Authorizer
- 表示授权器,负责控制用户在系统中可以访问哪些资源。在访问资源时都需要该组件进行判断当前用户是否拥有这个资源的权限
Realm
- 表示领域,充当Shiro与应用程序的安全数据之间的桥梁或者连接器,和DataSource数据源差不多,当需要和安全相关的数据(如用户账号)进行实际交互从而执行认证和授权时,Shiro会从应用配置的一个或者多个Realm中查询其中的内容
- SecurityManager进行安全认证时需要通过Realm获取到用户权限数据
- Realm不只是从数据库取数据,还有认证和授权的相关逻辑代码
SessionManager
- 表示会话管理,知道如果创建和管理用户生命周期,以便为所欲环境中的用户提供强大的会话体验
- 不依赖WEB容器,所以Shiro可以使用在非WEB应用中也可以将分布式应用的会话集中在一点管理,次特征可以使它实现单点登录
SessionDao
- 表示会话Dao,是对session会话操作的一套接口
CacheManager
- 表示缓存管理器,将用户权限存储到缓存中,从而提高性能
Cryptography
- 表示密码管理,Shiro中提供了一套加密/解密的组件,方便开发
三、Shiro中的认证
3.1 认证中的关键对象
- Subject:主体
访问系统的每一个用户或者应用程序,经过认证的都成为主体
- Principal:身份信息
是主体(Subject)进行身份证认证的表示,表示必须具有唯一性。比如用户名/手机号/邮箱,一个主体(Subject)中可以有多个身份信息,但必须有一个主身份
- Credential:凭证信息
3.2 认证流程
图示:
文字:
- 收集使用者的Principal和Credential
- 提交进行身份验证
- 如果验证成功就允许访问,否则重试身份证验证或者阻止访问
3.3 环境搭建
- 引入Shiro依赖
1. <dependency> 2. <groupId>org.apache.shiro</groupId> 3. <artifactId>shiro-core</artifactId> 4. <version>1.9.1</version> 5. </dependency>
- 在resources下创建.ini的配置文件,来临时模拟数据库存储用户的身份信息和凭证信息
1. [users] 2. admin=1234 3. tom=222 4. jack=456
- 编写认证代码
1. public class ShiroAuthentication { 2. public static void main(String[] args) { 3. // 1.创建SecurityManager安全管理器的实现类 4. DefaultSecurityManager securityManager = new DefaultSecurityManager(); 5. 6. // 2.将Realm中的数据设置到安全管理器中 7. securityManager.setRealm(new IniRealm("classpath:shiro.ini")); // Realm去读取ini中的主体与凭证信息 8. 9. // 3.将安全管理器设置到全局安全工具类中 10. SecurityUtils.setSecurityManager(securityManager); 11. 12. // 4.获取主体 13. Subject subject = SecurityUtils.getSubject(); 14. 15. // 5.认证 16. if (!subject.isAuthenticated()) { // 是否已经认证 17. // 如果没有认证过,那么证明该用户第一次登录.收集使用者的身份信息和凭证信息 18. UsernamePasswordToken token = new UsernamePasswordToken("admin","1234"); // 模拟前台输入 19. // 提交身份信息和凭证信息 20. subject.login(token); 21. } 22. } 23. }
3.4 处理结果
如果提交成功,执行后续的逻辑代码;提交过程中出现错误,那么Shiro将以抛异常的形式声明错误
异常列表:
- UnknowAccountException --> 未知账号异常【用户名错误】
- IncorrectCredentialsException --> 凭证信息异常【密码错误】
- LockedAccountException --> 锁定账号异常
- ExcessiveAttemptsException --> 过度尝试异常
- AuthenticationException --> 身份认证异常
修改以上程序:
1. try { 2. // 开始认证 3. subject.login(token); 4. }catch (UnknownAccountException e){ 5. System.out.println("账号错误"); 6. }catch (IncorrectCredentialsException e){ 7. System.out.println("密码错误"); 8. }
3.5 获取主体身份信息与注销
此操作必须保证认证通过
- 获取主体身份信息
1. if (subject.isAuthenticated()){ // 认证通过 2. Object principal = subject.getPrincipal(); // 6. 获取身份信息 3. System.out.println(principal); // admin 4. }
- 注销
subject.logout();
3.6 底层实现
身份信息校验
- 在SimpleAccountRealm类中的doGetAuthenticationInfo方法判断身份信息是否一致
- 如果用户名错误,那么返回的info==null,系统抛出UnknowAccountException异常
密码校验
- 在AuthenticatingRealm类中的assertCredentialsMatch方法进行密码(凭证信息)的校验,Shiro中凭证信息默认的校验规则是equals
- 如果密码错误,抛出IncorrectCredentialsException异常
3.7 自定义Realm
以后校验用户名肯定不能使用Shiro中定义的,需要连接数据库,通过用户名查询。所以Shiro中也可以让我们自定义Realm
Realm继承图
全部
主要部分
认证方法和授权方法都在AuthorizingRealm定义为抽象方法,等待子类继承并重写这两个方法。
SimpleAccountRealm继承了AuthorizingRealm,所以这个类里面有认证和授权功能,其两个功能对应的方法为:
- doGetAuthenticationInfo:认证
- doGetAuthorizationInfo:授权
以后自定义的Realm只需要继承AuthorizingRealm,然后重写这两个方法
1. /** 2. * 自定义Realm,继承AuthorizingRealm类 3. */ 4. public class LoginRealm extends AuthorizingRealm { 5. /* 6. 授权 7. */ 8. @Override 9. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 10. return null; 11. } 12. 13. /* 14. 认证 15. */ 16. @Override 17. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 18. return null; 19. } 20. }
在doGetAuthenticationInfo方法中获取用户的身份信息,然后校验是否和数据库中的一致
1. @Override 2. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 3. // getPrincipal()获取身份信息 4. String username = (String) token.getPrincipal(); 5. if ("admin".equals(username)){ 6. // 正确 返回AuthenticationInfo的实现类 7. // 参数:1.当前用户的身份信息 2.验证主体的凭证信息[如果和前台传入的不一致,抛出IncorrectCredentialsException异常] 3.当前Realm 8. return new SimpleAuthenticationInfo(username,"1234",super.getName()); 9. } 10. // 不正确返回一个null, info == null 抛出UnknownAccountException异常 11. return null; 12. }
3.8 加密
测试程序:
1. public class MD5Test { 2. public static void main(String[] args) { 3. // 加密 4. Md5Hash md5Hash = new Md5Hash("1234"); 5. 6. // 加密+salt(盐) 7. Md5Hash md5Hash1 = new Md5Hash("1234", "f5gy"); 8. 9. // 加密+salt(盐)+散列次数 10. Md5Hash md5Hash2 = new Md5Hash("1234","f5gy",1024); 11. } 12. }
整合认证:
- 修改认证方法
1. @Override 2. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 3. // getPrincipal()获取身份信息 4. String username = (String) token.getPrincipal(); 5. if ("admin".equals(username)){ 6. // 正确 返回AuthenticationInfo的实现类 7. /* 8. 参数: 9. 1.当前用户的身份信息 10. 2.验证主体的凭证信息[如果和前台传入的不一致,抛出IncorrectCredentialsException异常] 11. 3.盐 12. 4.当前Realm 13. */ 14. String password = "75b323294effa42ed07f895f37f9a192"; 15. String salt = "f5gy"; 16. return new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes(salt),super.getName()); 17. } 18. // 不正确返回一个null, info == null 抛出UnknownAccountException异常 19. return null; 20. }
- 修改密码比较器
Shiro默认实用的是simpleCredentialsMatcher中的doCredentialsMatcher方法,这个方法使用的是equals的方式进行比较密码。
CredentialsMatcher继承图:
使用HashedCredentialsMatcher这个类
1. LoginRealm realm = new LoginRealm(); 2. HashedCredentialsMatcher hash = new HashedCredentialsMatcher(); 3. // 设置算法 4. hash.setHashAlgorithmName("MD5"); 5. // 设置散列次数 6. hash.setHashIterations(1024); 7. // 设置到Realm中 8. realm.setCredentialsMatcher(hash);
四、Shiro中的授权
4.1 授权中的关键对象
- Who
表示主体,主题需要系统中的资源
- What
表示资源,这个资源可以是一个按钮、菜单等。资源又分为资源实例和资源类型
- How
表示权限/许可,控制主体对资源的访问
4.2 授权方式
- 基于角色的访问控制(Role-Based Access Control):以角色为中心进行权限控制
- 基于资源的访问控制(Resource-Based Access Control):以资源为中心进行权限控制
4.3 权限字符串
权限字符串的规则:资源标识符:操作,意思是对哪个资源进行哪些操作。":"是分割符,权限字符串可以使用"*"来表示通配符
4.4 校验角色
- 在doGetAuthorizationInfo方法中设置当前主体的角色
1. @Override 2. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 3. // 获取主题中的身份信息[用户名] 4. String principal = (String) principals.getPrimaryPrincipal(); 5. // 返回AuthorizationInfo的实现类 6. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 7. // 给当前主体添加角色 8. info.addRole("admin"); 9. info.addRole("user"); 10. return info; 11. }
- 模拟前台测试
1. if (subject.isAuthenticated()){ 2. // 校验单个角色 3. System.out.println(subject.hasRole("admin")); // 是否有admin角色 4. // 校验多个角色 5. System.out.println(subject.hasAllRoles(Arrays.asList("admin", "user"))); // 是否同时有admin user角色 6. // 校验 多次 角色 7. boolean[] booleans = subject.hasRoles(Arrays.asList("admin", "user", "super")); 8. for (boolean b : booleans) { 9. System.out.println(b); 10. } 11. }
4.5 校验权限字符串
- 在doGetAuthorizationInfo方法中设置当前主体的权限字符串
1. @Override 2. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 3. // 获取主题中的身份信息[用户名] 4. String principal = (String) principals.getPrimaryPrincipal(); 5. // 返回AuthorizationInfo的实现类 6. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 7. // 给当前主体添加权限字符串 8. info.addStringPermission("user:update"); 9. info.addStringPermission("product:select"); 10. return info; 11. }
- 模拟前台测试
五、整合SpringBoot
5.1 整合思路
5.2 环境搭建
- 导入springboot和shiro整合的依赖包
1. <dependency> 2. <groupId>org.apache.shiro</groupId> 3. <artifactId>shiro-spring-boot-starter</artifactId> 4. <version>1.4.0</version> 5. </dependency>
- 创建一个类,继承AuthorizingRealm类,重写授权和认证方法
1. public class MyRealm extends AuthorizingRealm { 2. @Override 3. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 4. return null; 5. } 6. 7. @Override 8. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 9. return null; 10. } 11. }
- 编写shiro和springboot整合的配置
1. @Configuration 2. public class ShiroConfig { 3. @Bean 4. public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ 5. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 6. // 将SecurityManager设置到Filter中 7. shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); 8. return shiroFilterFactoryBean; 9. } 10. @Bean 11. public DefaultWebSecurityManager defaultWebSecurityManager(Realm realm){ 12. DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); 13. // 给安全管理器设置Realm 14. defaultWebSecurityManager.setRealm(realm); 15. return defaultWebSecurityManager; 16. } 17. @Bean 18. public Realm Realm(){ 19. LoginRealm loginRealm = new LoginRealm(); 20. return loginRealm; 21. } 22. }
- 在ShiroFilter过滤器中配置需要拦截的资源URL
1. @Bean 2. public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ 3. ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 4. // 将SecurityManager设置到Filter中 5. shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); 6. 7. //设置受限资源 8. Map<String,String> map = new HashMap<>(); 9. /** 10. * authc:该路径资源需要认证和授权 11. */ 12. map.put("/**","authc"); // "/**"代表所有的资源路径都拦截 13. shiroFilterFactoryBean.setFilterChainDefinitionMap(map); 14. return shiroFilterFactoryBean; 15. }
- 访问资源跳转到login.jsp,修改默认跳转路径
1. shiroFilterFactoryBean.setLoginUrl("/doLogin"); 2. 3. @Controller 4. public class IndexController { 5. 6. @GetMapping("/doLogin") 7. public String doLogin(){ 8. return "login"; 9. } 10. }
- 拦截后访问/doLogin路径
5.3 ShiroFilter过滤列表
Shiro中提供了多个默认的过滤器,用这些过滤器来控制指定URL路径下的资源
六、连接数据库完成认证
6.1 注册
- 表设计
- 注册页面
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="UTF-8"> 5. <title>Title</title> 6. </head> 7. <body> 8. <h1>注册页面</h1> 9. <hr> 10. <form action="/user/register" method="post"> 11. 用户名 : <input type="text" name="username"><br> 12. 密码 : <input type="password" name="password"> <br> 13. <input type="submit" value="注册"> 14. </form> 15. </body> 16. </html>
- 实体类POJO
1. @Data 2. public class User { 3. private Long id; 4. private String username; 5. private String password; 6. private String salt; 7. }
- 编写Controller,调用service处理业务逻辑
1. @Controller 2. @RequestMapping("/user") 3. public class UserController { 4. 5. @Autowired 6. private UserService userService; 7. 8. @GetMapping("/doRegister") 9. public String doRegister(){ 10. return "register"; 11. } 12. 13. @PostMapping("/register") 14. public String register(User user){ 15. int count = userService.register(user); 16. if (count > 0) 17. return "redirect:/doLogin"; 18. else 19. return "redirect:/doRegister"; 20. } 21. }
- 在ShiroFilter放过这些URL路径
1. map.put("/user/register","anon"); 2. map.put("/user/doRegister","anon");
- service中完成加密和散列盐,调用Mapper完成注册
1. @Service 2. public class UserServiceImpl implements UserService { 3. 4. @Autowired 5. private UserMapper userMapper; 6. 7. @Override 8. public int register(User user) { 9. String salt = "ga*n"; 10. Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024); 11. user.setPassword(md5Hash.toHex()); 12. user.setSalt(salt); 13. return userMapper.insert(user); 14. } 15. }
- Mapper接口和SQL语句
1. public interface UserMapper { 2. 3. int insert(User user); 4. }
1. <mapper namespace="com.jiuxiao.mapper.UserMapper"> 2. 3. <insert id="insert"> 4. insert into t_user(id,username,password,salt) values(null,#{username},#{password},#{salt}) 5. </insert> 6. </mapper>
- 启动类上添加@MapperScan注解,扫描Mapper包
1. @SpringBootApplication 2. @MapperScan("com.jiuxiao.mapper") 3. public class ShiroApp { 4. public static void main(String[] args) { 5. SpringApplication.run(ShiroApp.class,args); 6. } 7. }
- 注册页面输入用户信息后,完成注册
6.2 认证
- 在Shiro配置中修改密码比较器
1. @Bean 2. public Realm realm(){ 3. LoginRealm loginRealm = new LoginRealm(); 4. HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); 5. matcher.setHashAlgorithmName("MD5"); 6. matcher.setHashIterations(1024); 7. loginRealm.setCredentialsMatcher(matcher); 8. return loginRealm; 9. }
- 在自定义的Realm的doGetAuthenticationInfo方法中编写认证逻辑
1. @Override 2. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { 3. 4. @Autowired 5. private UserService userService; 6. 7. // 获取主体中的身份信息 8. String principal = (String) authenticationToken.getPrincipal(); 9. // 调用service查询数据库 10. User user = userService.selectByUsername(principal); 11. if (!ObjectUtils.isEmpty(user)){ 12. // 如果可以查询到,校验密码 13. return new SimpleAuthenticationInfo(user.getUsername(),user.getPassword(), ByteSource.Util.bytes(user.getSalt()),super.getName()); 14. } 15. // 查询不到直接返回null 16. return null; 17. }
- service调用Mapper
1. @Override 2. public User selectByUsername(String principal) { 3. return userMapper.selectByUsername(principal); 4. }
- Mapper接口与SQL语句
User selectByUsername(String principal);
1. <select id="selectByUsername" resultType="com.jiuxiao.pojo.User"> 2. select * from t_user where username = #{username} 3. </select>
- 登录页面代码
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="UTF-8"> 5. <title>$Title$</title> 6. </head> 7. <body> 8. <h1>登录页面</h1> 9. <hr> 10. <form action="/user/login" method="post"> 11. 用户名: <input type="text" name="username"> <br> 12. 密码: <input type="password" name="password"> <br> 13. <input type="submit" value="登录"> 14. </form> 15. </body> 16. </html>
- 在Controller中编写对应URL,封装token,并处理异常结果
1. @Controller 2. @RequestMapping("/user") 3. public class UserController { 4. 5. @PostMapping("/login") 6. public String login(String username,String password){ 7. Subject subject = SecurityUtils.getSubject(); 8. UsernamePasswordToken token = new UsernamePasswordToken(username, password); 9. try { 10. subject.login(token); 11. return "index"; // 登陆成功跳转到首页 12. } catch (UnknownAccountException e) { 13. e.printStackTrace(); 14. System.out.println("用户名错误"); 15. }catch (IncorrectCredentialsException e){ 16. e.printStackTrace(); 17. System.out.println("密码错误"); 18. } 19. return "login"; // 登录失败跳转到登录页面 20. } 21. }
- 首页代码
1. <!DOCTYPE html> 2. <html lang="en"> 3. <head> 4. <meta charset="UTF-8"> 5. <title>$Title$</title> 6. </head> 7. <body> 8. <h1>首页</h1> 9. <hr> 10. <ul> 11. <li><a href="">用户管理</a></li> 12. <li><a href="">商品管理</a></li> 13. <li><a href="">菜单管理</a></li> 14. <li><a href="">物流管理</a></li> 15. </ul> 16. </body> 17. </html>
- 在ShiroFilter过滤器中放过登录URL
map.put("/user/login","anon");
- 页面登录成功后,进入到index.html
6.3 注销
① 配置方式
- 在ShiroFilter过滤器中添加注销的URL
map.put("/user/logout","logout");
- 在页面直接输入这个URL即可注销
② 代码方式
调用subject的logout方法完成注销,页面访问该Controller的URL
七、授权的基本使用
7.1 校验角色
在自定义的Realm的doGetAuthorizationInfo方法中给当前主体赋予角色
1. @Override 2. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 3. // 获取主体中的身份信息 4. String principal = (String) principalCollection.getPrimaryPrincipal(); 5. if ("jiuxiao".equals(principal)){ // 给jiuxiao用户赋予admin和user角色 6. SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); 7. // 增加admin和user角色 8. simpleAuthorizationInfo.addRole("admin"); 9. simpleAuthorizationInfo.addRole("user"); 10. return simpleAuthorizationInfo; 11. } 12. return null; 13. }
① 编码方式
单个角色:使用subject中的hasRole方法
1. @Controller 2. @RequestMapping("/user") 3. public class UserController { 4. 5. @GetMapping("/save") 6. @ResponseBody 7. public String save(){ 8. Subject subject = SecurityUtils.getSubject(); 9. if (!subject.hasRole("admin")) { // 校验当前角色是否有admin这个角色 10. return "权限不足"; 11. }else{ 12. return "访问成功"; 13. } 14. } 15. }
多个角色:使用subject中的hasAllRoles方法【这些角色都有才能访问】
1. @Controller 2. @RequestMapping("/user") 3. public class UserController { 4. 5. @GetMapping("/save") 6. @ResponseBody 7. public String save(){ 8. List<String> roles = Arrays.asList("admin", "user"); 9. if (SecurityUtils.getSubject().hasAllRoles(roles)) { 10. return "访问成功"; 11. }else{ 12. return "权限不足"; 13. } 14. } 15. }
② 注解方式
单个角色:直接在注解参数中写入对应的角色即可
1. @Controller 2. @RequestMapping("/user") 3. public class UserController { 4. 5. @GetMapping("/save") 6. @ResponseBody 7. @RequiresRoles("admin") 8. public String save(){ 9. return "访问成功"; 10. } 11. }
多个角色:在注解中以数组的形式写入多个角色【这些角色都有才能访问】
1. @GetMapping("/save") 2. @ResponseBody 3. @RequiresRoles(value = {"admin","user"}) 4. public String save(){ 5. return "访问成功"; 6. }
7.2 校验权限字符串
在自定义的Realm的doGetAuthorizationInfo方法中给当前主体赋予权限字符串
1. @Override 2. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 3. String principal = (String) principalCollection.getPrimaryPrincipal(); 4. System.out.println("执行授权:"+principal); 5. if ("jiuxiao".equals(principal)){ 6. SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); 7. // 添加权限字符串 8. simpleAuthorizationInfo.addStringPermission("user:update"); 9. simpleAuthorizationInfo.addStringPermission("product:create"); 10. return simpleAuthorizationInfo; 11. } 12. return null; 13. }
① 编码方式
调用subject中的isPermittedAll方法,参数为可变长参数(可以传一个或者多个)【如果是多个,那么这个主体需要拥有参数里面所有的权限字符串才能访问】
1. @Controller 2. @RequestMapping("/user") 3. public class UserController { 4. 5. @GetMapping("/save") 6. @ResponseBody 7. public String save(){ 8. Subject subject = SecurityUtils.getSubject(); 9. // if (subject.isPermittedAll("user:update")){ // 判断当前主体使用拥有对user资源的001实例的更新操作 10. if (subject.isPermittedAll("user:update","product:update")){ 11. return "访问成功"; 12. }else{ 13. return "权限不足"; 14. } 15. } 16. }
② 注解方式
单个权限字符串:直接在注解参数中写入需要校验的权限字符串即可
1. @GetMapping("/save") 2. @ResponseBody 3. @RequiresPermissions("user:update") 4. public String save(){ 5. return "访问成功"; 6. }
多个权限字符串:在注解中以数组的形式写入多个权限字符串【当前主体主要拥有这些权限字符串才能访问】
1. @GetMapping("/save") 2. @ResponseBody 3. @RequiresPermissions(value = {"user:update","product:create"}) 4. public String save(){ 5. return "访问成功"; 6. }
③ 配置方式
在ShiroFilter过滤器使用perms进行权限的校验
map.put("/user/save","perms[user:update,product:delete]"); // 数组中添加权限字符串
如果权限不足,页面抛出401错误
在ShiroFilter中定义权限不足后跳转的URL
注意:定义权限不足跳转URL的方式只限制配置方式,别的方式都不能使用,权限不足时会抛出AuthorizationException异常
八、连接数据库完成授权
8.1 表设计:
1. SET NAMES utf8mb4; 2. SET FOREIGN_KEY_CHECKS = 0; 3. -- ---------------------------- 4. -- Table structure for role_perms 5. -- ---------------------------- 6. DROP TABLE IF EXISTS `role_perms`; 7. CREATE TABLE `role_perms` ( 8. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 9. `roleid` int(11) NULL DEFAULT NULL COMMENT '角色id', 10. `permid` int(11) NULL DEFAULT NULL COMMENT '权限id', 11. PRIMARY KEY (`id`) USING BTREE 12. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic; 13. 14. -- ---------------------------- 15. -- Table structure for role_user 16. -- ---------------------------- 17. DROP TABLE IF EXISTS `role_user`; 18. CREATE TABLE `role_user` ( 19. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 20. `roleid` int(11) NULL DEFAULT NULL COMMENT '角色id', 21. `userid` int(11) NULL DEFAULT NULL COMMENT '用户id', 22. PRIMARY KEY (`id`) USING BTREE 23. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic; 24. 25. -- ---------------------------- 26. -- Table structure for t_perms 27. -- ---------------------------- 28. DROP TABLE IF EXISTS `t_perms`; 29. CREATE TABLE `t_perms` ( 30. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 31. `perm` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '权限字符串', 32. `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '资源URL', 33. PRIMARY KEY (`id`) USING BTREE 34. ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic; 35. 36. -- ---------------------------- 37. -- Table structure for t_role 38. -- ---------------------------- 39. DROP TABLE IF EXISTS `t_role`; 40. CREATE TABLE `t_role` ( 41. `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 42. `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '角色', 43. PRIMARY KEY (`id`) USING BTREE 44. ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic; 45. 46. -- ---------------------------- 47. -- Table structure for t_user 48. -- ---------------------------- 49. DROP TABLE IF EXISTS `t_user`; 50. CREATE TABLE `t_user` ( 51. `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', 52. `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '用户名(身份信息)', 53. `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '密码(凭证信息)', 54. `salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_croatian_ci NULL DEFAULT NULL COMMENT '盐', 55. PRIMARY KEY (`id`) USING BTREE 56. ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_croatian_ci ROW_FORMAT = Dynamic; 57. 58. SET FOREIGN_KEY_CHECKS = 1;
8.2 POJO
User
1. @Data 2. public class User { 3. private Long id; 4. private String username; 5. private String password; 6. private String salt; 7. private List<Role> roles; 8. }
Role
1. @Data 2. public class Role { 3. private Integer id; 4. private String name; 5. private List<Perms> perms; 6. }
Perms
1. @Data 2. public class Perms { 3. private Integer id; 4. private String perm; 5. private String url; 6. }
8.3 授权角色
- Mapper接口与SQL语句
List<Role> selectRoleNameByUserId(String username);
1. <select id="selectRoleNameByUserId" resultType="com.jiuxiao.pojo.Role"> 2. select r.id,r.name 3. from t_user u 4. left join role_user ru on ru.userid = u.id 5. left join t_role r on r.id = ru.roleid 6. where u.username = #{username} 7. </select>
- Service
1. @Override 2. public List<Role> getRoleNameByUsername(String username) { 3. return userMapper.selectRoleNameByUsername(username); 4. }
- 自定义Realm中进行授权
1. @Override 2. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 3. String principal = (String) principalCollection.getPrimaryPrincipal(); 4. List<Role> roles = userService.getRoleNameByUsername(principal); 5. if(!CollectionUtils.isEmpty(roles)){ 6. SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); 7. roles.forEach(role-> simpleAuthorizationInfo.addRole(role.getName())); 8. return simpleAuthorizationInfo; 9. } 10. return null; 11. }
8.4 授权字符串
- Mapper接口与SQL语句
List<String> selectPermByRoleId(Integer id);
1. <select id="selectPermByRoleId" resultType="string"> 2. select p.perm 3. from t_role r 4. left join role_perms rp on r.id = rp.roleid 5. left join t_perms p on rp.permid = p.id 6. where r.id = #{id} 7. </select>
- Service
1. @Override 2. public List<String> getPermByRoleId(Integer id) { 3. return userMapper.selectPermByRoleId(id); 4. }
- 自定义Realm进行授权
1. @Override 2. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 3. String principal = (String) principalCollection.getPrimaryPrincipal(); 4. List<Role> roles = userService.getRoleNameByUsername(principal); 5. if(!CollectionUtils.isEmpty(roles)){ 6. SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); 7. roles.forEach(role-> simpleAuthorizationInfo.addRole(role.getName())); 8. roles.forEach(role->{ 9. List<String> perms = userService.getPermByRoleId(role.getId()); 10. simpleAuthorizationInfo.addStringPermissions(perms); 11. }); 12. return simpleAuthorizationInfo; 13. } 14. return null; 15. }
九、Shiro与thymeleaf整合
9.1 导入依赖
1. <dependency> 2. <groupId>com.github.theborakompanioni</groupId> 3. <artifactId>thymeleaf-extras-shiro</artifactId> 4. <version>2.0.0</version> 5. </dependency>
9.2 配置方言
1. @Bean 2. public ShiroDialect shiroDialect(){ 3. return new ShiroDialect(); 4. }
9.3 引入工作空间
<html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
9.4 常用标签使用
1. <!-- 验证当前用户是否为"访客",即未认证的用户 --> 2. <p shiro:guest="">未认证</p> 3. 4. <!-- 认证通过或者已经"记住我"的用户 --> 5. <p shiro:user="">hello</p> 6. 7. <!-- 认证通过的用户 --> 8. <p shiro:authenticated="">hello</p> 9. 10. <!-- 输出当前用户信息,通常为账号登录信息 --> 11. <p shiro:principal></p> 12. 13. <!-- 判断当前用户是否拥有该角色 --> 14. <p shiro:hasRole="admin">拥有该角色</p> 15. 16. <!-- 当前用户没有该角色认证通过 --> 17. <p shiro:lacksRole="user">没有改角色</p> 18. 19. <!-- 判断当前用户是否拥有以下所有角色 --> 20. <p shiro:hasAllRoles="admin,user"></p> 21. 22. <!-- 判断当前用户是否拥有以下任意一个角色 --> 23. <p shiro:hasAnyRoles="admin,user"></p> 24. 25. <!-- 判断当前用户是否拥有以下权限字符串 --> 26. <p shiro:hasPermission="user:add"></p> 27. 28. <!-- 当前用户没有该权限字符串认证通过 --> 29. <p shiro:lacksPermission="user:add"></p>