SpringBoot集成Shiro进行权限控制和管理的示例

时间:2022-11-02 12:35:40

shiro

apache shiro 是一个轻量级的身份验证与授权框架,与spring security 相比较,简单易用,灵活性高,springboot本身是提供了对security的支持,毕竟是自家的东西。springboot暂时没有集成shiro,这得自己配。

1 . 添加依赖

?
1
2
3
4
5
6
7
8
9
10
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring</artifactId>
  <version>1.2.5</version>
</dependency>
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-ehcache</artifactId>
  <version>1.2.5</version>
</dependency>

2 . 编写Shiro配置类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
package com.xbz.web.system.config; 
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.session.mgt.eis.MemorySessionDAO;
import org.apache.shiro.session.mgt.eis.SessionDAO;
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.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn; 
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
 * shiro配置类
 * ApacheShiro核心通过Filter来实现权限控制和拦截 , 就好像SpringMVC通过DispachServlet来主控制请求分发一样 .
 * 既然是使用Filter , 即是通过URL规则来进行过滤和权限校验 , 所以我们需要定义一系列关于URL的规则和访问权限
 */
@Configuration
public class ShiroConfiguration {
  /**
   * DefaultAdvisorAutoProxyCreator , Spring的一个bean , 由Advisor决定对哪些类的方法进行AOP代理 .
   */
  @Bean
  @ConditionalOnMissingBean
  public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
    DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
    defaultAAP.setProxyTargetClass(true);
    return defaultAAP;
  }
 
  /**
   * ShiroFilterFactoryBean : 为了生成ShiroFilter , 处理拦截资源文件问题 .
   * 它主要保持了三项数据 , securityManager , filters , filterChainDefinitionManager .
   * 注意 : 单独一个ShiroFilterFactoryBean配置是或报错的 , 因为在初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
   *
   * FilterChain定义说明
   * 1 . 一个URL可以配置多个Filter , 使用逗号分隔
   * 2 . 当设置多个过滤器时 , 全部验证通过 , 才视为通过
   * 3 . 部分过滤器可指定参数 , 如perms , roles
   *
   */
  @Bean
  public ShiroFilterFactoryBean shiroFilterFactoryBean() {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager());
    shiroFilterFactoryBean.setLoginUrl("/login");//不设置默认找web工程根目录下的login.jsp页面
    shiroFilterFactoryBean.setSuccessUrl("/index");//登录成功之后要跳转的连接
    shiroFilterFactoryBean.setUnauthorizedUrl("/403");//未授权跳转页面
    /* //自定义拦截器 , 多个filter的设置 */
