文章目录

  • 前言
  • 一、Shiro是什么?
  • 二、Shiro 的功能介绍
    • 1.基本功能点
    • 2.功能点介绍
  • 三、Shiro 的架构
    • 1.Shrio的应用层面架构
    • 2.Shiro的核心架构
  • 四、Shiro入门案例
    • 1.shiro.ini
    • 2.Quickstart.class
  • 五、Shiro实战-整合进Spring Boot
    • 1.创建父工程shiroDemo
    • 2.创建shiroAuth子模块(Maven)
    • 3.代码结构讲解
    • 4.各个类详细讲解
    • 5.补充多Realm认证

前言

这篇文章主要是为了辅助记忆Shiro的,下面的代码主要复用了我看的一个视频里面的代码:https://www.bilibili.com/video/BV1Tf4y1w7Yo,所以有些地方如果不太了解的话可以去看看这个视频,不过这个视频有些地方可能有点绕,所以如果有shiro基础,只是想回顾一下的话,我觉得看这篇文章应该就可以很快回顾起来,如果没有shiro基础的话可能还是得跟着视频一起看。由于本人水平有限,所以可能有些地方写的不是很好,有发现什么问题的话,大家都可以指出来,共同进步!


一、Shiro是什么?

  • Apache Shiro是Java的一个安全(权限)框架。
  • Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。
  • Shiro可以完成:认证、授权、加密、会话管理、与Web集成、缓存等。

Shiro官网:http://shiro.apache.org/

二、Shiro 的功能介绍

1.基本功能点

2.功能点介绍

  • Authentication(认证):判断一个用户是否为合法用户的处理过程。最常用的身份认证方式是核对用户输入的用户名和密码是否与系统中存储的用户名和密码一致。还可以采用指纹和人脸识别等方式认证;
  • Authorization(授权):访问控制或权限验证,简单来说就是给不同的角色授予不同的权限,以及验证某个已认证的用户是否拥有某个权限;
  • Session Manager(会话管理):管理所有用户登录后的会话信息。用户登录后用Subject.getSession() 即可获取会话,在没有退出之前,用户的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web环境的;
  • Cryptography(加密):保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  • Web Support(Web支持):可以非常容易的集成到Web环境;
  • Caching(缓存):用户登录后,其用户信息、拥有的角色/权限不必每次去查,直接从缓存中获取,这样可以提高效率;
  • Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me(记住我):非常常见的功能,即一次登录后,下次无需登录;

三、Shiro 的架构

1.Shrio的应用层面架构

  • Subject:任何直接与应用代码交互的对象。Subject代表了当前“用户”,这个用户不具体指代某个人,只要与当前应用交互的任何东西都是Subject,如第三方服务、网络爬虫、机器人等;所有Subject实例都必须被绑定到一个SecurityManager上,SecurityManager才是实际的执行者;
  • SecurityManager(安全管理器):与安全有关的操作都会与SecurityManager进行交互;管理着所有的Subject;它是Shiro的核心,负责与Shiro的其他组件进行交互,相当于SpringMVC中DispatcherServlet的角色;
  • Realm:担当Shiro和你的应用程序的安全数据之间的“桥梁”或“连接器”。Shiro可以从Realm中获取安全数据(如用户、角色、权限),即认证和授权等操作所需要的安全数据都需要从Realm中获得;

2.Shiro的核心架构

  • Subject:任何直接与应用代码交互的对象;
  • SecurityManager(安全管理器):所有与安全相关的操作都会与SecurityManager进行交互;它管理着所有的Subject;它是Shiro的核心,负责与Shiro的其他组件进行交互,相当于SpringMVC中DispatcherServlet的角色;
  • Authenticator:负责Subject认证,当一个用户尝试登录时,该逻辑被 Authenticator执行;且它是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过;
  • Authorizer(授权器):即访问控制器,用来决定主体(Subject)是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
  • Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全数据;可以是JDBC实现,也可以是内存实现等等;它由用户提供,所以一般在应用中都需要实现自己的Realm;
  • SessionManager:管理所有用户登录后的会话信息。用户登录后用Subject.getSession() 即可获取会话,在没有退出之前,用户的所有信息都在会话中;会话可以是普通JavaSE环境,也可以是Web环境的;
  • CacheManager(缓存控制器):管理如用户、角色、权限等信息的缓存;因为这些数据
    基本上很少改变,放到缓存中后可以提高访问的性能;
  • Cryptography:密码模块,Shiro提供了一些常见的加密组件用于如密码加密/解密;

