Shiro 自定义登陆、授权、拦截器

Shiro

  • 登陆、授权、拦截
  • 按钮权限控制

一、目标

  • Maven+Spring+shiro
  • 自定义登陆、授权
  • 自定义拦截器
  • 加载数据库资源构建拦截链
使用总结:
1、需要设计的数据库:用户、角色、权限、资源
2、可以通过,角色,权限,两个拦截器同时确定是否能访问
3、角色与权限的关系, role1=permission1,permission2,多级的权限:sys:permission1,拥有高级权限同时用于低级权限。
4、perms["permission1"] 为权限
5、拦截器机制介绍了拦截角色还是权限
6、角色与权限 是两个概念
7、权限-资源,一对一。资源分为上下级,因此权限分为父权限,子权限。创建资源的时候,创建权限。权限里资源的别名
8、角色-权限,一对多。角色里权限的别名
9、按钮是通过权限来控制的
10、防止有父级资源可以访问,子级资源不能访问的情况,不适用 sys:add 权限写法

二、代码

1、Pom.xml

 1     <properties>
 2         <spring.version>4.3.4.RELEASE</spring.version>  3 </properties>  4 <dependency>  5 <groupId>junit</groupId>  6 <artifactId>junit</artifactId>  7 <version>4.9</version>  8 </dependency>  9 <dependency> 10 <groupId>commons-logging</groupId> 11 <artifactId>commons-logging</artifactId> 12 <version>1.1.3</version> 13 </dependency> 14 <dependency> 15 <groupId>org.apache.shiro</groupId> 16 <artifactId>shiro-core</artifactId> 17 <version>1.2.2</version> 18 </dependency> 19 <dependency> 20 <groupId>org.apache.shiro</groupId> 21 <artifactId>shiro-spring</artifactId> 22 <version>1.2.2</version> 23 </dependency> 24 <dependency> 25 <groupId>javax.servlet</groupId> 26 <artifactId>javax.servlet-api</artifactId> 27 <version>3.0.1</version> 28 <scope>provided</scope> 29 </dependency> 30 <dependency> 31 <groupId>org.springframework</groupId> 32 <artifactId>spring-web</artifactId> 33 <version>${spring.version}</version> 34 </dependency> 35 <dependency> 36 <groupId>org.apache.shiro</groupId> 37 <artifactId>shiro-ehcache</artifactId> 38 <version>1.2.2</version> 39 </dependency> 40 <dependency> 41 <groupId>org.springframework</groupId> 42 <artifactId>spring-context</artifactId> 43 <version>${spring.version}</version> 44 </dependency> 45 <dependency> 46 <groupId>org.apache.shiro</groupId> 47 <artifactId>shiro-web</artifactId> 48 <version>1.2.2</version> 49 </dependency> 50 <dependency> 51 <groupId>net.sf.ehcache</groupId> 52 <artifactId>ehcache</artifactId> 53 <version>2.10.1</version> 54 </dependency> 