//    Map<String, Filter> filters = new LinkedHashMap<>();
//    LogoutFilter logoutFilter = new LogoutFilter();//限制同一帐号同时在线的个数。或单点登录等
//    logoutFilter.setRedirectUrl("/login");
//    filters.put("logout",null);
//    shiroFilterFactoryBean.setFilters(filters); 
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    //filterChainDefinitionManager必须是LinkedHashMap因为它必须保证有序
    filterChainDefinitionMap.put("/css/**", "anon");//静态资源不要求权限 , 若有其他目录下文件(如js,img等)也依此设置
    filterChainDefinitionMap.put("/", "anon");
    filterChainDefinitionMap.put("/login", "anon");//配置不需要权限访问的部分url
    filterChainDefinitionMap.put("/logout", "logout");
    filterChainDefinitionMap.put("/user/**", "authc,roles[ROLE_USER]");//用户为ROLE_USER 角色可以访问 . 由用户角色控制用户行为 . 
    filterChainDefinitionMap.put("/events/**", "authc,roles[ROLE_ADMIN]");
    //    filterChainDefinitionMap.put("/user/edit/**", "authc,perms[user:edit]");// 这里为了测试 , 固定写死的值 , 也可以从数据库或其他配置中读取 , 此处是用权限控制
 
    filterChainDefinitionMap.put("/**", "authc");//需要登录访问的资源 , 一般将/**放在最下边
 
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return shiroFilterFactoryBean;
  }
 
  //region Cookie及Session
  // ==================== Cookie及Session管理 begin ====================
  private static final String COOKIE_NAME = "rememberMe";
  /** cookie对象管理 */
  public SimpleCookie rememberMeCookie(){
    //这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
    SimpleCookie simpleCookie = new SimpleCookie(COOKIE_NAME);
    simpleCookie.setMaxAge(604800);//记住我cookie生效时间7天 ,单位秒
    return simpleCookie;
  }
 
  /** cookie管理对象 : 记住我功能 */
  public CookieRememberMeManager rememberMeManager(){
    CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
    cookieRememberMeManager.setCookie(rememberMeCookie());
    cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
    return cookieRememberMeManager;
  }
 
  @Bean
  SessionDAO sessionDAO() {
    return new MemorySessionDAO();
  }
 
  @Bean
  public SessionManager sessionManager() {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
    Collection<SessionListener> listeners = new ArrayList<>();
    listeners.add(new BDSessionListener());
    sessionManager.setSessionListeners(listeners);
    sessionManager.setSessionDAO(sessionDAO());
    return sessionManager;
  }
  // ==================== Cookie及Session管理 end ====================
  //endregion
 
  /**
   * SecurityManager : 核心安全事务管理器 , 权限管理
   * 这个类组合了登陆 , 登出 , 权限 , session的处理 . 是个比较重要的类 .
   */
  @Bean(name = "securityManager")
  public DefaultWebSecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    securityManager.setRealm(shiroRealm());
    securityManager.setCacheManager(ehCacheManager());////用户授权/认证信息Cache, 采用EhCache 缓存
 
    // 自定义session管理 使用redis
    securityManager.setSessionManager(sessionManager());
 
    //注入记住我管理器;
    securityManager.setRememberMeManager(rememberMeManager());
    return securityManager;
  }
 
  /**
   * ShiroRealm , 这是个自定义的认证类 , 继承自AuthorizingRealm ,
   * 负责用户的认证和权限的处理 , 可以参考JdbcRealm的实现 .
   */
  @Bean
  @DependsOn("lifecycleBeanPostProcessor")
  public ShiroRealm shiroRealm(CredentialsMatcher matcher) {
    ShiroRealm realm = new ShiroRealm();
    realm.setCredentialsMatcher(matcher);//密码校验实现
    return realm;
  }
 
  /**
   * EhCacheManager , 缓存管理
   * 用户登陆成功后 , 把用户信息和权限信息缓存起来 , 然后每次用户请求时 , 放入用户的session中 , 如果不设置这个bean , 每个请求都会查询一次数据库 .
   */
  @Bean
  @DependsOn("lifecycleBeanPostProcessor")
  public EhCacheManager ehCacheManager() {
    EhCacheManager em = new EhCacheManager();
    em.setCacheManagerConfigFile("classpath:config/ehcache.xml");//配置文件路径
    return em;
  }
 
  /**
   * LifecycleBeanPostProcessor , 这是个DestructionAwareBeanPostProcessor的子类 ,
   * 负责org.apache.shiro.util.Initializable类型bean的生命周期的 , 初始化和销毁 .
   * 主要是AuthorizingRealm类的子类 , 以及EhCacheManager类 .
   */
  @Bean(name = "lifecycleBeanPostProcessor")
  public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
    return new LifecycleBeanPostProcessor();
  }
 
  /**
   * HashedCredentialsMatcher , 这个类是为了对密码进行编码的 ,
   * 防止密码在数据库里明码保存 , 当然在登陆认证的时候 ,
   * 这个类也负责对form里输入的密码进行编码
   * 处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher
   */
  @Bean(name = "hashedCredentialsMatcher")
  public HashedCredentialsMatcher hashedCredentialsMatcher() {
    HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
    credentialsMatcher.setHashAlgorithmName("MD5");//指定加密方式方式,也可以在这里加入缓存,当用户超过五次登陆错误就锁定该用户禁止不断尝试登陆
    credentialsMatcher.setHashIterations(2);
    credentialsMatcher.setStoredCredentialsHexEncoded(true);
    return credentialsMatcher;
  }
 
  /**
   * AuthorizationAttributeSourceAdvisor , shiro里实现的Advisor类 ,
   * 内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法 .
   */
  @Bean
  public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
    AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
    advisor.setSecurityManager(securityManager());
    return advisor;
  }
 
  //thymeleaf模板引擎和shiro整合时使用
  @Bean
  public ShiroDialect shiroDialect() {
    return new ShiroDialect();
  }
}