四、Shiro入门案例

以下是Shiro官网的一个入门小案例,供我们参考学习,我们主要关注其中的Quickstart.class以及shiro.ini中的部分代码就好。shiro.ini中主要放的就是用户、角色以及权限等数据,Quickstart.class就是简单的模拟一下认证及授权的过程,其中所用到的数据就来源于shiro.ini中,实际开发中我们的这些数据肯定是从数据库中获得。

1.shiro.ini

[users]
# user 'root' with password 'secret' and the 'admin' role
root = secret, admin
# user 'guest' with the password 'guest' and the 'guest' role
guest = guest, guest
# user 'presidentskroob' with password '12345' ("That's the same combination on
# my luggage!!!" ;)), and role 'president'
presidentskroob = 12345, president
# user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz'
darkhelmet = ludicrousspeed, darklord, schwartz
# user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz'
lonestarr = vespa, goodguy, schwartz[roles]
# 'admin' role has all permissions, indicated by the wildcard '*'
admin = *
# The 'schwartz' role can do anything (*) with any lightsaber:
schwartz = lightsaber:*
# The 'goodguy' role is allowed to 'delete' (action) the User(type) with
# license plate 'zhangsan' (instance specific id)
goodguy = User:delete:zhangsan

2.Quickstart.class

package com.m33.shiro.helloworld;import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class Quickstart {private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);public static void main(String[] args) {// 创建SecurityManager(下面这三行看看就好)Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");SecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);// get the currently executing user// 获取当前的 Subject!调用 SecurityUtils.getSubject();Subject currentUser = SecurityUtils.getSubject();// Do some stuff with a Session (no need for a web or EJB container!!!)// 测试使用 Session // 首先获取 Session: Subject的getSession()Session session = currentUser.getSession();session.setAttribute("someKey", "aValue");String value = (String) session.getAttribute("someKey");if (value.equals("aValue")) {log.info("----> Retrieved the correct value! [" + value + "]");}// let's login the current user so we can check against roles and permissions:// 测试当前的用户是否已经被认证. 即是否已经登录. // 调用 Subject 的 isAuthenticated() ,若未认证,执行if里面的代码if (!currentUser.isAuthenticated()) {// 把用户名和密码封装为 UsernamePasswordToken 对象UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");// remembermetoken.setRememberMe(true);try {// 执行登录. currentUser.login(token);} // 若没有指定的账户, 则 shiro 将会抛出 UnknownAccountException 异常. catch (UnknownAccountException uae) {log.info("----> There is no user with username of " + token.getPrincipal());return; } // 若账户存在, 但密码不匹配, 则 shiro 会抛出 IncorrectCredentialsException 异常。 catch (IncorrectCredentialsException ice) {log.info("----> Password for account " + token.getPrincipal() + " was incorrect!");return; } // 用户被锁定的异常 LockedAccountExceptioncatch (LockedAccountException lae) {log.info("The account for username " + token.getPrincipal() + " is locked.  " +"Please contact your administrator to unlock it.");}// ... catch more exceptions here (maybe custom ones specific to your application?// 所有认证时异常的父类. catch (AuthenticationException ae) {//unexpected condition?  error?}}// 成功登录log.info("----> User [" + currentUser.getPrincipal() + "] logged in successfully.");//test a role:// 测试是否有某一个角色. 调用 Subject 的 hasRole 方法. if (currentUser.hasRole("schwartz")) {log.info("----> May the Schwartz be with you!");} else {log.info("----> Hello, mere mortal.");return; }//test a typed permission (not instance-level)// 测试用户是否具备某一个行为. 调用 Subject 的 isPermitted() 方法。 if (currentUser.isPermitted("lightsaber:weild")) {log.info("----> You may use a lightsaber ring.  Use it wisely.");} else {log.info("Sorry, lightsaber rings are for schwartz masters only.");}//a (very powerful) Instance Level permission:// 测试用户是否具备某一个行为. User:delete:zhangsan: 表示允许删除User类型的zhangsanif (currentUser.isPermitted("User:delete:zhangsan")) {log.info("----> You are permitted to 'delete'(action) the User(type)with license plate (id) 'zhangsan'.  " +"Here are the keys - have fun!");} else {log.info("Sorry, you aren't allowed to 'delete' the 'zhangsan' User!");}//all done - log out!// 执行登出. 调用 Subject 的 Logout() 方法. System.out.println("---->" + currentUser.isAuthenticated()); // truecurrentUser.logout();System.out.println("---->" + currentUser.isAuthenticated()); // falseSystem.exit(0);}
}

