SpringBoot搭建基于Apache Shiro的权限管理功能

  1. 云栖社区>
  2. 博客>
  3. 正文

SpringBoot搭建基于Apache Shiro的权限管理功能

nbfujx 2017-11-02 18:57:00 浏览953
展开阅读全文

Shiro 是什么

Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能: 

  • 认证 - 用户身份识别,常被称为用户“登录”;
  • 授权 - 访问控制;
  • 密码加密 - 保护或隐藏数据防止被偷窥;
  • 会话管理 - 每用户相关的时间敏感的状态。

对于任何一个应用程序,Shiro都可以提供全面的安全管理服务。并且相对于其他安全框架,Shiro要简单的多。

Shiro的架构介绍 

首先,来了解一下Shiro的三个核心组件:Subject, SecurityManager 和 Realms. 如下图: 
 
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。 
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。 
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。 
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。 
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。 
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
Shiro完整架构图: 
 
除前文所讲Subject、SecurityManager 、Realm三个核心组件外,Shiro主要组件还包括: 
Authenticator :认证就是核实用户身份的过程。这个过程的常见例子是大家都熟悉的“用户/密码”组合。多数用户在登录软件系统时,通常提供自己的用户名(当事人)和支持他们的密码(证书)。如果存储在系统中的密码(或密码表示)与用户提供的匹配,他们就被认为通过认证。 
Authorizer :授权实质上就是访问控制 - 控制用户能够访问应用中的哪些内容,比如资源、Web页面等等。 
SessionManager :在安全框架领域,Apache Shiro提供了一些独特的东西:可在任何应用或架构层一致地使用Session API。即,Shiro为任何应用提供了一个会话编程范式 - 从小型后台独立应用到大型集群Web应用。这意味着,那些希望使用会话的应用开发者,不必被迫使用Servlet或EJB容器了。或者,如果正在使用这些容器,开发者现在也可以选择使用在任何层统一一致的会话API,取代Servlet或EJB机制。 
CacheManager :对Shiro的其他组件提供缓存支持。 

Shiro的使用

首先搭建一个springBoot项目

http://www.cnblogs.com/nbfujx/p/7694768.html

Maven Plugin添加Shiro相关jar包

 1  <!-- shiro   -->
 2         <dependency>
 3             <groupId>org.apache.shiro</groupId>
 4             <artifactId>shiro-core</artifactId>
 5             <version>${shiro.version}</version>
 6         </dependency>
 7         <dependency>
 8             <groupId>org.apache.shiro</groupId>
 9             <artifactId>shiro-web</artifactId>
10             <version>${shiro.version}</version>
11         </dependency>
12         <dependency>
13             <groupId>org.apache.shiro</groupId>
14             <artifactId>shiro-spring</artifactId>
15             <version>${shiro.version}</version>
16         </dependency>
17         <dependency>
18             <groupId>org.apache.shiro</groupId>
19             <artifactId>shiro-ehcache</artifactId>
20             <version>${shiro.version}</version>
21         </dependency>
View Code

添加Shiro支持功能

 1 package com.goku.webapi.config.Shiro;
 2 
 3 import com.goku.webapi.mapper.ext.sysMenuExtMapper;
 4 import com.goku.webapi.mapper.ext.sysUserExtMapper;
 5 import com.goku.webapi.model.sysMenu;
 6 import com.goku.webapi.model.sysRole;
 7 import com.goku.webapi.model.sysUser;
 8 import com.goku.webapi.service.sysUserService;
 9 import org.apache.shiro.SecurityUtils;
