虽然都2020年了,但我确实还不大会用shiro,所以利用空闲时间学习了一下基础的用法,参考网上的资料,总结一下自己的理解,也方便自己偶尔看看0.0。
Shiro是什么?
Shiro是一个轻量级的安全认证框架,他可以完成认证,授权,会话管理,缓存等一系列功能。
这是从百度百科kiang来的结构图:
我个人对于这些结构及概念性的文字很头疼,一看就容易云里雾里,但是概念确实还是非常重要的,所以我大部分时候还是结合着代码理解概念。
怎么操作?(springboot整合Shiro)
-
首先,创建一个普通的springboot项目,除springboot之外的依赖
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
我这里用了lombok插件,可以理解成是一个使用注解给实体类生成get/set,toString。。这些方法的插件,很好用。
-
三个实体类
@Data 会生成各属性get/set,重写equals(),hashCode(),toString()方法
@AllArgsConstructor 会生成全参构造方法
@NoArgsConstructor 即为无参构造方法
User.java(用户信息)
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String username; private String password; private Set<Role> roles; }
Role.java (角色)
@Data @AllArgsConstructor @NoArgsConstructor public class Role { private int id; private String roleName; private Set<Permission> permissions; }
Permission.java (权限)
@Data @AllArgsConstructor @NoArgsConstructor public class Permission { private int id; private String permissionName; }
-
草率的业务实现类
假装我们是从持久层拿到的数据,我一共准备了两个用户(zhangsan,lisi),两个角色(admin,user),两个权限字符(query,add)。
zhangsan 为 admin 拥有 query,add 两个权限。
lisi 为 user 拥有 query 权限。
@Service public class LoginServiceImpl implements LoginService { /** * 模拟数据库查询用户信息 * * @param name * @return */ @Override public User getUserByName(String name) { //一共两个权限 query和add 管理员拥有两个权限 用户只拥有query Permission p1 = new Permission(1,"query"); Permission p2 = new Permission(2,"add"); Set<Permission> ap = new HashSet<>(); ap.add(p1); ap.add(p2); Set<Permission> up = new HashSet<>(); up.add(p1); Role r1 = new Role(1,"admin",ap); Role r2 = new Role(2,"user",up); Set<Role> ar = new HashSet<>(); ar.add(r1); Set<Role> ur = new HashSet<>(); ur.add(r2); //张三为管理员角色 李四为普通用户角色 User u1 = new User(1,"zhangsan","123456",ar); User u2 = new User(2,"lisi","123456",ur); Map<String,User> map = new HashMap<>(); map.put("zhangsan",u1); map.put("lisi",u2); return map.get(name); } }
-
自定义Realm用于查询用户的角色和授权信息
需要继承 AuthorizingRealm 并重写他的两个方法,上面的是授权,下面的是登录认证。
/** * 自定义Realm用于查询用户的角色和权限信息并保存到权限管理器 */ public class CustomRealm extends AuthorizingRealm { @Autowired LoginService loginService; /** * 授权 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取用户名 String name = (String) principalCollection.getPrimaryPrincipal(); //获取用户 User user = loginService.getUserByName(name); //添加角色和权限 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); for (Role r : user.getRoles()) { //添加角色 simpleAuthorizationInfo.addRole(r.getRoleName()); //添加权限 for (Permission p : r.getPermissions()) { simpleAuthorizationInfo.addStringPermission(p.getPermissionName()); } } return simpleAuthorizationInfo; } /** * 登录验证 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { if (null == authenticationToken.getPrincipal()) return null; //获取用户 String name = authenticationToken.getPrincipal().toString(); User user = loginService.getUserByName(name); if (null == user) return null; SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword(), getName()); return simpleAuthenticationInfo; } }
-
把一系列东西放入spring容器
把自定义的
Realm
和实现记住我功能的cookieRememberMeManager
放进SecurityManager
,再把SecurityManager
放入spring容器。再配置Shiro的过滤器。
tips:map.put("/**",“user”); 这个是指放行登录认证成功或记住我的用户。
/** * 把CustomRealm和SecurityManager等加入到spring容器 */ @Configuration public class ShiroConfig { @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator defaultAAPC = new DefaultAdvisorAutoProxyCreator(); defaultAAPC.setProxyTargetClass(true); return defaultAAPC; } //自定义的验证方式 @Bean public CustomRealm myShiroRealm(){ CustomRealm cr = new CustomRealm(); return cr; } //配置cookie基础属性 public SimpleCookie simpleCookie(){ SimpleCookie sc = new SimpleCookie("rememberMe"); //cookie有效时间 单位秒 以下设置了30天 sc.setMaxAge(30 * 24 * 60 * 60); return sc; } //cookie管理器 public CookieRememberMeManager cookieRememberMeManager(){ CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(simpleCookie()); // cookieRememberMeManager.setCipherKey用来设置加密的Key,参数类型byte[],字节数组长度要求16 cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag==")); return cookieRememberMeManager; } //权限管理 @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager dsm = new DefaultWebSecurityManager(); //自定义验证方式 dsm.setRealm(myShiroRealm()); //记住我 dsm.setRememberMeManager(cookieRememberMeManager()); return dsm; } //Filter工厂,设置对应的过滤条件和跳转条件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); Map<String,String> map = new HashMap<>(); //登出 map.put("/logout", "logout"); //对所有用户认证 map.put("/**","user"); //登录 shiroFilterFactoryBean.setLoginUrl("/login"); //首页 登录成功跳转 shiroFilterFactoryBean.setSuccessUrl("/index"); //验证失败跳转 shiroFilterFactoryBean.setUnauthorizedUrl("/error"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
-
登录控制器
重点是学校Shiro,所以就不写页面了,直接返回字符串。
@RestController public class LoginController { @RequestMapping("/login") public String login(String username, String password, boolean rememberMe) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken upt = new UsernamePasswordToken(username, password); try { upt.setRememberMe(rememberMe); subject.login(upt); //可以在逻辑中检查用户有没有相应角色或权限 没有的话会抛出AuthorizationException异常 // subject.checkRole("admin"); // subject.checkPermission("add"); } catch (AuthenticationException e) { //不同的操作错误会抛出不同的异常 e.printStackTrace(); return "账号或密码错误"; } catch (AuthorizationException e) { e.printStackTrace(); return "无权限"; } return "login success"; } /** * 注解验证 */ @RequiresRoles("admin") @RequiresPermissions("add") @RequestMapping("/index") public String index() { return "index"; } }
-
由于注解方式不方便捕获异常,所以增加了一个类拦截异常
/** * 通过注解的方式无法捕捉到抛出的异常,也就没有办法很好的给前端反馈 * 所以使用这个类拦截注解方式抛出的无权限异常 * */ @ControllerAdvice @Slf4j public class MyExpectHandle { @ExceptionHandler @ResponseBody public String ErrorHandle(AuthorizationException e){ log.error("未通过权限验证",e); return "使用注解方式拦截下的未通过权限验证异常"; } }
-
启动
访问: http://localhost:8080/login?username=zhangsan&password=123456&rememberMe=1
提示 login success 也就是登录成功了
再访问: http://localhost:8080/index
ok 没问题 证明登录成功,且权限没问题
重启浏览器
再访问 : http://localhost:8080/index
ok 还是没问题,证明记住我功能生效了
检测权限,访问: http://localhost:8080/login?username=lisi&password=123456&rememberMe=0
返回 login success 后再访问:http://localhost:8080/index
提示没有权限,ok,权限判断生效了
重启浏览器
再访问:http://localhost:8080/index
需要重新登录,ok
参考: https://www.jianshu.com/p/7f724bec3dc3
感谢大佬!