五、Shiro实战-整合进Spring Boot

这里先简单介绍一下我们这次实战所要完成的功能,其实主要就是对用户登录进行认证以及授予某些权限,让不同用户登录的时候看到不同资源。下面代入实际的应用场景给大家讲解一下:
首先我们有三个用户admin、manager、worker,它们分别具有role(角色):admin、manager、worker;admin角色拥有访问mobile、salary资源的权限,manager角色拥有访问salary资源的权限;woker角色不具备任何权限;当我们用admin用户登录的时候就可以查看mobile和salary,manager只能查看salary,worker则什么都查看不了

1.创建父工程shiroDemo

以下为父工程的pom.xml文件内容,需要引入以下依赖

<properties><java.version>1.8</java.version>
</properties><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>2.3.3.RELEASE</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-starter</artifactId><version>1.6.0</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency></dependencies>
</dependencyManagement>

2.创建shiroAuth子模块(Maven)

以下为子工程的pom.xml文件内容,需要引入以下依赖,具体版本由父工程管理,这里只需添加我们需要用到的依赖即可

<dependencies><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId></dependency>
</dependencies>

3.代码结构讲解

下图是我们本次实战项目的代码结构,其中蓝色划掉的部分先暂时不用管它,后续我会讲解的
下面带大家先简单的了解下这些类所要实现的功能,后面再进行更详细的讲解

类名 功能
User 实体类,主要就是定义一些属性:用户名、密码、角色和权限等
MyRealm 一个自定义的Realm,主要负责获取认证和授权所需要的信息
ShiroConfig shiro的配置类,缓存、过滤器等功能在这里进行配置,在编译的时候便会先执行该配置类,后续登录认证或者授权的时候就会进到MyRealm中获取数据
LoginController 负责登录业务流程的控制
MobileController 负责获取mobile
SalaryController 负责获取salary
UserService 负责获取TestData中数据,返回给LoginController
MyPassWordEncoder 这是我们封装的对密码加盐加密的工具类
TestData 一些测试数据,主要是为了模拟UserService获取数据库中的数据,我们这次实战并没有连接数据库,为了测试使用才有了这个工具类,实际开发中这些数据需要存放在数据库中

4.各个类详细讲解

User

import java.util.List;
public class User {private String userName;private String userPass;private List<String> userRoles; //用户的角色private List<String> userPerms; //用户所具有的权限// 省略构造方法和get/set方法...
}

MyRealm

