Springboot 整合ApachShiro完成登录验证和权限管理
1.前言
做一个系统最大的问题就是安全问题以及权限的问题,如何正确的选择一个安全框架对自己的系统进行保护,这方面常用的框架有SpringSecurity,但考虑到它的庞大和复杂,大多数公司还是会选择
ApachShiro 这个框架作为公司安全的框架,这里我们掌握这个框架也是特别有必要的,所以,开始搭建!
2.简介 shiro能做什么
- 验证用户身份
- 用户访问权限控制,比如:
- 判断用户是否分配了一定的安全角色。
- 判断用户是否被授予完成某个操作的权限
- 在非 web 或 EJB 容器的环境下可以任意使用Session API
- 可以响应认证、访问控制,或者 Session 生命周期中发生的事件
- 可将一个或以上用户安全数据源数据组合成一个复合的用户 "view"(视图)
- 支持单点登录(SSO)功能
- 支持提供“Remember Me”服务,获取用户关联信息而无需登录
3.shiro 功能简介
- Authentication(认证):用户身份识别,通常被称为用户“登录”
- Authorization(授权):访问控制。比如某个用户是否具有某个操作的使用权限。
- Session Management(会话管理):特定于用户的会话管理,甚至在非web 或 EJB 应用程序。
- Cryptography(加密):在对数据源使用加密算法加密的同时,保证易于使用。
还有其他的功能来支持和加强这些不同应用环境下安全领域的关注点。特别是对以下的功能支持:
- Web支持:Shiro 提供的 web 支持 api ,可以很轻松的保护 web 应用程序的安全。
- 缓存:缓存是 Apache Shiro 保证安全操作快速、高效的重要手段。
- 并发:Apache Shiro 支持多线程应用程序的并发特性。
- 测试:支持单元测试和集成测试,确保代码和预想的一样安全。
- "Run As":这个功能允许用户假设另一个用户的身份(在许可的前提下)。
- "Remember Me":跨 session 记录用户的身份,只有在强制需要时才需要登录。
3.Architecture 架构
Apache Shiro 设计理念是使程序的安全变得简单直观而易于实现,Shiro的核心设计参照大多数用户对安全的思考模式--如何对某人(或某事)在与程序交互的环境中的进行安全控制。
程序设计通常都以用户故事为基础,也就是说,你会经常设计用户接口或服务api基于用户如何(或应该)与软件交互。 例如,你可能会说,“如果我的应用程序的用户交互是登录,我将展示他们可以单击一个按钮来查看他们的帐户信息。 如果不登录,我将展示一个注册按钮。”
4.Shiro 架构包含三个主要的理念
-
Subject:就像我们在上一章示例中提到的那样,Subject 本质上是当前运行用户特定的'View'(视图),而单词“User”经常暗指一个人,Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它--当前和软件交互的任何事件。 Subject 实例都和(也需要)一个 SecurityManager 绑定,当你和一个Subject 进行交互,这些交互动作被转换成 SecurityManager 下Subject 特定的交互动作。
-
SecurityManager: SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞。然而,一旦一个程序配置好了SecurityManager 和它的内部对象,SecurityManager通常独自留下来,程序开发人员几乎花费的所有时间都集中在 Subjet API上。 我们将在以后详细讨论 SecurityManager,但当你和一个 Subject 互动时了解它是很重要的。任何 Subject 的安全操作中 SecurityManager 是幕后真正的举重者,这在上面的图表中可以反映出来。
-
Realms: Reamls 是 Shiro 和你的程序安全数据之间的“桥”或者“连接”,它用来实际和安全相关的数据如用户执行身份认证(登录)的帐号和授权(访问控制)进行交互,Shiro 从一个或多个程序配置的 Realm 中查找这些东西。 Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)。SecurityManager 可以配置多个复杂的 Realm,但是至少有一个是需要的。 Shiro 提供开箱即用的 Realms 来连接安全数据源(或叫地址)如 LDAP、JDBC、文件配置如INI和属性文件等,如果已有的Realm不能满足你的需求你也可以开发自己的Realm实现。 和其它内部组件一样,Shiro SecurityManager 管理如何使用 Realms获取 Subject 实例所代表的安全和身份信息。
5.说了这么多 了解一些概念后开始搭建
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.0.0</version> </dependency> <!-- thymeleaf --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- apache shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <!-- spring jpa --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
6.配置ShiroConfig (注意导包,容易导错)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; @Configuration public class ShiroConfig { /*** * 配置shiro过滤器工厂Bean * @Author MRC * @Date 2019年6月6日 上午11:42:46 * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { System.out.println("ShiroConfiguration.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // 拦截器. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put("/static/**", "anon"); // 配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); // <!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index"); // 未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 凭证匹配器 告诉 * @return */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5");// 散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2);// 散列的次数,比如散列两次,相当于 md5(md5("")); return hashedCredentialsMatcher; } /** * 自定义桥----》它用来实际和安全相关的数据如用户执行身份认证(登录)的帐号和授权(访问控制)进行交互 * @Author MRC * @Date 2019年6月6日 上午11:44:45 * @return */ @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); //设置密码凭证匹配器 myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; } /*** * SecurityManager 是 Shiro 架构的核心,配合内部安全组件共同组成安全伞 * @Author MRC * @Date 2019年6月6日 上午11:46:23 * @return */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } /** * 开启shiro aop注解支持. 使用代理方式;所以需要开启代码支持; * * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean(name = "simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError");// 数据库异常处理 mappings.setProperty("UnauthorizedException", "403"); r.setExceptionMappings(mappings); // None by default r.setDefaultErrorView("error"); // No default r.setExceptionAttribute("ex"); // Default is "exception" // r.setWarnLogCategory("example.MvcLogger"); // No default return r; } }
7.配置Realms (配置认证与授权)
它用来实际和安全相关的数据如用户执行身份认证(登录)的帐号和授权(访问控制)进行交互,Shiro 从一个或多个程序配置的 Realm 中查找这些东西。 Realm 本质上是一个特定的安全 DAO:它封装与数据源连接的细节,得到Shiro 所需的相关的数据。在配置 Shiro 的时候,你必须指定至少一个Realm 来实现认证(authentication)和/或授权(authorization)
public class MyShiroRealm extends AuthorizingRealm { @Autowired private UserInfoService userInfoService; /*** * 当访问到页面的时候,链接配置了相应的权限或者 Shiro 标签才会执行此方法否则不会执行 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal(); for(SysRole role:userInfo.getRoleList()){ //加入角色 authorizationInfo.addRole(role.getRole()); for(SysPermission p:role.getPermissions()){ //循环放入权限信息 authorizationInfo.addStringPermission(p.getPermission()); } } return authorizationInfo; } /*** * 重写获取用户信息的方法 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("MyShiroRealm.doGetAuthenticationInfo()"); // 获取用户的输入的账号. String username = (String) token.getPrincipal(); System.out.println(token.getCredentials()); // 通过username从数据库中查找 User对象,如果找到,没找到. // 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 UserInfo userInfo = userInfoService.findByUsername(username); System.out.println("----->>userInfo=" + userInfo); if (userInfo == null) { return null; } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo, // 用户名 userInfo.getPassword(), // 密码 ByteSource.Util.bytes(userInfo.getCredentialsSalt()), // salt=username+salt getName() // realm name ); return authenticationInfo; } }
8 具体注释都写在代码里面了,导入sql文件开箱即可使用Demo
码云:https://gitee.com/mrc1999/Springboot-apachShiro
参考:https://www.cnblogs.com/ityouknow/p/7089177.html
参考:ApachShiro中文参考手册