Spring boot 入门(四):集成 Shiro 实现登陆认证和权限管理
本文是接着上篇博客写的:spring boot 入门(三):springboot 集成结合 adminlte(freemarker),利用 generate 自动生成代码,利用 datatable 和 pagehelper 进行分页显示。按照前面的博客,已经可以搭建一个简单的 spring boot 系统,本篇博客继续对此系统进行改造,主要集成了 shiro 权限认证框架,关于 shiro 部分,在本人之前的博客(认证与shiro安全框架)有介绍到,这里就不做累赘的介绍。
此系列的博客为实践部分,以代码和搭建系统的过程为主,如遇到专业名词,自行查找其含义。
1.shiro 配置类
系统搭建到目前为止,主要用到了3个配置类,均与 shiro 有关,后期随着项目的扩大,配置文件也会随之增多。
- freemarkerconfig:主要针对 freemarker 页面显示的配置,关于 shiro 部分,为 shiro 标签设置了共享变量
,如果不设置此变量,freemarker 页面将不能识别 shiro 的标签
,其主要代码如下:
1 configuration.setsharedvariable("shiro", new shirotags());
- mshirofilterfactorybean:设置了过滤器,当然也可以在 config 文件里面配置过滤器,其缺点是:
在每次请求里面都做了 session 的读取和更新访问时间等操作,这样在集群部署 session 共享的情况下,数量级的加大了处理量负载
。本项目后期将用到分布式,因此这里就直接将过滤器与 config 配置文件分离,提高效率。
1 private final class mspringshirofilter extends abstractshirofilter { 2 protected mspringshirofilter(websecuritymanager websecuritymanager, filterchainresolver resolver) { 3 super(); 4 if (websecuritymanager == null) { 5 throw new illegalargumentexception("websecuritymanager property cannot be null."); 6 } 7 setsecuritymanager(websecuritymanager); 8 if (resolver != null) { 9 setfilterchainresolver(resolver); 10 } 11 } 12 13 @override 14 protected void dofilterinternal(servletrequest servletrequest, servletresponse servletresponse, 15 filterchain chain) throws servletexception, ioexception { 16 httpservletrequest request = (httpservletrequest) servletrequest; 17 string str = request.getrequesturi().tolowercase(); 18 boolean flag = true; 19 int idx = 0; 20 if ((idx = str.indexof(".")) > 0) { 21 str = str.substring(idx); 22 if (ignoreext.contains(str.tolowercase())) 23 flag = false; 24 } 25 if (flag) { 26 super.dofilterinternal(servletrequest, servletresponse, chain); 27 } else { 28 chain.dofilter(servletrequest, servletresponse); 29 } 30 } 31 32 }
- shiroconfiguration:通用配置文件,此配置文件为 shiro 的基础通用配置文件,只要是集成 shiro,必有此文件,主要配置 shiro 的登录认证相关的信息,其代码如下:
1 /** 2 * 设置shiro的缓存,缓存参数均配置在xml文件中 3 * @return 4 */ 5 @bean 6 public ehcachemanager getehcachemanager() { 7 ehcachemanager em = new ehcachemanager(); 8 em.setcachemanagerconfigfile("classpath:ehcache/ehcache-shiro.xml"); 9 return em; 10 } 11 /** 12 * 凭证匹配器 13 * (由于我们的密码校验交给shiro的simpleauthenticationinfo进行处理了 14 * 所以我们需要修改下dogetauthenticationinfo中的代码; 15 * ) 16 * @return 17 */ 18 @bean 19 public hashedcredentialsmatcher hashedcredentialsmatcher(){ 20 hashedcredentialsmatcher hashedcredentialsmatcher = new hashedcredentialsmatcher(); 21 hashedcredentialsmatcher.sethashalgorithmname("md5");//散列算法:这里使用md5算法; 22 hashedcredentialsmatcher.sethashiterations(1);//散列的次数,比如散列两次,相当于 md5(md5("")); 23 return hashedcredentialsmatcher; 24 } 25 /** 26 * 27 * 主文件 28 */ 29 @bean(name = "myshirorealm") 30 public userrealm myshirorealm(ehcachemanager cachemanager) { 31 userrealm realm = new userrealm(); 32 realm.setcachemanager(cachemanager); 33 realm.setcredentialsmatcher(hashedcredentialsmatcher()); 34 return realm; 35 } 36 //会话id生成器 37 @bean(name = "sessionidgenerator") 38 public javauuidsessionidgenerator javauuidsessionidgenerator(){ 39 javauuidsessionidgenerator javauuidsessionidgenerator = new javauuidsessionidgenerator(); 40 return javauuidsessionidgenerator; 41 } 42 @bean(name = "sessionidcookie") 43 public simplecookie getsessionidcookie(){ 44 simplecookie sessionidcookie = new simplecookie("sid"); 45 sessionidcookie.sethttponly(true); 46 sessionidcookie.setmaxage(-1); 47 return sessionidcookie; 48 49 } 50 /*<!-- 会话dao -->*/ 51 @bean(name = "sessiondao") 52 public enterprisecachesessiondao enterprisecachesessiondao(){ 53 enterprisecachesessiondao sessiondao = new enterprisecachesessiondao(); 54 sessiondao.setsessionidgenerator(javauuidsessionidgenerator()); 55 sessiondao.setactivesessionscachename("shiro-activesessioncache"); 56 return sessiondao; 57 } 58 @bean(name = "sessionvalidationscheduler") 59 public executorservicesessionvalidationscheduler getexecutorservicesessionvalidationscheduler() { 60 executorservicesessionvalidationscheduler scheduler = new executorservicesessionvalidationscheduler(); 61 scheduler.setinterval(1800000); 62 return scheduler; 63 } 64 @bean(name = "sessionmanager") 65 public defaultwebsessionmanager sessionmanager(enterprisecachesessiondao sessiondao){ 66 defaultwebsessionmanager sessionmanager = new defaultwebsessionmanager(); 67 sessionmanager.setglobalsessiontimeout(1800000); 68 sessionmanager.setdeleteinvalidsessions(true); 69 sessionmanager.setsessionvalidationschedulerenabled(true); 70 sessionmanager.setsessionvalidationscheduler(getexecutorservicesessionvalidationscheduler()); 71 sessionmanager.setsessiondao(sessiondao); 72 sessionmanager.setsessionidcookieenabled(true); 73 sessionmanager.setsessionidcookie(getsessionidcookie()); 74 return sessionmanager; 75 } 76 @bean(name = "lifecyclebeanpostprocessor") 77 public lifecyclebeanpostprocessor getlifecyclebeanpostprocessor() { 78 return new lifecyclebeanpostprocessor(); 79 } 80 @bean 81 public defaultadvisorautoproxycreator getdefaultadvisorautoproxycreator() { 82 defaultadvisorautoproxycreator daap = new defaultadvisorautoproxycreator(); 83 daap.setproxytargetclass(true); 84 return daap; 85 } 86 @bean(name = "securitymanager") 87 public defaultwebsecuritymanager getdefaultwebsecuritymanager(userrealm myshirorealm, defaultwebsessionmanager sessionmanager) { 88 defaultwebsecuritymanager dwsm = new defaultwebsecuritymanager(); 89 dwsm.setrealm(myshirorealm); 90 // <!-- 用户授权/认证信息cache, 采用ehcache 缓存 --> 91 dwsm.setcachemanager(getehcachemanager()); 92 dwsm.setsessionmanager(sessionmanager); 93 return dwsm; 94 } 95 /** 96 * 开启shiro aop注解支持. 97 * 使用代理方式;所以需要开启代码支持; 98 * @param securitymanager 99 * @return 100 */ 101 @bean 102 public authorizationattributesourceadvisor getauthorizationattributesourceadvisor(defaultwebsecuritymanager securitymanager) { 103 authorizationattributesourceadvisor aasa = new authorizationattributesourceadvisor(); 104 aasa.setsecuritymanager(securitymanager); 105 return aasa; 106 } 107 /** 108 * shirofilter<br/> 109 * 注意这里参数中的 studentservice 和 iscoredao 只是一个例子,因为我们在这里可以用这样的方式获取到相关访问数据库的对象, 110 * 然后读取数据库相关配置,配置到 shirofilterfactorybean 的访问规则中。实际项目中,请使用自己的service来处理业务逻辑。 111 * 112 */ 113 @bean(name = "shirofilter") 114 public shirofilterfactorybean getshirofilterfactorybean(defaultwebsecuritymanager securitymanager) { 115 shirofilterfactorybean shirofilterfactorybean = new mshirofilterfactorybean(); 116 // 必须设置 securitymanager 117 shirofilterfactorybean.setsecuritymanager(securitymanager); 118 // 如果不设置默认会自动寻找web工程根目录下的"/login.jsp"页面 119 shirofilterfactorybean.setloginurl("/login"); 120 // 登录成功后要跳转的连接 121 shirofilterfactorybean.setsuccessurl("/certification"); 122 //shirofilterfactorybean.setsuccessurl("/index"); 123 shirofilterfactorybean.setunauthorizedurl("/403"); 124 loadshirofilterchain(shirofilterfactorybean); 125 return shirofilterfactorybean; 126 } 127 /** 128 * 加载shirofilter权限控制规则(从数据库读取然后配置) 129 * 130 */ 131 private void loadshirofilterchain(shirofilterfactorybean shirofilterfactorybean){ 132 /////////////////////// 下面这些规则配置最好配置到配置文件中 /////////////////////// 133 map<string, string> filterchaindefinitionmap = new linkedhashmap<string, string>(); 134 // authc:该过滤器下的页面必须验证后才能访问,它是shiro内置的一个拦截器org.apache.shiro.web.filter.authc.formauthenticationfilter 135 filterchaindefinitionmap.put("/login", "authc"); 136 filterchaindefinitionmap.put("/logout", "logout"); 137 // anon:它对应的过滤器里面是空的,什么都没做 138 logger.info("##################从数据库读取权限规则,加载到shirofilter中##################"); 139 // filterchaindefinitionmap.put("/user/edit/**", "authc,perms[user:edit]");// 这里为了测试,固定写死的值,也可以从数据库或其他配置中读取 140 shirofilterfactorybean.setfilterchaindefinitionmap(filterchaindefinitionmap); 141 }
2.登录认证与权限管理
主要重写了 realm域,完成权限认证和权限管理:
1 protected authorizationinfo dogetauthorizationinfo(principalcollection principals) { 2 //如果没有做权限验证,此处只需要return null即可 3 simpleauthorizationinfo authorizationinfo = new simpleauthorizationinfo(); 4 string username = (string) principals.getprimaryprincipal(); 5 result<tuser> list = userservice.getuserbyusername(username); 6 if(list.isstatus()) { 7 //获取该用户所属的角色 8 result<list<trole>> resultrole = roleservice.getrolebyuserid(list.getresultdata().getuserid()); 9 if(resultrole.isstatus()) { 10 hashset<string> role = new hashset<string>(); 11 for(trole trole : resultrole.getresultdata()) { 12 role.add(trole.getroleid()+""); 13 } 14 //获取该角色拥有的权限 15 result<list<tpermission>> resultpermission = permissionservice.getpermissionsbyroleid(role); 16 if(resultpermission.isstatus()) { 17 hashset<string> permissions = new hashset<string>(); 18 for(tpermission tpermission : resultpermission.getresultdata()) { 19 permissions.add(tpermission.getpermissionsvalue()); 20 } 21 system.out.println("权限:"+permissions); 22 authorizationinfo.setstringpermissions(permissions); 23 } 24 } 25 } 26 //return null; 27 return authorizationinfo; 28 } 29 30 @override 31 protected authenticationinfo dogetauthenticationinfo(authenticationtoken authenticationtoken) throws authenticationexception { 32 //认证登录 33 string username = (string) authenticationtoken.getprincipal(); 34 //string password = new string((char[]) authenticationtoken.getcredentials()); 35 result<tuser> result = userservice.getuserbyusername(username); 36 if (result.isstatus()) { 37 tuser user = result.getresultdata(); 38 return new simpleauthenticationinfo(user.getusername(), user.getpassword(), getname()); 39 } 40 //return new simpleauthenticationinfo(user., "123456", getname()); 41 return null; 42 } 43 }
2.1.登录认证
首先创建一个前端登录界面,做一个简单的登录 form 表单
点击登录即想后台发送一个请求,必须是post请求,否则shiro不能识别
,认证部分主要在 ream 中完成,新建一个类,继承 authorizingrealm ,然后在重写 dogetauthenticationinfo 方法:
new simpleauthenticationinfo(user.getusername(), user.getpassword(), getname())
),我们可以自己重新定义密码比较器,密码比较器的写法较多,在认证与shiro安全框架中,直接将密码比较器写入到ream中,耦合度太高,本项目通过配置的方式重写密码比较器,具体代码请参考参考shiroconfiguration配置类:
1 @requestmapping(value = "/login", method = requestmethod.post) 2 public string postlogin(redirectattributes redirectattributes, httpservletrequest request, httpsession session) { 3 // 登录失败从request中获取shiro处理的异常信息。 4 // shirologinfailure:就是shiro异常类的全类名. 5 string exception = (string) request.getattribute("shirologinfailure"); 6 7 system.out.println("exception=" + exception); 8 string msg = ""; 9 if (exception != null) { 10 if (unknownaccountexception.class.getname().equals(exception)) { 11 system.out.println("unknownaccountexception -- > 账号不存在:"); 12 msg = "用户不存在!"; 13 } else if (incorrectcredentialsexception.class.getname().equals(exception)) { 14 system.out.println("incorrectcredentialsexception -- > 密码不正确:"); 15 msg = "密码不正确!"; 16 } else if ("kaptchavalidatefailed".equals(exception)) { 17 system.out.println("kaptchavalidatefailed -- > 验证码错误"); 18 msg = "验证码错误!"; 19 } else { 20 //msg = "else >> "+exception; 21 msg = "密码不正确!"; 22 system.out.println("else -- >" + exception); 23 } 24 } 25 redirectattributes.addflashattribute("msg", msg); 26 session.setattribute("msg", msg); 27 //return redirect("/login"); 28 return "redirect:login"; 29 //return msg; 30 }
此时登录认证部门已经完成:一个页面+后台2个函数(1个认证函数+1个login函数)
2.2.权限管理
总体来说,权限管理只需要在界面增加 shiro 的权限标签即可,可以使用角色的标签,也可以使用权限的标签,一般情况下2种标签配合使用,效果最好 <@shiro.haspermission name="xtgl-yhgl:read">
<@shiro.hasrolen name="xtgl-yhgl:read">
authorizationinfo.setstringpermissions(permissions);
authorizationinfo.setroles(role);
本项目也是通过此逻辑完成权限管理的
上面2张截图表示的是一个函数。
到此,spring boot集成shiro框架的权限认证已经搭建完毕,可以实现简单的权限管理。
3.新增文件
较上一篇博客,shiro 部分新增加的文件