import com.m33.shiroAuth.bean.User;
import com.m33.shiroAuth.service.UserService;
import com.m33.shiroAuth.utils.MyPassWordEncoder;
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.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;import javax.annotation.Resource;@Configuration(value = "myRealm")
public class MyRealm extends AuthorizingRealm {private final Logger logger = LoggerFactory.getLogger(MyRealm.class);@Resourceprivate UserService userService;//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {logger.info("---------->entered method doGetAuthorizationInfo;");//拿到当前用户Subject subject = SecurityUtils.getSubject();User currentUser = (User)subject.getPrincipal();//写入当前用户的角色和权限信息SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.addRoles(currentUser.getUserRoles());info.addStringPermissions(currentUser.getUserPerms());return info;}// 认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {logger.info("--------------->entered method doGetAuthenticationInfo;");//获得当前用户的tokenUsernamePasswordToken userToken = (UsernamePasswordToken)authenticationToken;//从token中获得当前用户的用户名String username = userToken.getUsername();//模拟通过用户名获取数据库中用户密码User user = userService.getUserByUserName(username);if(null == user){//后续会抛出UnknownAccountExceptionreturn null;}else{//MyPassWordEncoder.getEncodedPassword()是通过我们的工具类对用户密码进行加盐加密//正常是不需要这一步的,注册的时候就需要对密码进行加盐加密。因为我们没有模拟注册流程,// 所以现在密码是明文,我们才需要先把明文密码加盐加密,后续才能与输入的密码进行比对user.setUserPass(MyPassWordEncoder.getEncodedPassword(user.getUserPass()));//各参数含义分别为当前用户、用户密码(加密后的)、盐值(里面的“salt”可随意设置)、realm名SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getUserPass(),ByteSource.Util.bytes("salt"), "myRealm");//密码验证逻辑由shiro完成,我们只需返回SimpleAuthenticationInfo对象即可。密码不匹配会抛出密码不匹配异常。//密码正确,会把user对象传入SecurityUtils.getSubject()。这些都是shiro帮我们做的。return authenticationInfo;}}
}

ShiroConfig

import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;@Configuration
public class ShiroConfig {// 1、注册Realm对象,在MyRealm中有@Configuration(value = "myRealm"),已经注册好Realm对象了//所以以下代码就不需要了,但是我们需要知道配置shiro一般都需要以下三个步骤,我们可以当成模板来记忆,根据实际需求进行修改即可//@Bean//public Realm myRealm(){//     return new MyRealm();// }// 2、DefaultWebSecurityManager @Beanpublic DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("myRealm") AuthorizingRealm myRealm){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 通过HashedCredentialsMatcher 指定算法和加密次数HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();matcher.setHashAlgorithmName("MD5");matcher.setHashIterations(2);myRealm.setCredentialsMatcher(matcher);//设置缓存,使用缓存可以避免需要授权信息时频繁的调用数据库查询的问题。//原理很简单,只要在SecurityManager里注入CacheManager即可。//我们也可以自定义CacheManager的实现,可以是ehcache、redis等等。//实际开发中自定义RedisCache是很常见的,这里由于本人水平有限,就不演示如何用redis实现缓存了securityManager.setCacheManager(new MemoryConstrainedCacheManager());//最后要把我们的myRealm交给securityManager进行管理securityManager.setRealm(myRealm);return securityManager;}//3、ShiroFilterFactoryBean@Beanpublic ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();bean.setSecurityManager(securityManager);/*配合Shiro的内置过滤器,参见DefaultFilteranon : 无需认证就可以访问authc : 必须认证才可以访问 对应@RequiresAuthentication注解user : 用户登录且记住我 才可以访问 对应@RequiresUser注解perms : 拥有某个资源才可以访问 对应@RequiresPermissions注解roles : 拥有某个角色才可以访问  对应@RequiresRoles注解*/Map<String, String> filterChainDefinition = new HashMap<>();//配置antMatcher,跟SpringSecurity一样,可以配**,*,?// **:匹配路径中的零个或多个路径,如/admin/**将匹配/admin/a或/admin/a/b// *:匹配零个或多个字符串,如/admin将匹配/admin、/admin123,但不匹配/admin/1// ?:匹配一个字符,如/admin?将匹配/admin1,但不匹配/admin或/admin/filterChainDefinition.put("/mobile/**","perms[mobile]");filterChainDefinition.put("/salary/**","perms[salary]");filterChainDefinition.put("/main.html","authc");//配置登出过滤器filterChainDefinition.put("/logout","logout");//将配好的内容添加到拦截器中bean.setFilterChainDefinitionMap(filterChainDefinition);//设置登录页bean.setLoginUrl("/index.html");//设置成功后跳转的页面bean.setSuccessUrl("/main.html");//设置没有资源权限时跳转到的页面bean.setUnauthorizedUrl("/common/noauth");return bean;}
}