10 import org.apache.shiro.authc.*;
11 import org.apache.shiro.authz.AuthorizationInfo;
12 import org.apache.shiro.authz.SimpleAuthorizationInfo;
13 import org.apache.shiro.realm.AuthorizingRealm;
14 import org.apache.shiro.session.Session;
15 import org.apache.shiro.subject.PrincipalCollection;
16 import org.springframework.beans.factory.annotation.Autowired;
17 
18 /**
19  * Created by nbfujx on 2017/11/7.
20  */
21 public class ShiroRealm extends AuthorizingRealm {
22 
23     @Autowired
24     private sysUserExtMapper sysuserextmapper;
25     @Autowired
26     private sysMenuExtMapper sysmenuextmapper;
27 
28     /**
29      *权限验证
30      * **/
31     @Override
32     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
33         sysUser user = sysuserextmapper.selectByUsername((String) principalCollection.getPrimaryPrincipal());
34         //把principals放session中 key=userId value=principals
35         SecurityUtils.getSubject().getSession().setAttribute(String.valueOf(user.getId()),SecurityUtils.getSubject().getPrincipals());
36         SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
37         //赋予角色
38         for(sysRole userRole:user.getSysrole()){
39             info.addRole(userRole.getRoleName());
40         }
41         //赋予权限
42         for(sysMenu menu:sysmenuextmapper.selectByUserId(user.getId())){
43             info.addStringPermission(menu.getPerms());
44         }
45 
46         return info;
47 
48     }
49 
50     /**
51      * 登录验证
52      * **/
53     @Override
54     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
55         UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
56         String userName=token.getUsername();
57         sysUser user = sysuserextmapper.selectByUsername(token.getUsername());
58         if (user != null) {
59             //设置用户session
60             Session session = SecurityUtils.getSubject().getSession();
61             session.setAttribute("user", user);
62             return new SimpleAuthenticationInfo(userName,user.getPassword(),getName());
63         } else {
64             return null;
65         }
66     }
67 }
View Code

添加Shiro配置文件

  1 package com.goku.webapi.config.Shiro;
  2 
  3 import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
  4 import org.apache.shiro.cache.ehcache.EhCacheManager;
  5 import org.apache.shiro.spring.LifecycleBeanPostProcessor;
  6 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
  7 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
  8 import org.apache.shiro.web.filter.authc.LogoutFilter;
  9 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
 10 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
 11 import org.springframework.beans.factory.annotation.Qualifier;
 12 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 13 import org.springframework.context.annotation.Bean;
 14 import org.apache.shiro.mgt.SecurityManager;
 15 import org.springframework.context.annotation.Configuration;
 16 import org.springframework.context.annotation.DependsOn;
 17 
 18 import javax.servlet.Filter;
 19 import java.util.LinkedHashMap;
 20 import java.util.Map;
 21 
 22 
 23 /**
 24  * shiro配置类
 25  * Created by nbfujx on 2017/11/7.
 26  */
 27 @Configuration
 28 public class ShiroConfig {
 29     /**
 30      * LifecycleBeanPostProcessor,这是个DestructionAwareBeanPostProcessor的子类,
 31      * 负责org.apache.shiro.util.Initializable类型bean的生命周期的,初始化和销毁。
 32      * 主要是AuthorizingRealm类的子类,以及EhCacheManager类。
 33      */
 34     @Bean(name = "lifecycleBeanPostProcessor")
 35     public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
 36         return new LifecycleBeanPostProcessor();
 37     }
 38 
 39 
 40     /**
 41      * ShiroRealm,这是个自定义的认证类,继承自AuthorizingRealm,
 42      * 负责用户的认证和权限的处理,可以参考JdbcRealm的实现。
 43      */
 44     @Bean(name = "shiroRealm")
 45     @DependsOn("lifecycleBeanPostProcessor")
 46     public ShiroRealm shiroRealm() {
 47         ShiroRealm realm = new ShiroRealm();
 48         realm.setCacheManager(ehCacheManager());
 49         return realm;
 50     }
 51 
 52     /**
 53      * EhCacheManager,缓存管理,用户登陆成功后,把用户信息和权限信息缓存起来,
 54      * 然后每次用户请求时,放入用户的session中,如果不设置这个bean,每个请求都会查询一次数据库。
 55      */
 56     @Bean(name = "ehCacheManager")
 57     @DependsOn("lifecycleBeanPostProcessor")
 58     public EhCacheManager ehCacheManager() {
 59         return new EhCacheManager();
 60     }
 61 
 62     /**
 63      * SecurityManager,权限管理,这个类组合了登陆,登出,权限,session的处理,是个比较重要的类。
 64      */
 65     @Bean(name = "securityManager")
 66     public DefaultWebSecurityManager securityManager() {
 67         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
 68         securityManager.setRealm(shiroRealm());
 69         securityManager.setCacheManager(ehCacheManager());
 70         return securityManager;
 71     }
 72 
 73 
 74     /**
 75      * ShiroFilterFactoryBean,是个factorybean,为了生成ShiroFilter。
 76      * 它主要保持了三项数据,securityManager,filters,filterChainDefinitionManager。
 77      */
 78     @Bean(name = "shiroFilter")
 79     public ShiroFilterFactoryBean shiroFilterFactoryBean() {
 80         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
 81         shiroFilterFactoryBean.setSecurityManager(securityManager());
 82 
 83         Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
 84         shiroFilterFactoryBean.setFilters(filters);
 85 
 86 
 87         Map<String, String> filterChainDefinitionManager = new LinkedHashMap<String, String>();
 88         filterChainDefinitionManager.put("/login", "anon");
 89         filterChainDefinitionManager.put("/logout", "anon");
 90         filterChainDefinitionManager.put("/sysUser/*", "authc,perms");//"authc,perms[sysUser:*]");
 91         filterChainDefinitionManager.put("/sysMenu/*", "authc,perms");//"authc,perms[sysUser:*]");
 92         filterChainDefinitionManager.put("/*", "anon");
 93         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);
 94 
 95         shiroFilterFactoryBean.setLoginUrl("/notAuthc");
 96         shiroFilterFactoryBean.setSuccessUrl("/");
 97         shiroFilterFactoryBean.setUnauthorizedUrl("/notAuthz");
 98         return shiroFilterFactoryBean;
 99     }
