springboot(六)-使用shiro

前提

写之前纠结了一番,这一节放在shiro里面还是springboot里面。后来想了下,还是放springboot里吧,因为这里没有shiro的新东西,只有springboot添加了新东西的使用。

但是这里还是基于shiro那边的代码已经内容来写的,对权限和角色做控制。

pom.xml

按照我个人习惯,新建一个boot工程,首先当然是先配置一下基本的pom.xml文件啦。后期再开发的时候,需要什么再加。

  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 注解表示它是一个配置类

@Bean
public 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();
}
  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);
 
 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

原文地址:https://www.cnblogs.com/fengyuduke/p/10496299.html