补充:Shiro中默认的过滤器

配置缩写 对应的过滤器 功能 例子
anon AnonymousFilter 指定url可以匿名访问 /admins/**=anon
authc FormAuthenticationFilter 指定url需要form表单登录,默认会从请求中获取username、password,rememberMe等参数并尝试登录,如果登录不了就会跳转到loginUrl配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录逻辑的,自己写的话出错返回的信息都可以定制嘛。 /user/**=authc
authcBasic BasicHttpAuthenticationFilter 指定url需要basic登录 /user/**=authcBasic
logout LogoutFilter 登出过滤器,配置指定url就可以实现退出功能,非常方便 filterChainDefinition.put(“/logout”,“logout”);
noSessionCreation NoSessionCreationFilter 禁止创建会话
perms PermissionsAuthorizationFilter 需要指定权限才能访问 /admin/**=perms[user:add:*],多个perms需要在[]中加“”,中间用,隔开
port PortFilter 需要指定端口才能访问 /admins/**=port[8080]
rest HttpMethodPermissionFilter 将http请求方法转化成相应的动词来构造一个权限字符串,这个感觉意义不大,有兴趣自己看源码的注释 /admin/user/**=perms[user.method],其中method为post,get,delete等
roles RolesAuthorizationFilter 需要指定角色才能访问 /admins/**=roles[“admin,user”]
ssl SslFilter 需要https请求才能访问
user UserFilter 需要已登录或“记住我”的用户才能访问

LoginController、MobileController、SalaryController

import com.m33.shiroAuth.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;@RequestMapping("/common")
@RestController
public class LoginController {private final Logger logger = LoggerFactory.getLogger(LoginController.class);@PostMapping("/login")public Object login(String username,String password){Map<String,String> errorCode = new HashMap<>();UsernamePasswordToken token = new UsernamePasswordToken(username,password);Subject user = SecurityUtils.getSubject();//如果用户已认证,直接打印already loginif(user.isAuthenticated()){return "already login";}else{try{//用户未认证,下面进行认证user.login(token);user.getSession().setAttribute("currentUser",user.getPrincipal());return "login succeed";} catch (UnknownAccountException uae) {//捕获认证过程中出现的各种异常logger.info("There is no user with username of " + token.getPrincipal());errorCode.put("errorMsg","不存在的用户名");} catch (IncorrectCredentialsException ice) {logger.info("Password for account " + token.getPrincipal() + " was incorrect!");errorCode.put("errorMsg","密码不正确");} catch (LockedAccountException lae) {logger.info("The account for username " + token.getPrincipal() + " is locked.  " +"Please contact your administrator to unlock it.");errorCode.put("errorMsg","账号被锁定");} catch(AuthenticationException authe){logger.info("Authentication error ",authe);errorCode.put("errorMsg",authe.getMessage());}return errorCode;}}//获取当前用户,用于前台页面展示用@RequestMapping("/getCurrentUser")public Object getCurrentUser(){Subject subject = SecurityUtils.getSubject();return subject.getSession().getAttribute("currentUser");}//未授权时执行的方法@RequestMapping("/noauth")public String noAuth(){return "未经授权,无法访问。";}
}
--------------------------------------------------------------
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/mobile")
public class MobileController {/*** 代表一个查手机号的后台接口。* @return*/@GetMapping("/query")public String query(){return "mobile";}
}
-------------------------------------------------------------
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/salary")
public class SalaryController {/*** 代表一个查薪水的后台接口。* @return*/@RequiresPermissions("salary")@GetMapping("/query")public String query(){return "salary";}
}