100 
101     /**
102      * DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理。
103      */
104     @Bean
105     @DependsOn("lifecycleBeanPostProcessor")
106     public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
107         DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
108         defaultAAP.setProxyTargetClass(true);
109         return defaultAAP;
110     }
111 
112     /**
113      * AuthorizationAttributeSourceAdvisor,shiro里实现的Advisor类,
114      * 内部使用AopAllianceAnnotationsAuthorizingMethodInterceptor来拦截用以下注解的方法。
115      */
116     @Bean
117     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
118         AuthorizationAttributeSourceAdvisor aASA = new AuthorizationAttributeSourceAdvisor();
119         aASA.setSecurityManager(securityManager());
120         return aASA;
121     }
122 
123 
124 
125 
126 }
View Code

添加login验证类

  1 package com.goku.webapi.controller.impl;
  2 
  3 import com.alibaba.fastjson.JSON;
  4 import com.goku.webapi.controller.loginController;
  5 import com.goku.webapi.util.enums.returnCode;
  6 import com.goku.webapi.util.message.returnMsg;
  7 import org.apache.shiro.SecurityUtils;
  8 import org.apache.shiro.authc.AuthenticationException;
  9 import org.apache.shiro.authc.UsernamePasswordToken;
 10 import org.apache.shiro.crypto.hash.Md5Hash;
 11 import org.apache.shiro.session.SessionException;
 12 import org.apache.shiro.subject.Subject;
 13 import org.springframework.beans.factory.annotation.Autowired;
 14 import org.springframework.boot.autoconfigure.web.ErrorAttributes;
 15 import org.springframework.boot.autoconfigure.web.ErrorController;
 16 import org.springframework.http.HttpStatus;
 17 import org.springframework.web.bind.annotation.RequestMapping;
 18 import org.springframework.web.bind.annotation.RequestMethod;
 19 import org.springframework.web.bind.annotation.RequestParam;
 20 import org.springframework.web.bind.annotation.RestController;
 21 import org.springframework.web.context.request.RequestAttributes;
 22 import org.springframework.web.context.request.ServletRequestAttributes;
 23 
 24 import javax.servlet.http.HttpServletRequest;
 25 import java.util.HashMap;
 26 import java.util.Map;
 27 
 28 
 29 /**
 30  * Created by nbfujx on 2017-11-07.
 31  */
 32 @RestController
 33 public class loginControllerImpl implements loginController,ErrorController {
 34 
 35 
 36     private final static String ERROR_PATH = "/error";
 37 
 38     @Autowired
 39     private ErrorAttributes errorAttributes;
 40 
 41     @RequestMapping(value = "/login", method = RequestMethod.GET)//测试方法 实际用post方法
 42     public String login(
 43             @RequestParam(value = "username", required = true) String userName,
 44             @RequestParam(value = "password", required = true) String password,
 45             @RequestParam(value = "rememberMe", required = true, defaultValue = "false") boolean rememberMe
 46     ) {
 47         String passwordmd5 = new Md5Hash(password, "2").toString();
 48         Subject subject = SecurityUtils.getSubject();
 49         UsernamePasswordToken token = new UsernamePasswordToken(userName, passwordmd5);
 50         token.setRememberMe(rememberMe);
 51         try {
 52             subject.login(token);
 53         } catch (AuthenticationException e) {
 54             e.printStackTrace();
 55             return JSON.toJSONString (new  returnMsg(returnCode.ERROR));
 56         }
 57         return  JSON.toJSONString (new  returnMsg(returnCode.SUCCESS));
 58     }
 59 
 60 
 61     @Override
 62     @RequestMapping(value = "/logout", method = RequestMethod.GET)
 63     public String logout() {
 64         Subject subject = SecurityUtils.getSubject();
 65         try {
 66             subject.logout();
 67         }catch (SessionException e){
 68             e.printStackTrace();
 69             return JSON.toJSONString (new  returnMsg(returnCode.ERROR));
 70         }
 71         return JSON.toJSONString (new  returnMsg(returnCode.SUCCESS));
 72     }
 73 
 74     @Override
 75     @RequestMapping(value = "/notAuthc", method = RequestMethod.GET)
 76     public String notAuthc() {
 77         return JSON.toJSONString (new  returnMsg(returnCode.NOTAUTHC));
 78     }
 79 
 80     @Override
 81     @RequestMapping(value = "/notAuthz", method = RequestMethod.GET)
 82     public String notAuthz() {
 83         return  JSON.toJSONString (new  returnMsg(returnCode.NOTAUTHZ));
 84     }
 85 
 86     @Override
 87     @RequestMapping(value =ERROR_PATH)
 88     public String error(HttpServletRequest request)
 89     {
 90         Map<String, Object> body = getErrorAttributes(request, getTraceParameter(request));
 91         return  JSON.toJSONString (new  returnMsg(returnCode.ERROR,body));
 92     }
 93 
 94     @Override
 95     public String getErrorPath() {
 96         return ERROR_PATH;
 97     }
 98 
 99     private boolean getTraceParameter(HttpServletRequest request) {
100         String parameter = request.getParameter("trace");
101         if (parameter == null) {
102             return false;
103         }
104         return !"false".equals(parameter.toLowerCase());
105     }
106 
107     private Map<String, Object> getErrorAttributes(HttpServletRequest request,boolean includeStackTrace) {
108         RequestAttributes requestAttributes = new ServletRequestAttributes(request);
109         Map<String, Object> map = this.errorAttributes.getErrorAttributes(requestAttributes,includeStackTrace);
110         String URL = request.getRequestURL().toString();
111         map.put("URL", URL);
112         return map;
113     }
114 
115 }
View Code

添加业务类,业务方法增加RequiresPermissions权限注解

有权限

1 @Override
2     @RequestMapping(value="getUser/{id}", method = RequestMethod.GET)
3     @RequiresPermissions(value={"sysUser:selectByid"})
4     public String  selectByid(@PathVariable String id) {
5         this.logger.info("selectByid");
6         return JSON.toJSONString (new returnMsg(returnCode.SUCCESS,sysuserService.selectByid(id)));
7     }
View Code

无权限

1 @Override
2     @RequestMapping(value="getMenu/{id}", method = RequestMethod.GET)
3     @RequiresPermissions(value={"sysMenu:selectByid"})
4     public String  selectByid(@PathVariable long id) {
5         return JSON.toJSONString (new returnMsg(returnCode.SUCCESS,sysmenuService.selectByid(id)));
6     }
View Code

Shiro的使用验证

首先我们访问未登录时的资源

进行登录

我们访问登录后有权限的资源

 

我们访问登录后没有权限的资源

 

 

我们可以对每个方法增加权限控制

GITHUB

github :  https://github.com/nbfujx/learn-java-demo/tree/master/Goku.WebService.Simple.Shiro

网友评论

登录后评论
0/500
评论
nbfujx
+ 关注