0


Shiro安全框架【SpringBoot版】

文章目录


Shiro安全框架

一、 入门概述

1.1、Shiro是什么

Apache Shiro 是一款功能强大的且易于使用的Java的安全框架。Shiro可以完成:认证、加密、会话管理、与web集集成等。借助SHiro可以帮助我们快速轻松的保护任何应用程序。

shiro官网:Apache Shiro | Simple. Java. Security.

在这里插入图片描述

1.2、为什么使用Shiro

与Shiro的特性密不可分:

  • 易于使用
  • 全面
  • 灵活
  • 强力支持Web
  • 兼容性强
  • 社区支持

1.3、Shiro与Spring Security的区别

  1. SpringSecurity基于Spring开发,项目若使用Spring 可以与SpringSecurity作权限更加方便,而Shiro需要与Spring进行整合
  2. Spring Security功能更加丰富
  3. Spring Security社区资源更加丰富

看到这里,是不是有些人就认为Spring Security功能更发面都比Shiro好,为什么不学习SpringSecurity。有一句话:

存在即合理

。下面看看Shiro的特点

  1. Shiro的配置和使用比较简单,SpringSecurity使用比较复杂
  2. Shiro的依赖性低,不需要任何的容器与框架,可以独立运行
  3. Shiro不仅仅可以使用在Web端,可以使用在任何的场景。

1.4、基本功能

了解Shiro的功能,我们可以去官网下载一张Shiro的功能结构图来进行补充学习:

阿帕奇四郎特色

1.4.1、主要功能

  1. 认证登录(Authentication)
  2. 授权验证(Authorization)
  3. 会话管理(Session Management)
  4. 密码加密(Cryptography)

1.4.2 、次要功能

  1. Web支持(web support)
  2. 缓存(caching)
  3. 多线程并发验证(Concurrency)
  4. 测试(Testing)
  5. 另外身份登录(Run as)
  6. 记住我(Remember me)

1.5、架构原理

从外部来看Shiro,即从应用程序的角度来观察使用Shiro完成工作

Shiro Basic Architecture Diagram

应用程序—>(登录)---->subject(对象)进行身份校验---->安全管理器(SecurityManager)---->Reaim(用户登陆的用户信息)

从内部的架构来看Shiro

Shiro Architecture Diagram

二、基本使用

2.1、环境准备

1、Shiro不依赖容器,可以直接利用Maven使用

2、添加依赖

<!-- Shiro依赖 --><dependencies><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.9.0</version></dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency></dependencies>

3、创建Maven工程

结构如下:

在这里插入图片描述

2.2、配置ini文件

在创建好的工程的Resources目录下,创建一个shiro.ini文件

在这里插入图片描述

[users]
zhangsan=z3
lisi=l4

2.3、登录认证

2.3.1、登录认证概念

(1)身份认证:一般需要提供身份ID等一些表示用户登陆这信息身份的标识,如提供email、用户名\密码来认证

(2)在Shiro中、用户需要提供principals(身份)和credentials(证明)给shiro。从而应用能验证用户身份。

2.3.2、登录认证的流程

  1. 收集用户二点身份/凭证,及如用户名/密码
  2. 调用Subject.login进行登录,如果失败则将得到的相应的AuthenticationException异常,根据异常提示用户登录错误信息,否则登陆成功。
  3. 创建自定义的Realm类,继承org.apache.shiro.realm.AuthenticationRealm类,实现doGetAuthenticationInfo()方法

在这里插入图片描述

2.3.3、登录认证示例

创建测试类,获取认证对象,进行登录认证,如下:

publicclassShiroRun{@SuppressWarnings("deprecation")publicstaticvoidmain(String[] args){// 1、获取Shiro初始化 通过ini文件获取用户信息@SuppressWarnings("deprecation")IniSecurityManagerFactory factory =newIniSecurityManagerFactory("classpath:shiro.ini");try{// 通过工厂创建SecurityManagerSecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);// 2、获取Subject对象Subject subject =SecurityUtils.getSubject();// 3、获取外部参数 通过页面获取的用户名和密码 创建token对象AuthenticationToken token =newUsernamePasswordToken("zhangsan","z31");// 4、完成登录
            subject.login(token);System.out.println("登陆成功...");}catch(UnknownAccountException e){// TODO: handle exceptionSystem.out.println("用户名不存在...");}catch(IncorrectCredentialsException e){// TODO: handle exceptionSystem.out.println("密码错误...");}catch(AuthenticationException e){System.out.println("登陆失败...");}}}

登陆成功:

在这里插入图片描述

密码错误:

在这里插入图片描述

账户错误:

在这里插入图片描述

2.4、角色、授权

2.4.1、授权

授权:

也叫做访问控制,即在应用中控制谁访问哪些资源

授权中需要了解的概念:

主体(Subject)

资源(Resources)

权限(Permission)

角色(Role)
  • 主体:访问应用的用户,用户经授权才可访问指定资源
  • 资源:在应用中用户可以访问的URL,比如JSP页面,查看/编辑某些权限
  • 权限:表示在应用中用户能不能访问某个资源(有没有权利去访问某一个资源)

Shiro支持粗粒度的授权(用户模块的所有权限的授权)、也支持细粒度的授权(某个模块下的某个功能,比如查询)

2.4.1.1、授权方式
  1. 编程式授权:通过IF-ELSE授权
if(subject.hasRole("admin")){// 有admin的权限}elseif(subject.hasRole("commons")){// 普通用户的权限}else{// 没有权限}
  1. 注解式:通过执行的Java方法上加上注解完成,没有泉下今年的将抛出异常
@RequriesRole("admin")publicvoidqueryALL(){// 具体的业务逻辑}
  1. JSP/GSP标签,在JSP/GSP页面通过相应的标签完成
<shiro:hasRole name="admin">
    <input type="button" class="queryAll" name="queryAll" value="查询所有"/>
</shiro:hasRole>
2.4.1.2、授权流程
  1. 首先调用Subject.isPermitted*/hasRole* 接口,其余委托SecurityManager。而SecurityManager接着会委托给Authorizer。
  2. Authorizer是真正的授权者,如果调用isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转化成相应的Permission示例。
  3. 再授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限 1. Authorizer会判断Realm的角色/权限是否与传过来的匹配,如果有多个Realm,则会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败。

在这里插入图片描述

2.4.2、角色

角色:权限的集合(比如说系统管理员、业务人员、普通用户人员等)

2.4.3、角色授权示例

【角色】

在ini文件里配置用户角色的权限信息:

[users]
zhangsan=z3,admin,commons
lisi=l4,commons

[roles]
admin=user:insert,user:select
commons=user:select

通过以下方式完成用户角色下权限的判断:

if(subject.isPermitted("user:insert")){System.out.println("用户有插入权限");}else{System.out.println("没有insert权限");}

完整示例:

publicclassShiroRun{@SuppressWarnings("deprecation")publicstaticvoidmain(String[] args){// 1、获取Shiro初始化 通过ini文件获取用户信息@SuppressWarnings("deprecation")IniSecurityManagerFactory factory =newIniSecurityManagerFactory("classpath:shiro.ini");try{// 通过工厂创建SecurityManagerSecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);// 2、获取Subject对象Subject subject =SecurityUtils.getSubject();// 3、获取外部参数 通过页面获取的用户名和密码 创建token对象AuthenticationToken token =newUsernamePasswordToken("lisi","l4");// 4、完成登录
            subject.login(token);System.out.println("登陆成功...");// 5、判断用户角色if(subject.hasRole("commons")){System.out.println("拥有commons角色");}else{System.out.println("没有拥有commons角色");}if(subject.isPermitted("user:insert")){System.out.println("用户有插入权限");}else{System.out.println("没有insert权限");}}catch(UnknownAccountException e){// TODO: handle exceptionSystem.out.println("用户名不存在...");}catch(IncorrectCredentialsException e){// TODO: handle exceptionSystem.out.println("密码错误...");}catch(AuthenticationException e){System.out.println("登陆失败...");}}}

注意:使用subject.checkPermission(“user:insert”);没有权限则会抛异常

【授权】

首先现在ini文件里为用户添加相应的角色(zhangsan添加admin、commons角色;lisi添加commons角色)

[users]
zhangsan=z3,admin,commons
lisi=l4,commons

使用以下方式判断角色:

if(subject.hasRole("commons")){System.out.println("拥有commons角色");}else{System.out.println("没有拥有commons角色");}

完整示例:

publicclassShiroRun{@SuppressWarnings("deprecation")publicstaticvoidmain(String[] args){// 1、获取Shiro初始化 通过ini文件获取用户信息@SuppressWarnings("deprecation")IniSecurityManagerFactory factory =newIniSecurityManagerFactory("classpath:shiro.ini");try{// 通过工厂创建SecurityManagerSecurityManager securityManager = factory.getInstance();SecurityUtils.setSecurityManager(securityManager);// 2、获取Subject对象Subject subject =SecurityUtils.getSubject();// 3、获取外部参数 通过页面获取的用户名和密码 创建token对象AuthenticationToken token =newUsernamePasswordToken("zhangsan","z3");// 4、完成登录
            subject.login(token);System.out.println("登陆成功...");// 5、判断用户角色if(subject.hasRole("commons")){System.out.println("拥有commons角色");}else{System.out.println("没有拥有commons角色");}}catch(UnknownAccountException e){// TODO: handle exceptionSystem.out.println("用户名不存在...");}catch(IncorrectCredentialsException e){// TODO: handle exceptionSystem.out.println("密码错误...");}catch(AuthenticationException e){System.out.println("登陆失败...");}}}

2.5、密码加密

在实际的开发中,一些敏感的信息需要加密,比如说用户的密码,shiro内嵌了很多的加密算法

2.5.1、使用Shiro进行加密

publicclassShiroMD5{publicstaticvoidmain(String[] args){String salt ="salt";// 1、密码明文String password ="z3";// 2、使用MD5加密Md5Hash MD5 =newMd5Hash(password);System.out.println("使用MD5加密后的密码:"+ MD5.toHex());// 3、给MD5加盐值 在加密玩的再次拼接一段字符串Md5Hash MD5_2 =newMd5Hash(password, salt);System.out.println("使用MD5(带盐值)加密后的密码:"+ MD5_2.toHex());// 3、给MD5加盐值 多次加密Md5Hash MD5_3 =newMd5Hash(password, salt,3);System.out.println("使用MD5(带盐值三次加密)加密后的密码:"+ MD5_3.toHex());}}

在这里插入图片描述

三、Shiro整合SpringBoot

3.1、整合依赖

<projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>com.wei</groupId><artifactId>dhcc_ShiroProject</artifactId><version>0.0.1-SNAPSHOT</version></parent><artifactId>dhcc_ShiroSpringBoot</artifactId><properties><java.version>1.8</java.version><spring.shiro.version>1.9.0</spring.shiro.version></properties><dependencies><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><scope>test</scope></dependency><!-- shiro --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId><version>${spring.shiro.version}</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><!--页面模板依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!--热部署依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><scope>runtime</scope></dependency><!-- MyBatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.0.5</version></dependency><!-- MySQL --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

3.2、yml配置文件

mybatis-plus:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*Mapper.xmlspring:datasource:type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shirodb?characterEncoding=UTF-8&useUnicode=true&useSSL=false&serverTimezone=UTCusername: root
    password:123456jackson:date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
shiro:loginUrl: /shiroController/login

3.3、创建目录结构

在这里插入图片描述

3.4、创建数据库

打开SqlYog工具创建数据库shirodb

USE`shirodb`;DROPTABLEIFEXISTS`user`;CREATETABLE`user`(`id`bigintNOTNULLAUTO_INCREMENTCOMMENT'主键id',`name`varchar(50)DEFAULTNULLCOMMENT'用户名',`password`varchar(50)DEFAULTNULLCOMMENT'密码',`role_id`bigintDEFAULTNULLCOMMENT'角色编号',PRIMARYKEY(`id`))ENGINE=InnoDBDEFAULTCHARSET=utf8mb3;

3.5、创建对应的类

【实体类User】

@DatapublicclassUser{privateLong id;privateString name;privateString password;privateLong roleId;}

【respority数据持久层】

@RepositorypublicinterfaceUserMapperextendsBaseMapper<User>{}

采用MyBatis-PLUS的通用Mapper

【Service数据服务层(业务层)】

// 接口publicinterfaceUserService{UsergetUserInfoByName(String name);}// 实现类@ServicepublicclassUserServiceImplimplementsUserService{@ResourceprivateUserMapper userMapper;publicUsergetUserInfoByName(String name){QueryWrapper<User> queryWrapper =newQueryWrapper<User>();
        queryWrapper.eq("name", name);User user = userMapper.selectOne(queryWrapper);return user;}}

【controller控制层】

@Controller@RequestMapping("/shiro")publicclassUserController{@RequestMapping(value ="/login", method =RequestMethod.GET)@ResponseBodypublicStringlogin(@RequestParam("name")String name,@RequestParam("password")String password){// 1、获取Subject对象Subject subject =SecurityUtils.getSubject();// 2、 封装请求对象到Token对象UsernamePasswordToken token =newUsernamePasswordToken(name,password);try{// 3、调用Subject的login方法完成登录
            subject.login(token);return"登陆成功!";}catch(UnknownAccountException e){
            e.printStackTrace();System.out.println("用户名不存在...");return"用户名错误,登陆失败";}catch(IncorrectCredentialsException e){
            e.printStackTrace();System.out.println("密码错误...");return"密码错误,登陆失败!";}catch(AuthenticationException e){
            e.printStackTrace();System.out.println("登陆失败...");return"登陆失败";}}}

【Shiro的自定义授权配置类】

@ComponentpublicclassMyShiroRealmextendsAuthorizingRealm{@ResourceprivateUserServiceUserService;/**
     * 用户的登录信息 自定义授权方法
     */@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principals){// TODO Auto-generated method stubreturnnull;}/**
     * 自定义登录认证方法 token
     */@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationToken token)throwsAuthenticationException{// 获取登录用户的信息String name = token.getPrincipal().toString();// 获取用户名//        String password = new String((char[]) token.getCredentials()); // 获取密码// 调用业务层的接口获取数据库的用户信息User user =UserService.getUserInfoByName(name);if(user !=null){// 封装数据AuthenticationInfo info =newSimpleAuthenticationInfo(
                                    token.getPrincipal().toString(),
                                    user.getPassword(),ByteSource.Util.bytes("salt"),
                                    token.getPrincipal().toString());return info;}returnnull;}}

这里需要继承一下AuthorizingRealm,重写参数为token的方法,实现用户的授权登录功能

3.6、创建Shiro配置类

3.6.1、自定义Shiro配置类

要想实现自定义的Shiro配置类,需要创建一个DefaultSecurityManager的方法,在里面去重新自定义授权功能。

@ConfigurationpublicclassShiroConfig{@ResourceprivateMyShiroRealm shiroRealm;/**
    * 描述:TODO(这里用一句话描述这个方法的作用) 
    * @Title: 创建默认的安全管理器
    * @return
    * @author weiyongpeng
    * @date  2022年10月3日 上午8:15:52
     */@BeanpublicDefaultWebSecurityManagerdefaultWebSecurityManager(){// 1、创建DefaultWebSecurityManagerDefaultWebSecurityManager manager =newDefaultWebSecurityManager();// 2、创建加密对象 设置加密属性HashedCredentialsMatcher matcher =newHashedCredentialsMatcher();// 3、将加密对象存储到MyShiroRealm 采用MD5 迭代次数3
        matcher.setHashAlgorithmName("MD5");
        matcher.setHashIterations(3);
        shiroRealm.setCredentialsMatcher(matcher);// 4将MyShiroRealm存储到DefaultSecurityManager
        manager.setRealm(shiroRealm);// 5、返回DefaultSecurityManagerreturn manager;}}

3.6.2、自定义Shiro拦截范围

@BeanpublicDefaultShiroFilterChainDefinitionshiroFilterChainDefinition(){DefaultShiroFilterChainDefinition filter =newDefaultShiroFilterChainDefinition();// 设置不忍证可以访问的资源
    filter.addPathDefinition("/shiro/login","anon");
    filter.addPathDefinition("/login","anon");// 设置需要进行登录才可以访问的拦截范围
    filter.addPathDefinition("/**","authc");return filter;}

3.7、测试

使用APIFOX测试登录

【登陆成功】

在这里插入图片描述

【登陆失败】

在这里插入图片描述

3.8、登录认证前端

使用Thymeleaf实现前端的登陆页面

【引入依赖】

<!--页面模板依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>

【添加配置】

thymeleaf:cache:falseprefix: classpath:/templates/
    suffix: .html
    encoding: UTF-8mode: HTML5

【编写页面】

登陆页面

<!DOCTYPEhtml><htmllang="en"xmlns:th="http://www.thymeleaf.org"><head><metacharset="UTF-8"><title>登录</title></head><body><h1>Shiro登录认证</h1><formth:action="@{/shiro/userLogin}"method="post"><div><label>用户名:</label><inputtype="text"name="name"placeholder="请输入用户名:"></div><div><label>用户名:</label><inputtype="password"name="password"placeholder="123456"></div><divclass="buttonDiv"><inputtype="reset"value="重置"><inputtype="submit"value="登录"></div></form></body></html>

登陆成功首页

<!DOCTYPEhtml><htmllang="en"xmlns:th="http://www.thymeleaf.org"><head><metacharset="UTF-8"><title>登录首页</title></head><body>
登陆的用户:<spanth:text="${session.user}"></span></body></html>

登陆失败错误页

<!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>Insert title here</title></head><body><h1>登陆失败错误页面</h1><h3>
    对不起,你在登陆的时候遇到了<spanth:text="${errorMsg}"></span>的错误
    <ath:href="@{/shiro/login}">重新登陆</a></h3></body></html>

修改controller的代码

@RequestMapping(value ="/login",method =RequestMethod.GET)publicStringlogin(){return"login";}@RequestMapping(value ="/userLogin", method =RequestMethod.POST)publicStringuserLogin(@RequestParam("name")String name,@RequestParam("password")String password,HttpSession session,Model model){// 1、获取Subject对象Subject subject =SecurityUtils.getSubject();// 2、 封装请求对象到Token对象UsernamePasswordToken token =newUsernamePasswordToken(name,password);try{// 3、调用Subject的login方法完成登录
            subject.login(token);// 放入session
            session.setAttribute("user", token.getPrincipal().toString());return"main";}catch(UnknownAccountException e){
            e.printStackTrace();System.out.println("用户名不存在...");
            model.addAttribute("errorMsg","用户名错误,登陆失败");return"error";}catch(IncorrectCredentialsException e){
            e.printStackTrace();System.out.println("密码错误...");
            model.addAttribute("errorMsg","密码错误,登陆失败!");return"error";}catch(AuthenticationException e){
            e.printStackTrace();System.out.println("登陆失败...");
            model.addAttribute("errorMsg","登陆异常,登陆失败!");return"error";}}

四、多个Realm登录校验

  1. 多个Realm实现原理

当应用程序配置多个Realm时,例如,用户名密码校验,手机号校验,邮箱校验等等。Shiro的ModularRealmAuthentication会使用内部的AuthenticationStarategy组件判断认证是否成功或者谁败。

AuthenticationStrategy是一个无状态的组件,它本身验证尝试中被询问4次(这4次交互所需的任何必须的状态将被作为方法参数)

(1)在所有的Realm被调用之前

(2)在调用Realm的getAuthenticationInfo()方法之前

(3)在调用Realm的getAuthenticationInfo()方法之后

(4)在所有的Realm被调用之后

五、rememberMe功能

Shiro提供了记住我的(Remember Me)功能,用户可以在登陆成功后,下次访问页面无需再次登录仍然可以访问。

5.1、基本流程

  1. 首先在登陆的页面选中Remember Me然后再登陆成功后,如果是浏览器登录,一般会把Remember Me的Cookie写道客户端并保存。
  2. 关闭浏览器再次重新打开,会发现浏览器还是记住你。
  3. 访问一般的网页服务器,仍然知道你是谁,且能正常访问。
  4. 但是,如果我们访问电商平台,如果要查看我的订单或者进行支付,此事还需再次进行身份的认证。

5.2、代码实现

5.2.1、设置记住我

在配置类里的安全管理器方法里添加记住我功能

在这里插入图片描述

5.2.2、配置记住我管理器以及Cookie属性

// Cookie的属性设置publicSimpleCookierememberCookie(){SimpleCookie cookie =newSimpleCookie("rememberMe");// 设置跨域//    cookie.setDomain(domain);
    cookie.setPath("/");
    cookie.setHttpOnly(true);
    cookie.setMaxAge(30*24*60*60);// 30天return cookie;}//创建CookieMaangerpublicCookieRememberMeManagerrememberMeManager(){CookieRememberMeManager manager  =newCookieRememberMeManager();
    manager.setCookie(rememberCookie());
    manager.setCipherKey("1234567890987654".getBytes());return manager;}

5.2.3、添加用户过滤器

保证在登陆成功后,Shiro将登陆成功的用户信息放入到cookie中存储

在这里插入图片描述

5.2.4、改造Controller登录接口

// 2、 封装请求对象到Token对象 开启RememberUsernamePasswordToken token =newUsernamePasswordToken(name,password,rememberMe);

5.2.5、改造登陆页面

<div>记住我:<inputtype="checkbox"name="rememberMe"value="true"></div>

在这里插入图片描述

六、用户登出

用户登陆之后,配套的操作有登出操作,直接通过Shiro过滤器即可以实现

6.1、代码实现

【过滤器】

@BeanpublicDefaultShiroFilterChainDefinitionshiroFilterChainDefinition(){DefaultShiroFilterChainDefinition filter =newDefaultShiroFilterChainDefinition();// 设置不认证可以访问的资源
        filter.addPathDefinition("/shiro/userLogin","anon");
        filter.addPathDefinition("/shiro/login","anon");// 配置登出操作
        filter.addPathDefinition("/logout","logout");// 设置需要进行登录认证才可以访问的拦截范围
        filter.addPathDefinition("/**","authc");// 添加remember的用户
        filter.addPathDefinition("/**","user");return filter;}

【登陆后的页面改造】

在这里插入图片描述

七、授权、角色认证

7.1、角色认证

用户登录后,需要验证是否具有指定角色权限,Shiro也提供了方便的工具进行判断,

这个工具就是Realm的doGetAuthenticationinfo方法进行判断,出发权限判断的有两种方式

  1. 在页面中通过shiro:xxxx属性判断
  2. 在接口中通过注解@Requiresxxxxx判断

7.1.1、后端接口服务注解🔥🔥🔥

同过给接口方法添加注解可以实现权限校验,可以加载控制器上,也可以加载业务方法上,

一般加载控制器方法上

,常用的注解如下:

  1. @RequiresAuthentication

验证用户是否登录,等同于方法subject.isAuthenticated();

  1. @RequiresUser

验证用户是否记忆:

登录认证成功subject.isAuthenticated()为true

登录后被记忆subjec.isRemembered()为true

  1. @RequiresGuest

验证是否是一个Guest请求,是否是游客的请求

此时subject.getPrincipal()为null

  1. @RequiresRoles

验证subject是否有相应的角色,有角色访问方法,否则会抛出异常

AuthentizationException

例如:@RequiresRoles(“aRoleName”)

void someMethod();

只有subject有aRoleName角色才能访问方法someMethod()

  1. @RequiresPremissions🔥🔥

验证subject是否具有相应的权限,有权限访问方法,没有则抛出异常

AuthorizationException。

例如:@RequiresPermissions(“USER_SERVICE:QUERY”,“USER_SERVICE:MODIFY”);

void someMethod();

subject只有同时具有

USER_SERVICE:QUERY

,

USER_SERVICE:MODIFY

权限才可以访问someMethod()方法

7.1.2、授权验证-没有角色无法访问

【首先在Controller层写一个方法】

@RequiresRoles("admin")@RequestMapping(value ="/userLoginRoles",method =RequestMethod.GET)@ResponseBodypublicStringuserLoginRoles(){System.out.println("登陆验证表示");return"验证角色成功";}

注意加上注解@RequiresRoles

当我们去访问这个接口的时候,如果这时候还有没做任何的操作,肯定会被抛出异常,如下图所示:

在这里插入图片描述

7.1.3、授权验证-有角色

在自定义的Realm类里重写的doGetAuthorizationInfo定义角色授权信息

@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principals){// 进行授权信息System.out.println("进入自定义授权方法");// 有权限放行// 1、 创建存储信息的对象SimpleAuthorizationInfo info =newSimpleAuthorizationInfo();// 2、存储角色信息 正常是从数据库里获取
    info.addRole("admin");// 3、返回角色信息return info;}

在这里插入图片描述

7.1.4、创建角色表

在实际的业务开发中,我们要想完成角色的认证授权,不能向上述一样,需要在数据库中获取相关的用户权限角色信息。

【权限表】

请添加图片描述

【创建权限实体类】

publicclassRole{/**
     * 权限编号
     */privateLong id;/**
     * 角色名称
     */privateString name;/**
     * 描述
     */privateString desc;/**
     * 显示名称
     */privateString realName;}

【用户角色表】

请添加图片描述

【mapper的查询用户角色】

@Select("SELECT NAME FROM role r WHERE r.`id` IN (\r\n"+"   SELECT ru.`role_id` FROM role_user ru WHERE ru.`user_id`=(\r\n"+"      SELECT u.`role_id` FROM `user` u WHERE u.`name` = #{principal}  \r\n"+"   )\r\n"+")")List<String>getUserRolesInfoMapper(@Param("principal")String principal);

【Service接口方法】

List<String>getUserRolesInfo(String name);

【自定义MyShiroRealm】

@ResourceprivateUserServiceUserService;/**
     * 用户的登录信息 自定义授权方法
     */@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principals){// 进行授权信息System.out.println("进入自定义授权方法");// 有权限放行// 1、 创建存储信息的对象SimpleAuthorizationInfo info =newSimpleAuthorizationInfo();// 2、存储角色信息 正常是从数据库里获取//        info.addRole("admin");List<String> rolesInfo =UserService.getUserRolesInfo(principals.getPrimaryPrincipal().toString());
        rolesInfo.forEach(System.out::println);
        info.addRoles(rolesInfo);// 3、返回角色信息return info;}

7.2、授权访问

获取权限进行验证,首先是创建权限资源表

CREATETABLE`shirodb`.`premissions`(`id`BIGINT(20)NOTNULLAUTO_INCREMENTCOMMENT'编号',`name`VARCHAR(50)COMMENT'权限名称',`info`VARCHAR(30)COMMENT'权限信息',`desc`VARCHAR(50)COMMENT'描述',PRIMARYKEY(`id`))ENGINE=INNODBCHARSET=utf8 COLLATE=utf8_general_ci;

同理角色认证的编写,首先要在添加相应的接口方法查询数据库中对应角色下的权限集合。

【mapper】

@Select({"<script>","select info from premissions where id in ","(select permissions_id from role_ps where role_id in (","select id from role where name in ","<foreach collection='roles' item='name' open='(' separator=',' close=')'>","#{name}","</foreach>","))","</script>"})List<String>getUserPermissionsInfoMapper(@Param("roles")List<String> roles);

【Service接口】

List<String>getUserPermisssionsInfo(List<String> roles);

【Controller接口】

@RequiresPermissions(value ={"user:update","user:delete","user:add"})@RequestMapping(value ="/userLoginPermissions",method =RequestMethod.GET)@ResponseBodypublicStringuserLoginPermissions(){System.out.println("登陆权限验证标识");return"验证权限成功";}

注意🔥@RequiresPermissions注解的参数value是一个数组

最后要想真正能够的实现功能,还需要再自定义的Realm类里定义权限认证。

@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principals){// 进行授权信息System.out.println("进入自定义授权方法");// 有权限放行// 1、 创建存储信息的对象SimpleAuthorizationInfo info =newSimpleAuthorizationInfo();// 2、存储角色信息 正常是从数据库里获取//        info.addRole("admin");// 3、获取用户角色信息List<String> rolesInfo =UserService.getUserRolesInfo(principals.getPrimaryPrincipal().toString());// 3、获取用户的权限信息List<String> permisssionsInfo =UserService.getUserPermisssionsInfo(rolesInfo);
        permisssionsInfo.forEach(System.out::println);
        rolesInfo.forEach(System.out::println);// 4、存储到info、对象中
        info.addStringPermissions(permisssionsInfo);// 权限信息
        info.addRoles(rolesInfo);// 角色信息// 5、返回角色信息return info;}

八、自定义异常

创建认证异常处理类,使用@ControllerAdvice加@ExceptionHandler注解实现特殊异常处理

@ControllerAdvicepublicclassPermissionsException{/**
    * 描述:没有权限自定义异常
    * @Title: doNoPermissionException
    * @return
    * @author weiyongpeng
    * @date  2022年10月4日 上午8:21:39
     */@ResponseBody@ExceptionHandler(UnauthorizedException.class)publicStringdoNoPermissionException(Exception e){return"对不起,您无权限访问";}/**
    * 描述:无身份认证异常
    * @Title: doNoAuthenticationException
    * @param e
    * @return
    * @author weiyongpeng
    * @date  2022年10月4日 上午8:23:26
     */@ResponseBody@ExceptionHandler(AuthenticationException.class)publicStringdoNoAuthenticationException(Exception e){return"对不起,权限认证失败";}}

九、前端角色权限认证

前面虽然说已经实现了基本的功能,但是在用户体验上效果不是很好,我们在平常的系统中,比如说银行的管理系统、绩效系统。行内人员有着不同的权限,比如说行长有全权限,经理有调动查看账务的权限。我们想要的效果是:不同权限的用户登录系统后,看到的界面根据所拥有的权限显示。

那么接下来,我们来实现一把:

9.1、引入依赖

因为我们使用的前端框架是Thymeleaf,所以:

<!-- 配置Thyemleaf与Shiro的整合依赖 --><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>2.0.0</version></dependency>

配置Shiro的标签配置

// 配置类里配置解析Shiro标签的配置方法@BeanpublicShiroDialectshiroDialect(){returnnewShiroDialect();}

在Thymeleaf中常用的Shiro标签属性:

shiro:

引入完成后,直接启动应用会报错,因为thymeleaf-extras-shiro这个组件需要thymeleaf3.0支持,所以这里不再演示。

十、实现缓存

10.1、缓存工具Ehcache

Ehchche是一种广泛使用的开源Java分布式缓存框架,住哟啊面向的是通用缓存,JavaEE是轻量级的缓存容器。可以和大部分的Java项目无缝融合。例如Hibernate中使用的缓存技术就是Ehcache

Ehcache支持磁盘和内存中的存储,如果内存不够,可以放到磁盘中,也可以直接在JVM虚拟机中做缓存,高效,速度快,但是共享麻烦,分布式集群中不方便,主要做的是本地缓存。

10.2、Shiro整合Ehcache

Shiro官网提供了Shiro-ehcache的整合方案,减少对数据库的访问,提高项目的执行效率。

【添加依赖】

<!-- Shiro整合Ehcache框架 --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.4.2</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.6</version></dependency>

【配置类配置缓存管理器】

/**
    * 描述:获取ehcache缓存管理器
    * @Title: getEhcacheManager
    * @return
    * @author weiyongpeng
    * @date  2022年10月4日 上午9:12:36
     */@BeanpublicEhCacheManagergetEhCacheManager(){EhCacheManager ehCacheManager =newEhCacheManager();InputStream stream =null;try{
            stream =ResourceUtils.getInputStreamForPath("classpath:ehcache/shiro-ehcache.xml");}catch(Exception e){// TODO: handle exception
            e.printStackTrace();}CacheManager cacheManager =newCacheManager(stream);
        ehCacheManager.setCacheManager(cacheManager);return ehCacheManager;}

请添加图片描述


本文转载自: https://blog.csdn.net/mmklo/article/details/127159549
版权归原作者 @WAT 所有, 如有侵权,请联系我们删除。

“Shiro安全框架【SpringBoot版】”的评论:

还没有评论