UserService

import com.m33.shiroAuth.bean.User;
import com.m33.shiroAuth.utils.TestData;
import org.apache.commons.beanutils.BeanUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;@Service
public class UserService {@Resourceprivate TestData testData;//这里的内容看看就好,主要是为了模拟获取数据库中的用户数据,实际开发中肯定是直接从数据库中获得public User getUserByUserName(String username){List<User> queryUsers = testData.getAllUser().stream().filter(user -> username.equals(user.getUserName())).collect(Collectors.toList());if(null != queryUsers && queryUsers.size()>0){try {return (User)BeanUtils.cloneBean(queryUsers.get(0));} catch (Exception e) {e.printStackTrace();return null;}}else{return null;}}
}

TestData

import com.m33.shiroAuth.bean.User;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;@Component
public class TestData {private List<User> allUser;/*** 模拟数据库获取到的数据。* admin用户 拥有admin角色,拥有mobile和salary两个资源。* mobile用户,拥有mobile角色,拥有mobile资源。* worker用户,拥有worker角色,没有资源。* @return*/public List<User> getAllUser(){if(null == allUser){allUser = new ArrayList<User>();allUser.add(new User("admin","admin",Arrays.asList("admin"),Arrays.asList("mobile","salary")));allUser.add(new User("manager","manager",Arrays.asList("manager"),Arrays.asList("salary")));allUser.add(new User("worker","worker",Arrays.asList("worker"),Arrays.asList("worker")));}return allUser;}
}

MyPassWordEncoder

import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
/*** 负责密码加盐加密*/
public class MyPassWordEncoder {public static String getEncodedPassword(String password){//各参数含义分别为加密方式、密码、盐值(可随意设置)、迭代次数(指反复加盐加密的次数)SimpleHash simpleHash = new SimpleHash("MD5",password, ByteSource.Util.bytes("salt"),2);return simpleHash.toString();}
}

以上便是全部类的代码讲解,前端代码这里就不去研究了,下面补充一下认证流程和授权流程

认证流程如下:

  • 1、首先调用Subject.login(token)进行登录,其会自动委托给SecurityManager
  • 2、SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
  • 3、Authenticator才是真正的身份验证者,ShiroAPI中核心的身份认证入口点,此处可以自定义插入自己的实现;
  • 4、Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
  • 5、Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

授权流程如下:

  • 1、首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;
  • 2、Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
  • 3、在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
  • 4、Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。

5.补充多Realm认证

当我们需要通过Shiro实现“用户名/手机号/邮箱”任意一个都可以登录的时候,我们就需要用到多Realm认证;我们要实现的效果就是通过用户名+密码或者手机号码+密码都可以实现登录

修改User类,加一个mobile参数,为了可以通过手机号登录

import java.util.List;public class User {private String userName;private String userPass;private String mobile;//补充mobile属性private List<String> userRoles;private List<String> userPerms;......
}

修改TestData,添加手机号