2、web.xml

  Servlet拦截访问,使用注解更方便,需要删除项目中的servlet使用javax.servlet-api 3.0 包

 1 package com.cyd.shiro;
 2 
 3 import java.io.IOException;  4  5 import javax.servlet.ServletException;  6 import javax.servlet.annotation.WebServlet;  7 import javax.servlet.http.HttpServlet;  8 import javax.servlet.http.HttpServletRequest;  9 import javax.servlet.http.HttpServletResponse; 10 11 import org.apache.shiro.SecurityUtils; 12 import org.apache.shiro.authc.AuthenticationException; 13 import org.apache.shiro.authc.IncorrectCredentialsException; 14 import org.apache.shiro.authc.UnknownAccountException; 15 import org.apache.shiro.authc.UsernamePasswordToken; 16 import org.apache.shiro.subject.Subject; 17 import org.apache.shiro.web.util.SavedRequest; 18 import org.apache.shiro.web.util.WebUtils; 19 import org.junit.Test; 20 21 @WebServlet(name = "loginServlet", urlPatterns = "/loginController") 22 public class LoginServlet extends HttpServlet { 23  @Override 24 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 25 req.getRequestDispatcher("login.jsp").forward(req, resp); 26  } 27 28  @Override 29 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 30 System.out.println(LoginServlet.class.toString()); 31 String error = null; 32 String username = req.getParameter("username"); 33 String password = req.getParameter("password"); 34 Subject subject = SecurityUtils.getSubject(); 35 UsernamePasswordToken token = new UsernamePasswordToken(username, password); 36 try { 37  subject.login(token); 38 } catch (UnknownAccountException e) { 39 error = "用户名/密码错误"; 40 } catch (IncorrectCredentialsException e) { 41 error = "用户名/密码错误"; 42 } catch (AuthenticationException e) { 43 // 其他错误,比如锁定,如果想单独处理请单独catch处理 44 error = "其他错误:" + e.getMessage(); 45  } 46 if (error != null) {// 出错了,返回登录页面 47 req.setAttribute("error", error); 48 req.getRequestDispatcher("login.jsp").forward(req, resp); 49 } else {// 登录成功 50 //跳转到拦截登陆前的地址 51 SavedRequest request=WebUtils.getSavedRequest(req); 52 String url =request.getRequestURI(); 53 req.getRequestDispatcher(url.substring(url.lastIndexOf('/'))).forward(req, resp); 54  } 55  } 56 57 }

 

3、Spring-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.2.xsd"> <context:component-scan base-package="com.cyd.shiro.*"></context:component-scan> <!-- Shiro的Web过滤器 --> <bean id="shiroFilter" class="com.cyd.shiro.ExtendShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login.jsp" /> <!-- <property name="successUrl" value="/index.jsp" /> --> <property name="unauthorizedUrl" value="/unauthorized.jsp" /> <property name="filters"> <util:map> <!-- <entry key="onperms" value-ref="URLPermissionsFilter" /> --> <entry key="onrole" value-ref="ExtendRolesAuthorizationFilter" /> </util:map> </property> <property name="filterChainDefinitions"> <value> /unauthorized.jsp = anon /logoutController=anon /login.jsp=authc </value> </property> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm" /> <property name="cacheManager" ref="cacheManager" /> </bean> <!-- 自定义认证,授权 --> <bean id="myRealm" class="com.cyd.shiro.AdminRealm"></bean> <!-- 注册ehcache,不然每次访问都要登陆 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml" /> </bean> <!-- 自定义鉴权拦截器 --> <bean id="URLPermissionsFilter" class="com.cyd.shiro.URLPermissionsFilter" /> <bean id="ExtendRolesAuthorizationFilter" class="com.cyd.shiro.ExtendRolesAuthorizationFilter" /> </beans>

 

4、Ehcache.xml 缓存

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="600" timeToLiveSeconds="600" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> <!-- 内存中最多可以存储多少个数据 是否永久有效 空闲时间 存活时间 内存空间不够是否存储到磁盘 磁盘最大存储个数 服务器重启,磁盘数据是否需要 线程 淘汰策略(最近最少使用) --> </ehcache>

5、登陆Servlet

package com.cyd.shiro;

import java.io.IOException;

import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.apache.shiro.web.util.SavedRequest; import org.apache.shiro.web.util.WebUtils; @WebServlet(name = "loginServlet", urlPatterns = "/loginController") public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getRequestDispatcher("login.jsp").forward(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println(LoginServlet.class.toString()); String error = null; String username = req.getParameter("username"); String password = req.getParameter("password"); Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); } catch (UnknownAccountException e) { error = "用户名/密码错误"; } catch (IncorrectCredentialsException e) { error = "用户名/密码错误"; } catch (AuthenticationException e) { // 其他错误,比如锁定,如果想单独处理请单独catch处理 error = "其他错误:" + e.getMessage(); } if (error != null) {// 出错了,返回登录页面 req.setAttribute("error", error); req.getRequestDispatcher("login.jsp").forward(req, resp); } else {// 登录成功 //跳转到拦截登陆前的地址 SavedRequest request=WebUtils.getSavedRequest(req); String url =request.getRequestURI(); req.getRequestDispatcher(url.substring(url.lastIndexOf('/'))).forward(req, resp); } } }

 

6、自定义登陆、授权。

  根据需求自定义登陆异常。从数据库查询出当前用户拥有的权限并授权

  

 1 package com.cyd.shiro;
 2 
 3 import java.util.HashSet;  4 import java.util.LinkedList;  5 import java.util.List;  6 import java.util.Set;  7  8 import org.apache.shiro.authc.AuthenticationException;  9 import org.apache.shiro.authc.AuthenticationInfo; 10 import org.apache.shiro.authc.AuthenticationToken; 11 import org.apache.shiro.authc.SimpleAuthenticationInfo; 12 import org.apache.shiro.authc.UnknownAccountException; 13 import org.apache.shiro.authz.AuthorizationInfo; 14 import org.apache.shiro.authz.SimpleAuthorizationInfo; 15 import org.apache.shiro.realm.AuthorizingRealm; 16 import org.apache.shiro.subject.PrincipalCollection; 17 import org.springframework.beans.factory.annotation.Autowired; 18 19 import com.cyd.helloworld.SysRoles; 20 import com.cyd.helloworld.SysUsers; 21 import com.cyd.shiro.admin.SysUsersService; 22 23 public class AdminRealm extends AuthorizingRealm { 24 25  @Autowired 26 private SysUsersService sysusersservice; 27 // 认证登陆 28  @Override 29 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 30 System.out.println("do doGetAuthenticationInfo"); 31 String username = (String) token.getPrincipal(); 32 SysUsers user = sysusersservice.getSysUsers(username); 33 if (user == null) { 34 throw new UnknownAccountException();// 没找到帐号 35  } 36 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUserName(), // 用户名 37 user.getPassWorld(), // 密码 38 getName() // realm name 39  ); 40 return authenticationInfo; 41  } 42 43 // 用户授权 44  @Override 45 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { 46 System.out.println("do doGetAuthorizationInfo"); 47 String username = (String)principals.getPrimaryPrincipal(); 48 SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); 49 //从数据库加载当前用户的角色,例如:[admin] 50 authorizationInfo.setRoles(new HashSet<String>(sysusersservice.getSysRoles(username))); 51 //从数据库加载当前用户可以访问的资源,例如:[index.jsp, abc.jsp] 52 authorizationInfo.setStringPermissions(new HashSet<String>(sysusersservice.getSysResource(username))); 53 54 return authorizationInfo; 55  } 56 }

 

7、自定义拦截器。

  重写拦截器是因为shiro 验证是否有权限访问是需要当前用户拥有拦截器链的所有权限。一般需求只需要拥有部分权限即可。

       角色验证拦截,hasRole和hasAllRoles 验证是否有权限。

 1 package com.cyd.shiro;
 2 
 3 import java.io.IOException;  4 import java.util.Set;  5  6 import javax.servlet.ServletRequest;  7 import javax.servlet.ServletResponse;  8  9 import org.apache.shiro.subject.Subject; 10 import org.apache.shiro.util.CollectionUtils; 11 import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter; 12 13 /** 14  * 通过角色验证权限 15  * @author chenyd 16  * 2017年11月21日 17 */ 18 public class ExtendRolesAuthorizationFilter extends RolesAuthorizationFilter{ 19 20  @Override 21 public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { 22 23 System.out.println(ExtendRolesAuthorizationFilter.class.toString()); 24 Subject subject = getSubject(request, response); 25 String[] rolesArray = (String[]) mappedValue; 26 27 if (rolesArray == null || rolesArray.length == 0) { 28 //no roles specified, so nothing to check - allow access. 29 return true; 30  } 31 //AbstractFilter 32 Set<String> roles = CollectionUtils.asSet(rolesArray); 33 34 boolean flag=false; 35 for(String role: roles){ 36 if(subject.hasRole(role)){ 37 flag=true; 38 break; 39  } 40  } 41 return flag; 42  } 43 }

       url拦截校验,isPermitted和isPermittedAll验证是否有权限访问,

 1 package com.cyd.shiro;
 2 
 3 import java.io.IOException;  4  5 import javax.servlet.ServletRequest;  6 import javax.servlet.ServletResponse;  7 import javax.servlet.http.HttpServletRequest;  8  9 import org.apache.shiro.subject.Subject; 10 import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter; 11 /** 12  * 通过字符串验证权限 13  * @author chenyd 14  * 2017年11月21日 15 */ 16 public class URLPermissionsFilter extends PermissionsAuthorizationFilter { 17 18 /** 19  * mappedValue 访问该url时需要的权限 20  * subject.isPermitted 判断访问的用户是否拥有mappedValue权限 21  * 重写拦截器,只要符合配置的一个权限,即可通过 22 */ 23  @Override 24 public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) 25 throws IOException { 26 System.out.println(URLPermissionsFilter.class.toString()); 27 Subject subject = getSubject(request, response); 28 // DefaultFilterChainManager 29 // PathMatchingFilterChainResolver 30 String[] perms = (String[]) mappedValue; 31 boolean isPermitted = false; 32 if (perms != null && perms.length > 0) { 33 for (String str : perms) { 34 if (subject.isPermitted(str)) { 35 isPermitted = true; 36  } 37  } 38  } 39 40 return isPermitted; 41  } 42 }

8、加载数据库资源构建拦截器链

 1 package com.cyd.shiro;
 2 
 3 import java.util.Map;  4  5 import org.apache.shiro.config.Ini;  6 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;  7 import org.apache.shiro.util.CollectionUtils;  8 import org.apache.shiro.web.config.IniFilterChainResolverFactory;  9 import org.springframework.beans.factory.annotation.Autowired; 10 11 import com.cyd.shiro.admin.SysUsersService; 12 13 public class ExtendShiroFilterFactoryBean extends ShiroFilterFactoryBean{ 14 15  @Autowired 16 private SysUsersService sysusersservice; 17 //PathMatchingFilter 18  @Override 19 public void setFilterChainDefinitions(String definitions) { 20 //数据库中获取权限,{/index.jsp=authc,onrole["admin2","admin"], /abc.jsp=authc,onrole["admin2","admin"]} 21 Map<String, String> otherChains = sysusersservice.getFilterChain(); 22 Ini ini = new Ini(); 23  ini.load(definitions); 24 Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS); 25 if (CollectionUtils.isEmpty(section)) { 26 section = ini.getSection(Ini.DEFAULT_SECTION_NAME); 27  } 28  section.putAll(otherChains); 29  setFilterChainDefinitionMap(section); 30  } 31 32 }

 

三、  学习笔记

1、INI文件配置

[users]  #提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2  

zhang=123,admin

[roles] #提供了角色及权限之间关系的配置,角色=权限1,权限2 admin=index.jsp [urls] #配置拦截器链,/** 为拦截器链名称(filterChain),authc,roles[admin],perms["index.jsp"]拦截器列表名 /login.jsp=anon /loginController=anon /unauthorized.jsp=anon /**=authc,roles[admin],perms["index.jsp"] 

2、拦截器链

  Shiro的所有拦截器链名定义在源码DefaultFilter中。

anon            例子/admins/**=anon 没有参数,表示可以匿名使用。 
authc例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数  
roles

 例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,  

 并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],  

 每个参数通过才算通过,相当于hasAllRoles()方法。  

perms

例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,  

例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,  

想当于isPermitedAll()方法。

rest

例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,  

 其中method为post,get,delete等。

port

例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,  其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。

authcBasic                                 

例如/admins/user/**=authcBasic没有参数表示httpBasic认证

ssl

例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https

user

例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查  

注:anon,authcBasic,auchc,user是认证过滤器,  

  perms,roles,ssl,rest,port是授权过滤器  

3、拦截器链源码类关系图

  

①   NameableFilter有一个name属性,定义每一个filter的名字。

②   OncePerRequestFilter保证客户端请求后该filter的doFilter只会执行一次。

  doFilterInternal非常重要,在shiro整个filter体系中的核心方法及实质入口。另外,shiro是通过在request中设置一个该filter特定的属性值来保证该filter只会执行一次的。

③   AdviceFilter中主要是对doFilterInternal做了更细致的切分。

  springmvc中的Interceptor,doFilterInternal会先调用preHandle做一些前置判断,如果返回false则filter链不继续往下执行,

④   AccessControlFilter中的对onPreHandle方法做了进一步细化。

  isAccessAllowed方法和onAccessDenied方法达到控制效果。这两个方法都是抽象方法,由子类去实现。到这一层应该明白。isAccessAllowed和onAccessDenied方法会影响到onPreHandle方法,而onPreHandle方法会影响到preHandle方法,而preHandle方法会达到控制filter链是否执行下去的效果。所以如果正在执行的filter中isAccessAllowed和onAccessDenied都返回false,则整个filter控制链都将结束,不会到达目标方法(客户端请求的接口),而是直接跳转到某个页面(由filter定义的,将会在authc中看到)。

⑤   FormAuthenticationFiltershiro提供的登录的filter,

  saveRequestAndRedirectToLogin保存request并拦截到登陆页面,登陆成功后可从WebUtils.getSavedRequest(req);中取出。

四、未实现的功能

  • 动态URL权限控制。当修改权限时,重新加载拦截器链。
  • 密码加密
  • 记住我
  • 在线人数控制
  • 集成验证码 

五、参考链接

 

转载于:https://www.cnblogs.com/litblank/p/7883167.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值