springboot shiro权限管理
本来是打算接着写关于数据库方面,集成mybatis的,刚好赶上朋友问到shiro权限管理,就先总结下发出来了。
使用shiro之前用在spring mvc中,是通过xml文件进行配置。
既然现在在写spring boot的帖子,就将shiro应用到spring boot中,我本地已经完成了springboot使用shiro的实例,将配置方法共享一下。
先简单介绍一下shiro,对于没有用过shiro的朋友,也算是做个简介吧。
shiro是apache下的一个开源项目,我们称之为apache shiro。它是一个很易用与java项目的的安全框架,提供了认证、授权、加密、会话管理,与 spring security 一样都是做一个权限的安全框架,但是与spring security 相比,在于 shiro 使用了比较简单易懂易于使用的授权方式。
apache shiro 的三大核心组件
subject
当前用户操作securitymanager
用于管理所有的subjectrealms
用于进行权限信息的验证,也是我们需要自己实现的。
我们需要实现realms的authentication 和 authorization。其中 authentication 是用来验证用户身份,authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
apache shiro 核心通过 filter 来实现,就好像springmvc 通过dispachservlet 来主控制一样。
既然是使用 filter 一般也就能猜到,是通过url规则来进行过滤和权限校验,所以我们需要定义一系列关于url的规则和访问权限。
另外我们可以通过shiro 提供的会话管理来获取session中的信息。shiro 也提供了缓存支持,使用 cachemanager 来管理。
完整架构图:
下面我们通过代码实战来看下spring boot 中应用shiro:
1、创建数据库表
表(t_permission) id permissionname role_id ------ -------------- --------- 1 add 2 2 del 1 3 update 2 4 query 3 5 user:query 1 6 user:edit 2 表(t_role) id rolename ------ ---------- 1 admin 2 manager 3 normal 表(t_user) id username password ------ -------- ---------- 1 tom 123456 2 jack 123456 3 rose 123456 表(t_user_role) user_id role_id ------- --------- 1 1 1 3 2 2 2 3 3 3
看截图,上面3张表是我测试别的用的,可以忽略。
下面是,数据库脚本和测试数据。
/* sqlyog ultimate v10.00 beta1 mysql - 5.5.28 : database - test ********************************************************************* */ /*!40101 set names utf8 */; /*!40101 set sql_mode=''*/; /*!40014 set @old_unique_checks=@@unique_checks, unique_checks=0 */; /*!40014 set @old_foreign_key_checks=@@foreign_key_checks, foreign_key_checks=0 */; /*!40101 set @old_sql_mode=@@sql_mode, sql_mode='no_auto_value_on_zero' */; /*!40111 set @old_sql_notes=@@sql_notes, sql_notes=0 */; create database /*!32312 if not exists*/`test` /*!40100 default character set utf8 */; use `test`; /*table structure for table `t_permission` */ drop table if exists `t_permission`; create table `t_permission` ( `id` int(11) not null auto_increment, `permissionname` varchar(32) default null, `role_id` int(11) default null, key `id` (`id`) ) engine=innodb auto_increment=7 default charset=utf8; /*data for the table `t_permission` */ insert into `t_permission`(`id`,`permissionname`,`role_id`) values (1,'add',2),(2,'del',1),(3,'update',2),(4,'query',3),(5,'user:query',1),(6,'user:edit',2); /*table structure for table `t_role` */ drop table if exists `t_role`; create table `t_role` ( `id` int(11) not null auto_increment, `rolename` varchar(32) default null, key `id` (`id`) ) engine=innodb auto_increment=4 default charset=utf8; /*data for the table `t_role` */ insert into `t_role`(`id`,`rolename`) values (1,'admin'),(2,'manager'),(3,'normal'); /*table structure for table `t_user` */ drop table if exists `t_user`; create table `t_user` ( `id` int(11) not null auto_increment, `username` varchar(32) default null, `password` varchar(32) default null, primary key (`id`) ) engine=innodb auto_increment=4 default charset=utf8; /*data for the table `t_user` */ insert into `t_user`(`id`,`username`,`password`) values (1,'tom','123456'),(2,'jack','123456'),(3,'rose','123456'); /*table structure for table `t_user_role` */ drop table if exists `t_user_role`; create table `t_user_role` ( `user_id` int(11) default null, `role_id` int(11) default null ) engine=innodb default charset=utf8; /*data for the table `t_user_role` */ insert into `t_user_role`(`user_id`,`role_id`) values (1,1),(1,3),(2,2),(2,3),(3,3); /*!40101 set sql_mode=@old_sql_mode */; /*!40014 set foreign_key_checks=@old_foreign_key_checks */; /*!40014 set unique_checks=@old_unique_checks */; /*!40111 set sql_notes=@old_sql_notes */;
2、创建对应实体类
- user.java
package org.springboot.sample.entity; import java.util.hashset; import java.util.list; import java.util.set; import javax.persistence.entity; import javax.persistence.fetchtype; import javax.persistence.generatedvalue; import javax.persistence.generationtype; import javax.persistence.id; import javax.persistence.joincolumn; import javax.persistence.jointable; import javax.persistence.manytomany; import javax.persistence.table; import javax.persistence.transient; import org.hibernate.validator.constraints.notempty; /** * 用户 * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月13日 */ @entity @table(name = "t_user") public class user { @id @generatedvalue(strategy = generationtype.identity) private integer id; @notempty(message = "用户名不能为空") private string username; @notempty(message = "密码不能为空") private string password; @manytomany(fetch=fetchtype.eager) @jointable(name = "t_user_role", joincolumns = { @joincolumn(name = "user_id") }, inversejoincolumns = { @joincolumn(name = "role_id") }) private list<role> rolelist;// 一个用户具有多个角色 public user() { super(); } public user(string username, string password) { super(); this.username = username; this.password = password; } // 省略 get set 方法 @transient public set<string> getrolesname() { list<role> roles = getrolelist(); set<string> set = new hashset<string>(); for (role role : roles) { set.add(role.getrolename()); } return set; } }
- role.java
package org.springboot.sample.entity; import java.util.arraylist; import java.util.list; import javax.persistence.entity; import javax.persistence.fetchtype; import javax.persistence.generatedvalue; import javax.persistence.generationtype; import javax.persistence.id; import javax.persistence.joincolumn; import javax.persistence.jointable; import javax.persistence.manytomany; import javax.persistence.onetomany; import javax.persistence.table; import javax.persistence.transient; /** * 角色(管理员,普通用户等) * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月13日 */ @entity @table(name = "t_role") public class role { @id @generatedvalue(strategy = generationtype.identity) private integer id; private string rolename; @onetomany(mappedby = "role", fetch=fetchtype.eager) private list<permission> permissionlist;// 一个角色对应多个权限 @manytomany @jointable(name = "t_user_role", joincolumns = { @joincolumn(name = "role_id") }, inversejoincolumns = { @joincolumn(name = "user_id") }) private list<user> userlist;// 一个角色对应多个用户 // 省略 get set 方法 @transient public list<string> getpermissionsname() { list<string> list = new arraylist<string>(); list<permission> perlist = getpermissionlist(); for (permission per : perlist) { list.add(per.getpermissionname()); } return list; } }
- permission.java
package org.springboot.sample.entity; import javax.persistence.entity; import javax.persistence.generatedvalue; import javax.persistence.generationtype; import javax.persistence.id; import javax.persistence.joincolumn; import javax.persistence.manytoone; import javax.persistence.table; /** * 权限(增删改查等) * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月13日 */ @entity @table(name = "t_permission") public class permission { @id @generatedvalue(strategy = generationtype.identity) private integer id; private string permissionname; @manytoone @joincolumn(name = "role_id") private role role;// 一个权限对应一个角色 // 省略 get set }
3、shiro 配置,相当于springmvc 中的xml配置
- shiroconfiguration.java
package org.springboot.sample.config; import java.util.linkedhashmap; import java.util.map; import org.apache.shiro.cache.ehcache.ehcachemanager; import org.apache.shiro.spring.lifecyclebeanpostprocessor; import org.apache.shiro.spring.security.interceptor.authorizationattributesourceadvisor; import org.apache.shiro.spring.web.shirofilterfactorybean; import org.apache.shiro.web.mgt.defaultwebsecuritymanager; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springboot.sample.dao.iscoredao; import org.springboot.sample.security.myshirorealm; import org.springboot.sample.service.studentservice; import org.springframework.aop.framework.autoproxy.defaultadvisorautoproxycreator; import org.springframework.boot.context.embedded.filterregistrationbean; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; import org.springframework.web.filter.delegatingfilterproxy; /** * shiro 配置 * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月13日 */ @configuration public class shiroconfiguration { private static final logger logger = loggerfactory.getlogger(shiroconfiguration.class); @bean public ehcachemanager getehcachemanager() { ehcachemanager em = new ehcachemanager(); em.setcachemanagerconfigfile("classpath:ehcache-shiro.xml"); return em; } @bean(name = "myshirorealm") public myshirorealm myshirorealm(ehcachemanager cachemanager) { myshirorealm realm = new myshirorealm(); realm.setcachemanager(cachemanager); return realm; } /** * 注册delegatingfilterproxy(shiro) * 集成shiro有2种方法: * 1. 按这个方法自己组装一个filterregistrationbean(这种方法更为灵活,可以自己定义urlpattern, * 在项目使用中你可能会因为一些很但疼的问题最后采用它, 想使用它你可能需要看官网或者已经很了解shiro的处理原理了) * 2. 直接使用shirofilterfactorybean(这种方法比较简单,其内部对shirofilter做了组装工作,无法自己定义urlpattern, * 默认拦截 /*) * * @param dispatcherservlet * @return * @author shanhy * @create 2016年1月13日 */ // @bean // public filterregistrationbean filterregistrationbean() { // filterregistrationbean filterregistration = new filterregistrationbean(); // filterregistration.setfilter(new delegatingfilterproxy("shirofilter")); // // 该值缺省为false,表示生命周期由springapplicationcontext管理,设置为true则表示由servletcontainer管理 // filterregistration.addinitparameter("targetfilterlifecycle", "true"); // filterregistration.setenabled(true); // filterregistration.addurlpatterns("/*");// 可以自己灵活的定义很多,避免一些根本不需要被shiro处理的请求被包含进来 // return filterregistration; // } @bean(name = "lifecyclebeanpostprocessor") public lifecyclebeanpostprocessor getlifecyclebeanpostprocessor() { return new lifecyclebeanpostprocessor(); } @bean public defaultadvisorautoproxycreator getdefaultadvisorautoproxycreator() { defaultadvisorautoproxycreator daap = new defaultadvisorautoproxycreator(); daap.setproxytargetclass(true); return daap; } @bean(name = "securitymanager") public defaultwebsecuritymanager getdefaultwebsecuritymanager(myshirorealm myshirorealm) { defaultwebsecuritymanager dwsm = new defaultwebsecuritymanager(); dwsm.setrealm(myshirorealm); // <!-- 用户授权/认证信息cache, 采用ehcache 缓存 --> dwsm.setcachemanager(getehcachemanager()); return dwsm; } @bean public authorizationattributesourceadvisor getauthorizationattributesourceadvisor(defaultwebsecuritymanager securitymanager) { authorizationattributesourceadvisor aasa = new authorizationattributesourceadvisor(); aasa.setsecuritymanager(securitymanager); return aasa; } /** * 加载shirofilter权限控制规则(从数据库读取然后配置) * * @author shanhy * @create 2016年1月14日 */ private void loadshirofilterchain(shirofilterfactorybean shirofilterfactorybean, studentservice stuservice, iscoredao scoredao){ /// 下面这些规则配置最好配置到配置文件中 /// map<string, string> filterchaindefinitionmap = new linkedhashmap<string, string>(); // authc:该过滤器下的页面必须验证后才能访问,它是shiro内置的一个拦截器org.apache.shiro.web.filter.authc.formauthenticationfilter filterchaindefinitionmap.put("/user", "authc");// 这里为了测试,只限制/user,实际开发中请修改为具体拦截的请求规则 // anon:它对应的过滤器里面是空的,什么都没做 logger.info("##################从数据库读取权限规则,加载到shirofilter中##################"); filterchaindefinitionmap.put("/user/edit/**", "authc,perms[user:edit]");// 这里为了测试,固定写死的值,也可以从数据库或其他配置中读取 filterchaindefinitionmap.put("/login", "anon"); filterchaindefinitionmap.put("/**", "anon");//anon 可以理解为不拦截 shirofilterfactorybean.setfilterchaindefinitionmap(filterchaindefinitionmap); } /** * shirofilter<br/> * 注意这里参数中的 studentservice 和 iscoredao 只是一个例子,因为我们在这里可以用这样的方式获取到相关访问数据库的对象, * 然后读取数据库相关配置,配置到 shirofilterfactorybean 的访问规则中。实际项目中,请使用自己的service来处理业务逻辑。 * * @param myshirorealm * @param stuservice * @param scoredao * @return * @author shanhy * @create 2016年1月14日 */ @bean(name = "shirofilter") public shirofilterfactorybean getshirofilterfactorybean(defaultwebsecuritymanager securitymanager, studentservice stuservice, iscoredao scoredao) { shirofilterfactorybean shirofilterfactorybean = new mshirofilterfactorybean(); // 必须设置 securitymanager shirofilterfactorybean.setsecuritymanager(securitymanager); // 如果不设置默认会自动寻找web工程根目录下的"/login.jsp"页面 shirofilterfactorybean.setloginurl("/login"); // 登录成功后要跳转的连接 shirofilterfactorybean.setsuccessurl("/user"); shirofilterfactorybean.setunauthorizedurl("/403"); loadshirofilterchain(shirofilterfactorybean, stuservice, scoredao); return shirofilterfactorybean; } }
/** * 继承 shirofilterfactorybean 处理拦截资源文件问题。 * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年3月8日 */ public class mshirofilterfactorybean extends shirofilterfactorybean { // 对shirofilter来说,需要直接忽略的请求 private set<string> ignoreext; public mshirofilterfactorybean() { super(); ignoreext = new hashset<>(); ignoreext.add(".jpg"); ignoreext.add(".png"); ignoreext.add(".gif"); ignoreext.add(".bmp"); ignoreext.add(".js"); ignoreext.add(".css"); } @override protected abstractshirofilter createinstance() throws exception { securitymanager securitymanager = getsecuritymanager(); if (securitymanager == null) { string msg = "securitymanager property must be set."; throw new beaninitializationexception(msg); } if (!(securitymanager instanceof websecuritymanager)) { string msg = "the security manager does not implement the websecuritymanager interface."; throw new beaninitializationexception(msg); } filterchainmanager manager = createfilterchainmanager(); pathmatchingfilterchainresolver chainresolver = new pathmatchingfilterchainresolver(); chainresolver.setfilterchainmanager(manager); return new mspringshirofilter((websecuritymanager) securitymanager, chainresolver); } private final class mspringshirofilter extends abstractshirofilter { protected mspringshirofilter(websecuritymanager websecuritymanager, filterchainresolver resolver) { super(); if (websecuritymanager == null) { throw new illegalargumentexception("websecuritymanager property cannot be null."); } setsecuritymanager(websecuritymanager); if (resolver != null) { setfilterchainresolver(resolver); } } @override protected void dofilterinternal(servletrequest servletrequest, servletresponse servletresponse, filterchain chain) throws servletexception, ioexception { httpservletrequest request = (httpservletrequest)servletrequest; string str = request.getrequesturi().tolowercase(); // 因为shirofilter 拦截所有请求(在上面我们配置了urlpattern 为 * ,当然你也可以在那里精确的添加要处理的路径,这样就不需要这个类了),而在每次请求里面都做了session的读取和更新访问时间等操作,这样在集群部署session共享的情况下,数量级的加大了处理量负载。 // 所以我们这里将一些能忽略的请求忽略掉。 // 当然如果你的集群系统使用了动静分离处理,静态资料的请求不会到filter这个层面,便可以忽略。 boolean flag = true; int idx = 0; if(( idx = str.indexof(".")) > 0){ str = str.substring(idx); if(ignoreext.contains(str.tolowercase())) flag = false; } if(flag){ super.dofilterinternal(servletrequest, servletresponse, chain); }else{ chain.dofilter(servletrequest, servletresponse); } } } }
其中的 ehcache-shiro.xml 在 src/main/resources 下面,内容为:
<?xml version="1.0" encoding="utf-8"?> <ehcache updatecheck="false" name="shirocache"> <defaultcache maxelementsinmemory="10000" eternal="false" timetoidleseconds="120" timetoliveseconds="120" overflowtodisk="false" diskpersistent="false" diskexpirythreadintervalseconds="120" /> </ehcache>
4、继承 authorizingrealm 实现认证和授权2个方法
- myshirorealm.java
package org.springboot.sample.security; import java.util.list; import org.apache.commons.lang3.builder.reflectiontostringbuilder; import org.apache.commons.lang3.builder.tostringstyle; import org.apache.shiro.authc.authenticationexception; import org.apache.shiro.authc.authenticationinfo; import org.apache.shiro.authc.authenticationtoken; import org.apache.shiro.authc.simpleauthenticationinfo; import org.apache.shiro.authc.usernamepasswordtoken; import org.apache.shiro.authz.authorizationinfo; import org.apache.shiro.authz.simpleauthorizationinfo; import org.apache.shiro.realm.authorizingrealm; import org.apache.shiro.subject.principalcollection; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springboot.sample.dao.iuserdao; import org.springboot.sample.entity.role; import org.springboot.sample.entity.user; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.service; import org.springframework.transaction.annotation.transactional; /** * myshirorealm * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月13日 */ public class myshirorealm extends authorizingrealm{ private static final logger logger = loggerfactory.getlogger(myshirorealm.class); @autowired private iuserdao userdao; /** * 权限认证,为当前登录的subject授予角色和权限 * @see 经测试:本例中该方法的调用时机为需授权资源被访问时 * @see 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用authorizationcache * @see 经测试:如果连续访问同一个url(比如刷新),该方法不会被重复调用,shiro有一个时间间隔(也就是cache时间,在ehcache-shiro.xml中配置),超过这个时间间隔再刷新页面,该方法会被执行 */ @override protected authorizationinfo dogetauthorizationinfo(principalcollection principalcollection) { logger.info("##################执行shiro权限认证##################"); //获取当前登录输入的用户名,等价于(string) principalcollection.fromrealm(getname()).iterator().next(); string loginname = (string)super.getavailableprincipal(principalcollection); //到数据库查是否有此对象 user user=userdao.findbyname(loginname);// 实际项目中,这里可以根据实际情况做缓存,如果不做,shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 if(user!=null){ //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission) simpleauthorizationinfo info=new simpleauthorizationinfo(); //用户的角色集合 info.setroles(user.getrolesname()); //用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的四行可以不要 list<role> rolelist=user.getrolelist(); for (role role : rolelist) { info.addstringpermissions(role.getpermissionsname()); } // 或者按下面这样添加 //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色 // simpleauthorinfo.addrole("admin"); //添加权限 // simpleauthorinfo.addstringpermission("admin:manage"); // logger.info("已为用户[mike]赋予了[admin]角色和[admin:manage]权限"); return info; } // 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedurl指定的地址 return null; } /** * 登录认证 */ @override protected authenticationinfo dogetauthenticationinfo( authenticationtoken authenticationtoken) throws authenticationexception { //usernamepasswordtoken对象用来存放提交的登录信息 usernamepasswordtoken token=(usernamepasswordtoken) authenticationtoken; logger.info("验证当前subject时获取到token为:" + reflectiontostringbuilder.tostring(token, tostringstyle.multi_line_style)); //查出是否有此用户 user user=userdao.findbyname(token.getusername()); if(user!=null){ // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,shiro会为我们进行密码对比校验 return new simpleauthenticationinfo(user.getusername(), user.getpassword(), getname()); } return null; } }
注意:其中 userdao.findbyname 这个代码就不贴上了,也没啥可贴的,根据姓名查询一个对象而已。
5、编写测试的 controller 和测试 jsp 页面
- shirocontroller.java
package org.springboot.sample.controller; import java.util.map; import javax.validation.valid; import org.apache.shiro.securityutils; import org.apache.shiro.authc.authenticationexception; import org.apache.shiro.authc.excessiveattemptsexception; import org.apache.shiro.authc.incorrectcredentialsexception; import org.apache.shiro.authc.lockedaccountexception; import org.apache.shiro.authc.unknownaccountexception; import org.apache.shiro.authc.usernamepasswordtoken; import org.apache.shiro.subject.subject; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springboot.sample.dao.iuserdao; import org.springboot.sample.entity.user; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.controller; import org.springframework.ui.model; import org.springframework.validation.bindingresult; import org.springframework.web.bind.annotation.pathvariable; import org.springframework.web.bind.annotation.requestmapping; import org.springframework.web.bind.annotation.requestmethod; import org.springframework.web.servlet.mvc.support.redirectattributes; /** * shiro测试controller * * @author 单红宇(365384722) * @myblog http://blog.csdn.net/catoop/ * @create 2016年1月13日 */ @controller public class shirocontroller { private static final logger logger = loggerfactory.getlogger(shirocontroller.class); @autowired private iuserdao userdao; @requestmapping(value="/login",method=requestmethod.get) public string loginform(model model){ model.addattribute("user", new user()); return "login"; } @requestmapping(value="/login",method=requestmethod.post) public string login(@valid user user,bindingresult bindingresult,redirectattributes redirectattributes){ if(bindingresult.haserrors()){ return "login"; } string username = user.getusername(); usernamepasswordtoken token = new usernamepasswordtoken(user.getusername(), user.getpassword()); //获取当前的subject subject currentuser = securityutils.getsubject(); try { //在调用了login方法后,securitymanager会收到authenticationtoken,并将其发送给已配置的realm执行必须的认证检查 //每个realm都能在必要时对提交的authenticationtokens作出反应 //所以这一步在调用login(token)方法时,它会走到myrealm.dogetauthenticationinfo()方法中,具体验证方式详见此方法 logger.info("对用户[" + username + "]进行登录验证..验证开始"); currentuser.login(token); logger.info("对用户[" + username + "]进行登录验证..验证通过"); }catch(unknownaccountexception uae){ logger.info("对用户[" + username + "]进行登录验证..验证未通过,未知账户"); redirectattributes.addflashattribute("message", "未知账户"); }catch(incorrectcredentialsexception ice){ logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误的凭证"); redirectattributes.addflashattribute("message", "密码不正确"); }catch(lockedaccountexception lae){ logger.info("对用户[" + username + "]进行登录验证..验证未通过,账户已锁定"); redirectattributes.addflashattribute("message", "账户已锁定"); }catch(excessiveattemptsexception eae){ logger.info("对用户[" + username + "]进行登录验证..验证未通过,错误次数过多"); redirectattributes.addflashattribute("message", "用户名或密码错误次数过多"); }catch(authenticationexception ae){ //通过处理shiro的运行时authenticationexception就可以控制用户登录失败或密码错误时的情景 logger.info("对用户[" + username + "]进行登录验证..验证未通过,堆栈轨迹如下"); ae.printstacktrace(); redirectattributes.addflashattribute("message", "用户名或密码不正确"); } //验证是否登录成功 if(currentuser.isauthenticated()){ logger.info("用户[" + username + "]登录认证通过(这里可以进行一些认证通过后的一些系统参数初始化操作)"); return "redirect:/user"; }else{ token.clear(); return "redirect:/login"; } } @requestmapping(value="/logout",method=requestmethod.get) public string logout(redirectattributes redirectattributes ){ //使用权限管理工具进行用户的退出,跳出登录,给出提示信息 securityutils.getsubject().logout(); redirectattributes.addflashattribute("message", "您已安全退出"); return "redirect:/login"; } @requestmapping("/403") public string unauthorizedrole(){ logger.info("------没有权限-------"); return "403"; } @requestmapping("/user") public string getuserlist(map<string, object> model){ model.put("userlist", userdao.getlist()); return "user"; } @requestmapping("/user/edit/{userid}") public string getuserlist(@pathvariable int userid){ logger.info("------进入用户信息修改-------"); return "user_edit"; } }
- login.jsp
<%@ page language="java" import="java.util.*" pageencoding="utf-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <!doctype html public "-//w3c//dtd html 4.01 transitional//en"> <html> <head> <title>login</title> </head> <body> <h1>登录页面----${message }</h1> <img alt="" src="${pagecontext.request.contextpath }/pic.jpg"> <form:form action="${pagecontext.request.contextpath }/login" commandname="user" method="post"> 用户名:<form:input path="username" /> <form:errors path="username" cssclass="error" /> <br /> 密码:<form:password path="password" /> <form:errors path="password" cssclass="error" /> <br /> <form:button name="button">提交</form:button> </form:form> </body> </html>
- user.jsp
<%@ page language="java" import="java.util.*" pageencoding="utf-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!doctype html public "-//w3c//dtd html 4.01 transitional//en"> <html> <head> <title>用户列表</title> </head> <body> <h1>${message }</h1> <h1>用户列表--<a href="${pagecontext.request.contextpath }/logout" rel="external nofollow" >退出登录</a> </h1> <h2>权限列表</h2> <shiro:authenticated>用户已经登录显示此内容<br/></shiro:authenticated><br/> <shiro:hasrole name="manager">manager角色登录显示此内容<br/></shiro:hasrole> <shiro:hasrole name="admin">admin角色登录显示此内容<br/></shiro:hasrole> <shiro:hasrole name="normal">normal角色登录显示此内容<br/></shiro:hasrole><br/> <shiro:hasanyroles name="manager,admin">manager or admin 角色用户登录显示此内容<br/></shiro:hasanyroles><br/> <shiro:principal/>-显示当前登录用户名<br/><br/> <shiro:haspermission name="add">add权限用户显示此内容<br/></shiro:haspermission> <shiro:haspermission name="user:query">user:query权限用户显示此内容<br/></shiro:haspermission> <shiro:lackspermission name="user:query">不具有user:query权限的用户显示此内容 <br/></shiro:lackspermission> <br/>所有用户列表:<br/> <ul> <c:foreach items="${userlist }" var="user"> <li>用户名:${user.username }----密码:${user.password }----<a href="${pagecontext.request.contextpath }/user/edit/${user.id}" rel="external nofollow" >修改用户(测试根据不同用户可访问权限不同,本例tom无权限,jack有权限)</a></li> </c:foreach> </ul> <img alt="" src="${pagecontext.request.contextpath }/pic.jpg"> <script type="text/javascript" src="${pagecontext.request.contextpath }/webjarslocator/jquery/jquery.js"></script> </body> </html>
- user_edit.jsp
<%@ page language="java" import="java.util.*" pageencoding="utf-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <!doctype html public "-//w3c//dtd html 4.01 transitional//en"> <html> <head> <title>用户信息 - 修改</title> </head> <body> <h2>修改用户信息页面</h2><br/> <a href="${pagecontext.request.contextpath }/user" rel="external nofollow" >返回用户列表</a> </body> </html>
- 403.jsp
<%@ page language="java" import="java.util.*" pageencoding="utf-8"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %> <!doctype html public "-//w3c//dtd html 4.01 transitional//en"> <html> <head> <title>权限错误</title> </head> <body> <h1>对不起,您没有权限请求此连接!</h1> <img alt="" src="${pagecontext.request.contextpath }/pic.jpg"> </body> </html>
其中的pic.jpg 是测试代码遗留的,没有任何用处。关于 controller 和 jsp 页面本文不做介绍,关于spring boot 使用controller 和 jsp ,前面已经有文章介绍。
启动服务后访问 http://localhost:8080/myspringboot/user 会自动跳到 login 页面。
登录成功后,会打开 user 页面(关于默认登录页、成功成功url、没有权限url,在 shiroconfiguration 中已经配置)。
在 user 页面上,不同用户会根据权限不同显示不同的内容,下面的修改操作也已经有文字说明,更换账号测试便知。
然后我们在实际项目中:不但要在页面上控制不同权限隐藏或将某些操作设置为不可用状态,还要在实际上控制那个操作背后的请求是真的不可以使用的。(例如:页面上的修改按钮已经灰化了,而我知道了修改按钮正常情况下点击会触发的请求,此时我直接模拟这个修改请求,应当是没有权限的才对,这样才算是真正的控制了权限。)
附:
filter chain定义说明
1、一个url可以配置多个filter,使用逗号分隔
2、当设置多个过滤器时,全部验证通过,才视为通过
3、部分过滤器可指定参数,如perms,roles
shiro内置的filterchain
filter name | class |
---|---|
anon | org.apache.shiro.web.filter.authc.anonymousfilter |
authc | org.apache.shiro.web.filter.authc.formauthenticationfilter |
authcbasic | org.apache.shiro.web.filter.authc.basichttpauthenticationfilter |
perms | org.apache.shiro.web.filter.authz.permissionsauthorizationfilter |
port | org.apache.shiro.web.filter.authz.portfilter |
rest | org.apache.shiro.web.filter.authz.httpmethodpermissionfilter |
roles | org.apache.shiro.web.filter.authz.rolesauthorizationfilter |
ssl | org.apache.shiro.web.filter.authz.sslfilter |
user | org.apache.shiro.web.filter.authc.userfilter |
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论