 public List<User> getAllUser(){if(null == allUser){allUser = new ArrayList<User>();allUser.add(new User("admin","admin","15777777777",Arrays.asList("admin"),Arrays.asList("mobile","salary")));allUser.add(new User("manager","manager","15888888888",Arrays.asList("manager"),Arrays.asList("salary")));allUser.add(new User("worker","worker","15999999999",Arrays.asList("worker"),Arrays.asList("worker")));}return allUser;}

修改UserService类,添加getUserByMobile方法

public User getUserByMobile(String mobile){List<User> queryUsers = testData.getAllUser().stream().filter(user -> mobile.equals(user.getMobile())).collect(Collectors.toList());if(null != queryUsers && queryUsers.size()>0){return queryUsers.get(0);}else{return null;}}

在config下添加MobileRealm,这就是上面那个代码结构图划掉的蓝色部分

import com.m33.shiroAuth.bean.User;
import com.m33.shiroAuth.service.UserService;
import com.m33.shiroAuth.utils.MyPassWordEncoder;
import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
//与MyRealm差不多,但是只需要认证流程即可,因为我们只是为了用手机号登录认证
@Configuration(value = "mobileRealm")
public class MobileRealm extends AuthenticatingRealm {private final Logger logger = LoggerFactory.getLogger(MobileRealm.class);@Resourceprivate UserService userService;//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {logger.info("---->entered method doGetAuthenticationInfo;");UsernamePasswordToken usertoken = (UsernamePasswordToken)authenticationToken;String username = usertoken.getUsername();//只需要管用户名查询逻辑。模拟获取数据库中用户手机号User user = userService.getUserByMobile(username);if(null == user){return null; //后续会抛出UnknownAccountException}else{user.setUserPass(MyPassWordEncoder.getEncodedPassword(user.getUserPass()));SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getUserPass(),ByteSource.Util.bytes("salt"), "mobileRealm");return authenticationInfo;}}
}

最后,修改ShiroConfig的getDefaultWebSecurityManager方法

