5、身份授权
【1】基本流程
1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager。
2、SecurityManager接着会委托给内部组件Authorizer;
3、Authorizer再将其请求委托给我们的Realm去做;Realm才是真正干活的;
4、Realm将用户请求的参数封装成权限对象。再从我们重写的doGetAuthorizationInfo方法中获取从数据库中查询到的权限集合。
5、Realm将用户传入的权限对象,与从数据库中查出来的权限对象,进行一一对比。如果用户传入的权限对象在从数据库中查出来的权限对象中,则返回true,否则返回false。
进行授权操作的前提:用户必须通过认证。
在真实的项目中,角色与权限都存放在数据库中。为了快速上手,我们先创建一个自定义DefinitionRealm,模拟它已经登录成功。直接返回一个登录验证凭证,告诉Shiro框架,我们从数据库中查询出来的密码是也是就是你输入的密码。所以,不管用户输入什么,本次登录验证都是通过的。
1. /** 2. * @Description 认证接口 3. * @param token 传递登录token 4. * @return 5. */ 6. @Override 7. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 8. //从AuthenticationToken中获得登录名称 9. String loginName = (String) token.getPrincipal(); 10. SecurityService securityService = new SecurityServiceImpl(); 11. Map<String, String> map = securityService.findPasswordByLoginName(loginName); 12. if (map.isEmpty()){ 13. throw new UnknownAccountException("账户不存在"); 14. } 15. String salt = map.get("salt"); 16. String password = map.get("password"); 17. //传递账号和密码:参数1:用户认证凭证信息,参数2:明文密码,参数三:字节salt,参数4:当前DefinitionRealm名称 18. return new SimpleAuthenticationInfo(loginName,password, ByteSource.Util.bytes(salt),getName()); 19. }
好了,接下来,我们要重写我们本小节的核心方法了。在DefinitionRealm中找到下列方法:
1. @Override 2. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 3. return null; 4. }
此方法的传入的参数PrincipalCollection principals,是一个包装对象,它表示"用户认证凭证信息"。包装的是谁呢?没错,就是认证doGetAuthenticationInfo()方法的返回值的第一个参数loginName。你可以通过这个包装对象的getPrimaryPrincipal()方法拿到此值,然后再从数据库中拿到对应的角色和资源,构建SimpleAuthorizationInfo。
1. /** 2. * @Description 授权方法 3. */ 4. @Override 5. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 6. //拿到用户认证凭证信息 7. String loginName = (String) principals.getPrimaryPrincipal(); 8. //从数据库中查询对应的角色和资源 9. SecurityService securityService = new SecurityServiceImpl(); 10. List<String> roles = securityService.findRoleByloginName(loginName); 11. List<String> permissions = securityService.findPermissionByloginName(loginName); 12. //构建资源校验 13. SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 14. authorizationInfo.addRoles(roles); 15. authorizationInfo.addStringPermissions(permissions); 16. return authorizationInfo; 17. }
【2】案例演示
【2.1】需求
1. 1、实现doGetAuthorizationInfo方法实现鉴权 2. 2、使用subject类实现权限的校验
【2.2】实现
【2.2.1】创建项目
拷贝shiro-day01-05-ciphertext-realm新建shiro-day01-06-authentication-realm
【2.2.2】编写SecurityService
在SecurityService中添加
1. /** 2. * @Description 查找角色按用户登录名 3. * @param loginName 登录名称 4. * @return 5. */ 6. List<String> findRoleByloginName(String loginName); 7. 8. /** 9. * @Description 查找资源按用户登录名 10. * @param loginName 登录名称 11. * @return 12. */ 13. List<String> findPermissionByloginName(String loginName);
SecurityServiceImpl添加实现
1. @Override 2. public List<String> findRoleByloginName(String loginName) { 3. List<String> list = new ArrayList<>(); 4. list.add("admin"); 5. list.add("dev"); 6. return list; 7. } 8. 9. @Override 10. public List<String> findPermissionByloginName(String loginName) { 11. List<String> list = new ArrayList<>(); 12. list.add("order:add"); 13. list.add("order:list"); 14. list.add("order:del"); 15. return list; 16. }
【2.2.3】编写DefinitionRealm
在DefinitionRealm中修改doGetAuthorizationInfo方法如下
1. /** 2. * @Description 授权方法 3. */ 4. @Override 5. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 6. //拿到用户认证凭证信息 7. String loginName = (String) principals.getPrimaryPrincipal(); 8. //从数据库中查询对应的角色和资源 9. SecurityService securityService = new SecurityServiceImpl(); 10. List<String> roles = securityService.findRoleByloginName(loginName); 11. List<String> permissions = securityService.findPermissionByloginName(loginName); 12. //构建资源校验 13. SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 14. authorizationInfo.addRoles(roles); 15. authorizationInfo.addStringPermissions(permissions); 16. return authorizationInfo; 17. }
【2.2.4】编写HelloShiro
1. package com.itheima.shiro; 2. 3. import org.apache.shiro.SecurityUtils; 4. import org.apache.shiro.authc.UsernamePasswordToken; 5. import org.apache.shiro.config.IniSecurityManagerFactory; 6. import org.apache.shiro.mgt.SecurityManager; 7. import org.apache.shiro.subject.Subject; 8. import org.apache.shiro.util.Factory; 9. import org.junit.Assert; 10. import org.junit.Test; 11. 12. /** 13. * @Description:shiro的第一个例子 14. */ 15. public class HelloShiro { 16. 17. 18. @Test 19. public void testPermissionRealm() { 20. Subject subject = shiroLogin("jay", "123"); 21. //判断用户是否已经登录 22. System.out.println("是否登录成功:" + subject.isAuthenticated()); 23. 24. //---------检查当前用户的角色信息------------ 25. System.out.println("是否有管理员角色:"+subject.hasRole("admin")); 26. //---------如果当前用户有此角色,无返回值。若没有此权限,则抛 UnauthorizedException------------ 27. try { 28. subject.checkRole("coder"); 29. System.out.println("有coder角色"); 30. }catch (Exception e){ 31. System.out.println("没有coder角色"); 32. } 33. 34. //---------检查当前用户的权限信息------------ 35. System.out.println("是否有查看订单列表资源:"+subject.isPermitted("order:list")); 36. //---------如果当前用户有此权限,无返回值。若没有此权限,则抛 UnauthorizedException------------ 37. try { 38. subject.checkPermissions("order:add", "order:del"); 39. System.out.println("有添加和删除订单资源"); 40. }catch (Exception e){ 41. System.out.println("没有有添加和删除订单资源"); 42. } 43. 44. } 45. 46. 47. /** 48. * @Description 登录方法 49. */ 50. private Subject shiroLogin(String loginName,String password) { 51. //导入权限ini文件构建权限工厂 52. Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); 53. //工厂构建安全管理器 54. SecurityManager securityManager = factory.getInstance(); 55. //使用SecurityUtils工具生效安全管理器 56. SecurityUtils.setSecurityManager(securityManager); 57. //使用SecurityUtils工具获得主体 58. Subject subject = SecurityUtils.getSubject(); 59. //构建账号token 60. UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(loginName, password); 61. //登录操作 62. subject.login(usernamePasswordToken); 63. return subject; 64. } 65. }
【2.3】授权源码追踪
(1)客户端调用 subject.hasRole("admin"),判断当前用户是否有"admin"角色权限。
(2)Subject门面对象接收到要被验证的角色信息"admin",并将其委托给securityManager中验证。
(3)securityManager将验证请求再次委托给内部的小弟:内部组件Authorizer authorizer
(4)内部小弟authorizer也是个混子,将其委托给了我们自定义的 去做
(5) 先拿到PrincipalCollection principal对象,同时传入校验的角色循环校验,循环中先创建鉴权信息
(6)先看缓存中是否已经有鉴权信息
(7)都是一群懒货!!最后干活的还是我这个猴子!
【3】小结
1、鉴权需要实现doGetAuthorizationInfo方法
2、鉴权使用门面subject中方法进行鉴权
以check开头的会抛出异常
以is和has开头会返回布尔值