springboot(六)-使用shiro
前提
写之前纠结了一番,这一节放在shiro里面还是springboot里面。后来想了下,还是放springboot里吧,因为这里没有shiro的新东西,只有springboot添加了新东西的使用。
但是这里还是基于shiro那边的代码已经内容来写的,对权限和角色做控制。
pom.xml
按照我个人习惯,新建一个boot工程,首先当然是先配置一下基本的pom.xml文件啦。后期再开发的时候,需要什么再加。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 6 <groupId>com.how2java</groupId> 7 <artifactId>shiro</artifactId> 8 <version>0.0.1-SNAPSHOT</version> 9 <name>shiro</name> 10 <description>shiro</description> 11 <packaging>war</packaging> 12 13 <parent> 14 <groupId>org.springframework.boot</groupId> 15 <artifactId>spring-boot-starter-parent</artifactId> 16 <version>1.5.9.RELEASE</version> 17 </parent> 18 19 <dependencies> 20 <dependency> 21 <groupId>org.springframework.boot</groupId> 22 <artifactId>spring-boot-starter-web</artifactId> 23 </dependency> 24 <dependency> 25 <groupId>org.springframework.boot</groupId> 26 <artifactId>spring-boot-starter-tomcat</artifactId> 27 28 </dependency> 29 <dependency> 30 <groupId>junit</groupId> 31 <artifactId>junit</artifactId> 32 <version>3.8.1</version> 33 <scope>test</scope> 34 </dependency> 35 <!-- servlet依赖. --> 36 <dependency> 37 <groupId>javax.servlet</groupId> 38 <artifactId>javax.servlet-api</artifactId> 39 40 </dependency> 41 <dependency> 42 <groupId>javax.servlet</groupId> 43 <artifactId>jstl</artifactId> 44 </dependency> 45 <!-- tomcat的支持.--> 46 <dependency> 47 <groupId>org.apache.tomcat.embed</groupId> 48 <artifactId>tomcat-embed-jasper</artifactId> 49 50 </dependency> 51 <dependency> 52 <groupId>org.springframework.boot</groupId> 53 <artifactId>spring-boot-devtools</artifactId> 54 <optional>true</optional> <!-- 这个需要为 true 热部署才有效 --> 55 </dependency> 56 57 <!-- mybatis --> 58 <dependency> 59 <groupId>org.mybatis.spring.boot</groupId> 60 <artifactId>mybatis-spring-boot-starter</artifactId> 61 <version>1.1.1</version> 62 </dependency> 63 64 <!-- mysql --> 65 <dependency> 66 <groupId>mysql</groupId> 67 <artifactId>mysql-connector-java</artifactId> 68 <version>5.1.21</version> 69 </dependency> 70 71 <!-- mybatis 逆向工程 --> 72 <dependency> 73 <groupId>org.mybatis.generator</groupId> 74 <artifactId>mybatis-generator-core</artifactId> 75 <version>1.3.5</version> 76 </dependency> 77 78 <!-- shiro --> 79 <dependency> 80 <groupId>org.apache.shiro</groupId> 81 <artifactId>shiro-spring</artifactId> 82 <version>1.3.2</version> 83 </dependency> 84 85 <!-- shiro-web --> 86 <dependency> 87 <groupId>org.apache.shiro</groupId> 88 <artifactId>shiro-web</artifactId> 89 <version>1.3.2</version> 90 </dependency> 91 </dependencies> 92 93 <properties> 94 <java.version>1.8</java.version> 95 </properties> 96 97 <build> 98 <plugins> 99 <plugin> 100 <groupId>org.springframework.boot</groupId> 101 <artifactId>spring-boot-maven-plugin</artifactId> 102 </plugin> 103 </plugins> 104 </build> 105 106 </project>
application.properties
然后,配置application.properties文件或者application.yml文件,我是喜欢yml风格的。
server:
port: 8084
spring:
mvc:
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
datasource:
url: jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8
username: root
password: 1234
driver-class-name: com.mysql.jdbc.Driver
ShiroConfiguration
springboot最大的特点是啥呀?没有配置文件呐,但是我们使用别人的工具,人家需要添加配置文件,怎么办?在我看来,这是springboot的一个缺点吧,它居然是写配置类,感觉又回到过去了。因为配置文件的方式可读性要强。而且配置文件的方式就是由配置类发展过来的。嗯嗯嗯~~~~
ShiroConfiguration 这个类就是shiro配置类,类名怎么命名都可以,就是要用@Configuration 注解表示它是一个配置类
@Beanpublic static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}
这种写法就和 applicationContext-shiro.xml 中的
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
是同一个作用,只要用@Bean 注解了,就表示是被spring管理起来的对象了。
这个类里面就声明了SecurityManager,DatabaseRealm, HashedCredentialsMatcher,ShiroFilterFactoryBean 等等东西,只是写法变化了,其实和配置文件里是一样的。
需要注意一点,URLPathMatchingFilter 并没有用@Bean管理起来。 原因是Shiro的bug, 这个也是过滤器,ShiroFilterFactoryBean 也是过滤器,当他们都出现的时候,默认的什么anno,authc,logout过滤器就失效了。所以不能把他声明为@Bean。
public URLPathMatchingFilter getURLPathMatchingFilter() {return new URLPathMatchingFilter();}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.how2java.shiro; 2 3 import java.util.HashMap; 4 import java.util.LinkedHashMap; 5 import java.util.Map; 6 7 import javax.servlet.Filter; 8 9 import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 10 import org.apache.shiro.mgt.SecurityManager; 11 import org.apache.shiro.spring.LifecycleBeanPostProcessor; 12 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; 13 import org.apache.shiro.spring.web.ShiroFilterFactoryBean; 14 import org.apache.shiro.web.mgt.DefaultWebSecurityManager; 15 import org.springframework.context.annotation.Bean; 16 import org.springframework.context.annotation.Configuration; 17 18 import com.how2java.filter.URLPathMatchingFilter; 19 import com.how2java.realm.DatabaseRealm; 20 21 @Configuration 22 public class ShiroConfiguration { 23 @Bean 24 public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { 25 return new LifecycleBeanPostProcessor(); 26 } 27 28 /** 29 * ShiroFilterFactoryBean 处理拦截资源文件问题。 30 * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在 31 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager 32 * 33 Filter Chain定义说明 34 1、一个URL可以配置多个Filter,使用逗号分隔 35 2、当设置多个过滤器时,全部验证通过,才视为通过 36 3、部分过滤器可指定参数,如perms,roles 37 * 38 */ 39 @Bean 40 public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){ 41 System.out.println("ShiroConfiguration.shirFilter()"); 42 ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); 43 44 // 必须设置 SecurityManager 45 shiroFilterFactoryBean.setSecurityManager(securityManager); 46 // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 47 shiroFilterFactoryBean.setLoginUrl("/login"); 48 // 登录成功后要跳转的链接 49 shiroFilterFactoryBean.setSuccessUrl("/index"); 50 //未授权界面; 51 shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); 52 //拦截器. 53 Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); 54 //自定义拦截器 55 Map<String, Filter> customisedFilter = new HashMap<>(); 56 customisedFilter.put("url", getURLPathMatchingFilter()); 57 58 //配置映射关系 59 filterChainDefinitionMap.put("/login", "anon"); 60 filterChainDefinitionMap.put("/index", "anon"); 61 filterChainDefinitionMap.put("/static/**", "anon"); 62 filterChainDefinitionMap.put("/config/**", "anon"); 63 filterChainDefinitionMap.put("/doLogout", "logout");; 64 filterChainDefinitionMap.put("/**", "url"); 65 shiroFilterFactoryBean.setFilters(customisedFilter); 66 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); 67 return shiroFilterFactoryBean; 68 } 69 70 public URLPathMatchingFilter getURLPathMatchingFilter() { 71 return new URLPathMatchingFilter(); 72 } 73 74 @Bean 75 public SecurityManager securityManager(){ 76 DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); 77 //设置realm. 78 securityManager.setRealm(getDatabaseRealm()); 79 return securityManager; 80 } 81 82 @Bean 83 public DatabaseRealm getDatabaseRealm(){ 84 DatabaseRealm myShiroRealm = new DatabaseRealm(); 85 myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); 86 return myShiroRealm; 87 } 88 89 /** 90 * 凭证匹配器 91 * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 92 * 所以我们需要修改下doGetAuthenticationInfo中的代码; 93 * ) 94 * @return 95 */ 96 @Bean 97 public HashedCredentialsMatcher hashedCredentialsMatcher(){ 98 HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); 99 100 hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法; 101 hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5("")); 102 103 return hashedCredentialsMatcher; 104 } 105 106 /** 107 * 开启shiro aop注解支持. 108 * 使用代理方式;所以需要开启代码支持; 109 * @param securityManager 110 * @return 111 */ 112 @Bean 113 public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ 114 AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); 115 authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); 116 return authorizationAttributeSourceAdvisor; 117 } 118 }
SpringContextUtils.java
继续上面的知识点,因为URLPathMatchingFilter 没有被声明为@Bean, 那么换句话说 URLPathMatchingFilter 就没有被Spring管理起来,那么也就无法在里面注入 PermissionService类了。
但是在业务上URLPathMatchingFilter 里面又必须使用PermissionService类,怎么办呢? 就借助SpringContextUtils 这个工具类,来获取PermissionService的实例。
这里提供工具类,下个步骤讲解如何使用这个工具类。
package com.how2java.util; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringContextUtils implements ApplicationContextAware { private static ApplicationContext context; public void setApplicationContext(ApplicationContext context) throws BeansException { SpringContextUtils.context = context; } public static ApplicationContext getContext(){ return context; } }
URLPathMatchingFilter.java
因为前面两个步骤的原因,既不能把 URLPathMatchingFilter.java 作为@Bean管理起来,又需要在里面使用 PermissionService,所以就用上一步的工具类 SpringContextUtils.java,通过如下方式获取 PermissionService了:
permissionService = SpringContextUtils.getContext().getBean(PermissionService.class);
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 package com.how2java.filter; 2 3 import java.util.Arrays; 4 import java.util.Set; 5 6 import javax.servlet.ServletRequest; 7 import javax.servlet.ServletResponse; 8 9 import org.apache.shiro.SecurityUtils; 10 import org.apache.shiro.authz.UnauthorizedException; 11 import org.apache.shiro.subject.Subject; 12 import org.apache.shiro.web.filter.PathMatchingFilter; 13 import org.apache.shiro.web.util.WebUtils; 14 import org.springframework.beans.factory.annotation.Autowired; 15 16 import com.how2java.service.PermissionService; 17 import com.how2java.service.impl.PermissionServiceImpl; 18 import com.how2java.util.SpringContextUtils; 19 20 public class URLPathMatchingFilter extends PathMatchingFilter { 21 @Autowired 22 PermissionService permissionService; 23 24 @Override 25 protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) 26 throws Exception { 27 if(null==permissionService) 28 permissionService = SpringContextUtils.getContext().getBean(PermissionService.class); 29 30 String requestURI = getPathWithinApplication(request); 31 System.out.println("requestURI:" + requestURI); 32 33 Subject subject = SecurityUtils.getSubject(); 34 // 如果没有登录,就跳转到登录页面 35 if (!subject.isAuthenticated()) { 36 WebUtils.issueRedirect(request, response, "/login"); 37 return false; 38 } 39 40 // 看看这个路径权限里有没有维护,如果没有维护,一律放行(也可以改为一律不放行) 41 System.out.println("permissionService:"+permissionService); 42 boolean needInterceptor = permissionService.needInterceptor(requestURI); 43 if (!needInterceptor) { 44 return true; 45 } else { 46 boolean hasPermission = false; 47 String userName = subject.getPrincipal().toString(); 48 Set<String> permissionUrls = permissionService.listPermissionURLs(userName); 49 for (String url : permissionUrls) { 50 // 这就表示当前用户有这个权限 51 if (url.equals(requestURI)) { 52 hasPermission = true; 53 break; 54 } 55 } 56 57 if (hasPermission) 58 return true; 59 else { 60 UnauthorizedException ex = new UnauthorizedException("当前用户没有访问路径 " + requestURI + " 的权限"); 61 62 subject.getSession().setAttribute("ex", ex); 63 64 WebUtils.issueRedirect(request, response, "/unauthorized"); 65 return false; 66 } 67 68 } 69 70 } 71 }
测试
启动该springboot工程,大家试下效果吧。
代码下载地址:https://gitee.com/fengyuduke/my_open_resources/blob/master/springboot-shiro.zip