    @Beanpublic DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("myRealm") AuthorizingRealm myRealm, @Qualifier("mobileRealm") Realm mobileRealm){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();matcher.setHashAlgorithmName("MD5");matcher.setHashIterations(2);myRealm.setCredentialsMatcher(matcher);securityManager.setCacheManager(new MemoryConstrainedCacheManager());//创建多Realm认证器,设置认证策略,下面有对认证策略进行一个简单的介绍ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());securityManager.setAuthenticator(authenticator);//securityManager.setRealm(myRealm);securityManager.setRealms(Arrays.asList(myRealm,mobileRealm));return securityManager;}

补充:认证策略说明,Shiro提供了3个具体的 AuthenticationStrategy 实现
[1] AtLeastOneSuccessfulStrategy(默认实现):如果一个(或更多)验证成功,则整体的尝试被认为是成功的。如果没有一个验证成功,则整体失败。说白了就是,至少有一个Realm的验证是成功的算才认证通过,否则认证失败。
[2] FirstSuccessfulStrategy:第一个Realm成功验证返回的信息将被使用,其他的Realm将被忽略。如果没有一个Realm验证成功,则整体失败。
[3] AllSuccessfulStrategy:所有配置的Realm都必须验证成功才算认证通过,否则认证失败。
补充:Realm的继承关系

一般继承AuthorizingRealm(授权)即可;从图中我们可以看到它继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。

Shiro从入门到实战(整合进SpringBoot)相关推荐

  1. db2 springboot 整合_[SpringBoot]快速配置多数据源(整合MyBatis)

    前言 由于业务需求,需要同时在SpringBoot中配置两套数据源(连接两个数据库),要求能做到service层在调用各数据库表的mapper时能够自动切换数据源,也就是mapper自动访问正确的数据 ...

  2. 视频教程-SpringBoot实战教程:SpringBoot入门及前后端分离项目开发-Java

    SpringBoot实战教程:SpringBoot入门及前后端分离项目开发 十三,CSDN达人课课程作者,CSDN 博客作者,现就职于某网络科技公司任职高级 Java 开发工程师,13blog.sit ...

  3. 慕课网_《RabbitMQ消息中间件极速入门与实战》学习总结

    慕课网<RabbitMQ消息中间件极速入门与实战>学习总结 时间:2018年09月05日星期三 说明:本文部分内容均来自慕课网.@慕课网:https://www.imooc.com 教学源 ...

  4. 2019最新 Java商城秒杀系统的设计与实战视频教程(SpringBoot版)

    第一章 1-1课程整体介绍.mp4 1-2核心技术列表.mp4 1-3课程要求与收益.mp4 1-4系统的整体演示.mp4 https://www.jianshu.com/writer#/notebo ...

  5. 项目实战4: 基于 SpringBoot 的超市账单管理系统

    文章目录 一.项目介绍 二.数据库设计 三.概要设计 确立对象,划分模块 四.详细设计 4.1 Shiro 授权与鉴权流程梳理 4.1.1 了解 Shiro 4.1.2 Shiro 数据库设计 4.1 ...

  6. Docker技术入门与实战(第2版).

    容器技术系列 Docker技术入门与实战 第2版 杨保华 戴王剑 曹亚仑 编著 图书在版编目(CIP)数据 Docker技术入门与实战 / 杨保华,戴王剑,曹亚仑编著. -2版. -北京:机械工业出版 ...

  7. 《JavaCV从入门到实战教程合集》介绍和目录

    前言 <JavaCV从入门到实战教程合集>是2016年<JavaCV开发实战教程>和2018年<JavaCV入门教程>2022年<JavaCV音视频实战宝典& ...

  8. Redis6入门到实战------思维导图+章节目录

    Redis学习大纲 思维导图 思维导图 Redis6入门到实战------1.NoSQL数据库简介 地址: Redis6入门到实战------2.Redis6概述和安装 地址: Redis6入门到实战 ...

  9. 开放下载!解锁 Serverless 从入门到实战大“橙”就

    来源|阿里巴巴云原生公众号 <文末送好礼> Serverless 架构即将引领云计算的下一个十年已成行业共识.处于变革中的开发者,大多已从观望状态转向尝试阶段, 越来越多 Serverle ...

  10. 8s pod 查看 的yaml_Kubernetes入门到实战(五)深入浅出详解Pod

    作者:Happy老师 链接:https://blog.51cto.com/happylab/2500457 写在前面 前面的系列文章已介绍kubernetes架构,安装,升级和快速入门,读者通过文章的 ...

最新文章

  1. 集群服务器状态命令------rs.status()各个字段的含义
  2. 全球及中国橡胶行业盈利状况及十四五竞争前景展望报告2021版
  3. 微信windows版_刚刚微信内测更新!可以批量管理好友,太方便了
  4. Unity基础之:UnityAPI的学习
  5. C#中的bitmap类和图像像素值获取方法
  6. VS2010 快捷键 (空格显示 绿点, Tab 显示箭头)
  7. [RN] React Native 自定义导航栏随滚动渐变
  8. 更换Homebrew为中科大源
  9. 利用CNN和迁移学习方法识别植物叶片疾病
  10. 安卓3d游戏引擎_3D球闯关游戏-3D球闯关游戏安卓官方版预约 v1.2.5
  11. SpringBoot连接远程云服务器的Redis并且让Redis后台运行
  12. coderforces Gym 100803A/Aizu 1345/CSU 1536/UVALive 6832 Bit String Reordering(贪心证明缺)
  13. 自动驾驶_视觉定位_高德公开课
  14. ADSL密码查看器.rar
  15. 扩展以太网——集线器
  16. 数学建模竞赛常用软件培训1
  17. Linux下和编译器、程序的运行、环境变量等相关的常见问题
  18. Perfetto for linux-使用 Perfetto 分析调度问题
  19. 【论文解读 KDD 2019 | GATNE 】Representation Learning for Attributed Multiplex Heterogeneous Network
  20. android的退格字符,按键安卓版如何实现退格键功能

热门文章

  1. 计算机维护与维修毕业论文,计算机维修与维护毕业论文.doc
  2. 如何打印复印试卷,试卷打印复印去哪里方便
  3. 教您在Excel中批量生成条形码
  4. ajax ruby,在Ruby on Rails中使用AJAX的教程
  5. 盗墓小说和西高穴:真真假假曹操墓
  6. react中使用谷歌地图并定位
  7. 关于 PCB 多层板制程能力不得不说的那些事儿
  8. PCB多层板为什么都是偶数层
  9. 个人网站搭建时linux中的相关配置记录(mysql,jdk,nginx,redis)
  10. Adversarial Discriminative Domain Adaptation阅读笔记(详细)