3 . 自定义Realm验证类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package com.yiyun.web.system.config; 
import com.yiyun.dao.master.UserDao;
import com.yiyun.domain.UserDO;
import com.yiyun.web.common.utils.ShiroUtils;
import com.yiyun.web.system.service.MenuService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired; 
import java.util.*;
 
/**
 * 获取用户的角色和权限信息
 */
public class ShiroRealm extends AuthorizingRealm {
 
  // 一般这里都写的是servic
  @Autowired
  private UserDao userMapper;
  @Autowired
  private MenuService menuService;
 
  /**
   * 登录认证 一般情况下 , 登录成功之后就给当前用户进行权限赋予了
   * 根据用户的权限信息做授权判断,这一步是以doGetAuthenticationInfo为基础的,只有在有用户信息后才能根据用户的角色和授权信息做判断是否给用户授权,因此这里的Roles和Permissions是用户的两个重点判断依据
   * @param authenticationToken
   * @return
   * @throws AuthenticationException
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
    UserDo user = userMapper.findByName(token.getUsername());//查出是否有此用户
 
    if(user != null){
      // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
      List<URole> rlist = uRoleDao.findRoleByUid(user.getId());//获取用户角色
      List<UPermission> plist = uPermissionDao.findPermissionByUid(user.getId());//获取用户权限
      List<String> roleStrlist=new ArrayList<String>();////用户的角色集合
      List<String> perminsStrlist=new ArrayList<String>();//用户的权限集合
      for (URole role : rlist) {
        roleStrlist.add(role.getName());
      }
      for (UPermission uPermission : plist) {
        perminsStrlist.add(uPermission.getName());
      }
      user.setRoleStrlist(roleStrlist);
      user.setPerminsStrlist(perminsStrlist);
      Session session = SecurityUtils.getSubject().getSession();
      session.setAttribute("user", user);//成功则放入session
      // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
      return new SimpleAuthenticationInfo(user, user.getPassword(), getName());
    }
    return null;
  }
 
  /**
   * 权限认证
   * 获取用户的权限信息,这是为下一步的授权做判断,获取当前用户的角色和这些角色所拥有的权限信息
   * @param principalCollection
   * @return
   */
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    //获取当前登录输入的用户名,等价于(String) principalCollection.fromRealm(getName()).iterator().next();
//    String loginName = (String) super.getAvailablePrincipal(principalCollection);
    UserDo user = (UserDo) principalCollection.getPrimaryPrincipal();
//    //到数据库查是否有此对象
//    User user = null;// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
//    user = userMapper.findByName(loginName);
    if (user != null) {
      //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
      SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
      //用户的角色集合
      info.addRoles(user.getRoleStrlist());
      //用户的权限集合
      info.addStringPermissions(user.getPerminsStrlist());
 
      return info;
    }
    // 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址
    return null;
  }
}

4 . 最后看一下ehcache的配置文件

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
  updateCheck="false">
  <diskStore path="java.io.tmpdir/Tmp_EhCache" /> 
  <!--
    name:缓存名称。
    maxElementsInMemory:缓存最大数目
    maxElementsOnDisk:硬盘最大缓存个数。
    eternal:对象是否永久有效,一但设置了,timeout将不起作用。
    overflowToDisk:是否保存到磁盘,当系统当机时
    timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
    timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
    diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
    diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
    diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
    memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
    clearOnFlush:内存数量最大时是否清除。
     memoryStoreEvictionPolicy:
      Ehcache的三种清空策略;
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
  -->
 
  <defaultCache eternal="false" maxElementsInMemory="1000"
    overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"
    timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU" />
  <!-- 登录记录缓存锁定10分钟 -->
  <cache name="passwordRetryCache"
      maxEntriesLocalHeap="2000"
      eternal="false"
      timeToIdleSeconds="3600"
      timeToLiveSeconds="0"
      overflowToDisk="false"
      statistics="true">
  </cache>
</ehcache>

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:http://blog.csdn.net/xingbaozhen1210/article/details/79470255