Apache shiro框架

一、初识shiro框架

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

一、主要功能

三个核心组件:Subject, SecurityManager 和 Realms.

Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。

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实现。

基本功能点

  • Authentication:身份认证 / 登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  • Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通 JavaSE 环境的,也可以是如 Web 环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  • Web Support:Web 支持,可以非常容易的集成到 Web 环境;
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色 / 权限不必每次去查,这样可以提高效率;
  • Concurrency:shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了

特点

  • 易于理解的 Java Security API
  • 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等)
  • 对角色的简单的签权(访问控制),支持细粒度的签权
  • 支持一级缓存,以提升应用程序的性能
  • 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境
  • 异构客户端会话访问
  • 非常简单的加密 API
  • 不跟任何的框架或者容器捆绑,可以独立运行

二、基本使用

1、环境准备

1、Shiro不依赖容器,创建maven工程 / springboot项目都可

2、添加shiro依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.1</version>
        </dependency>

2、INI 文件

Shiro 获取权限相关信息可以通过数据库获取,也可以通过 ini 配置文件获取

[users]
zhangsan=z3
lisi=l4

3、登录认证

  • 身份验证:一般需要提供如身份ID等一些标识信息来表明登录者的身份,如提供 email,用户名/密码来证明。
  • 在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,从 而应用能验证用户身份。
  • principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一 即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/ 邮箱/手机号。
  • credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。
  • 最常见的principals和credentials组合就是用户名/密码

登录认证基本流程

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

登录认证实例

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

package com.syh.shiro.text;

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.subject.Subject;

public class Role{
    public static void main(String[] args) {
        //1 初始化获取 SecurityManager
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:static/shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        SecurityUtils.setSecurityManager(securityManager);
        //2 获取 Subject 对象
        Subject subject = SecurityUtils.getSubject();
        //3 创建 token 对象,web 应用用户名密码从页面传递
        AuthenticationToken token = new UsernamePasswordToken("zhangsan","z3");
        //4 完成登录
        try {
            subject.login(token);
            System.out.println("登录成功");
        }
        catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("用户不存在");
        }
        catch (IncorrectCredentialsException e) {
            e.printStackTrace();
            System.out.println("密码错误");
        }
        catch (AuthenticationException ae) {
            //unexpected condition? error?
        }
    }
}

身份认证流程

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

4、角色、授权

授权概念

  1. 授权:也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面 操作 等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权 限 (Permission)、角色(Role)。
  2. 主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只 有授权 后才允许访问相应的资源。
  3. 资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑 某些 数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
  4. 权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中 用户 有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访 问用 户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权 限控 制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允 不允 许。
  5. Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权 限, 即实例级别的)
  6. 角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可 以拥有 一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工 程师等 都是角色,不同的角色拥有一组不同的权限

授权方式

(1)编程式:通过写if/else 授权代码块完成

(2)注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相 应的异 常 

 

授权流程 

  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表示授权失败

授权实例

1、获取角色信息

(1)给shiro.ini增加角色配置

[users]
zhangsan=z3,role1,role2
lisi=l4

(2)上述示例代码中添加代码,沟通过hasRole()判断用户是否有指定角色

try {
   subject.login(token);
   System.out.println("登录成功");

   // 沟通过hasRole()判断用户是否有指定角色
   boolean role1 = subject.hasRole("role1");
   System.out.println("是否拥有此角色:" + role1);
}

2、判断权限信息信息

(1)给shiro.ini增加权限配置 

[roles]
role1=user:insert,user:select

(2)给例子添加代码,判断用户是否有指定权限

//判断权限信息信息
boolean isPermitted = subject.isPermitted("user:insert");
System.out.println("是否拥有此权限:"+isPermitted);
//也可以用 checkPermission 方法,但没有返回值,没权限抛 AuthenticationException
subject.checkPermission("user:select");

5、Shiro 加密

实际系统开发中,一些敏感信息需要进行加密,比如说用户的密码。Shiro 内嵌很多 常用的加密算法,比如 MD5 加密。Shiro 可以很简单的使用信息加密。

package com.syh.shiro.text;

import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.crypto.hash.SimpleHash;

public class ShiroJM {

    public static void main(String[] args) {
        //密码明文
        String password = "z3";
        //使用 md5 加密
        Md5Hash md5Hash = new Md5Hash(password);
        System.out.println("md5 加密:"+md5Hash.toHex());
        //带盐的 md5 加密,盐就是在密码明文后拼接新字符串,然后再进行加密
        Md5Hash md5Hash2 = new Md5Hash(password,"salt");
        System.out.println("md5 带盐加密:"+md5Hash2.toHex());
        //为了保证安全,避免被破解还可以多次迭代加密,保证数据安全
        Md5Hash md5Hash3 = new Md5Hash(password,"salt",3);
        System.out.println("md5 带盐三次加密:"+md5Hash3.toHex());
        //使用父类实现加密
        SimpleHash simpleHash = new SimpleHash("MD5",password,"salt",3);
        System.out.println("父类带盐三次加密:"+simpleHash.toHex());
    }
}

6、Shiro 自定义登录认证

Shiro 默认的登录认证是不带加密的,如果想要实现加密认证需要自定义登录认证, 自定义 Realm。

 (1)自定义登录认证

package com.syh.shiro.text;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.util.ByteSource;
import org.springframework.stereotype.Component;

@Component
public class MyRealm extends AuthenticatingRealm {
    //自定义的登录认证方法,Shiro 的 login 方法底层会调用该类的认证方法完成登录认证
    //需要配置自定义的 realm 生效,在 ini 文件中配置,或 Springboot 中配置
    //该方法只是获取进行对比的信息,认证逻辑还是按照 Shiro 的底层认证逻辑完成认证
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authenticationToken) throws
            AuthenticationException {
        //1 获取身份信息
        String principal = authenticationToken.getPrincipal().toString();
        //2 获取凭证信息
        String password = new String((char[])
                authenticationToken.getCredentials());
        System.out.println("认证用户信息:"+principal+"---"+password);
        //3 获取数据库中存储的用户信息
        if(principal.equals("zhangsan")){
            //3.1 数据库存储的加盐迭代 3 次密码
            String pwdInfo = "7174f64b13022acd3c56e2781e098a5f";
            //3.2 创建封装了校验逻辑的对象,将要比较的数据给该对象
            AuthenticationInfo info = new SimpleAuthenticationInfo(
                    authenticationToken.getPrincipal(),
                    pwdInfo,
                    ByteSource.Util.bytes("salt"),
                    authenticationToken.getPrincipal().toString());
            return info;
        }
        return null;
    }
}

(2)在shiro.ini中添加配置信息

配置信息中有些为自定义登录认证配置类的路径信息

[main]
md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3

myrealm=com.syh.shiro.text.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm

[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1,role2
lisi=l4
[roles]
role1=user:insert,user:select

(3)启动登录认证示例

三、Spring Boot 整合 shiro

1、创建springboot项目

(1)添加依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mp-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.1</version>
        </dependency>
    </dependencies>

(2)目录结构

 

2、登录认证实现

访问数据库获取用户信息,实现登录认证.

 (1)数据库

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户名',
  `pwd` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '密码',
  `r_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色编号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('11', 'zhangsan', '7174f64b13022acd3c56e2781e098a5f', '1111');

SET FOREIGN_KEY_CHECKS = 1;

(2)实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User {

    @TableId("id")
    private Integer id;

    @TableField("name")
    private String name;

    @TableField("pwd")
    private String pwd;

    @TableField("rid")
    private Integer rid;
}

(3)service业务

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /* 根据用户名称获取用户信息 */
    @Override
    public User getUserInfoByName(String name) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getName, name);
        return userMapper.selectOne(wrapper);
    }
}

(4)自定义realm

/*
* 自定义认证授权方法
* */
@Component
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;
    
    //自定义授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    //自定义登录认证方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1 获取用户身份信息
        String name = token.getPrincipal().toString();
        //2 调用业务层获取用户信息(数据库中)
        User user = userService.getUserInfoByName(name);
        //3 判断并将数据完成封装
        if(user!=null){
            AuthenticationInfo info = new SimpleAuthenticationInfo(
                    token.getPrincipal(),
                    user.getPwd(),
                    ByteSource.Util.bytes("salt"),
                    token.getPrincipal().toString()
            );
            return info;
        }
        return null;
    }
}

(5)编写配置类

@Configuration
public class ShiroConfig {

    @Autowired
    private MyRealm myRealm;

    //配置 SecurityManager
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        //1 创建 defaultWebSecurityManager 对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //2 创建加密对象,并设置相关属性
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //2.1 采用 md5 加密
        matcher.setHashAlgorithmName("md5");
        //2.2 迭代加密次数
        matcher.setHashIterations(3);
        //3 将加密对象存储到 myRealm 中
        myRealm.setCredentialsMatcher(matcher);
        //4 将 myRealm 存入 defaultWebSecurityManager 对象
        defaultWebSecurityManager.setRealm(myRealm);
        //5 返回
        return defaultWebSecurityManager;
    }


    // 过滤器
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        // 拦截配置
        Map<String, String> filter = new LinkedHashMap<>();
        // 登录接口放行
        filter.put("/api/UserController/userLogin", "anon");
        // 需要拦截的资源
        filter.put("/**", "authc");
        shiroFilter.setFilterChainDefinitionMap(filter);
        return shiroFilter;
    }

        /*
        * 以后会用到的几个:
        * anon  无需认证即可访问
        * logout 退出清除HTTPSession数据
        * authc  需要认证才可访问
        * user user是介于,anon和authc直之间的。
        *      换句话来说:而“/authenticated= user”
        *      表示访问该地址的用户是身份验证通过或RememberMe 登录的都可以。
        * */

}

(6)实现登录接口

@RestController
@RequestMapping("/api/UserController")
@Slf4j
public class UserController {
    // http://localhost:8080/api/UserController/userLogin?name=zhangsan&pwd=z3
    @GetMapping("/userLogin")
    public String userLogin(String name, String pwd){
        //1 获取 Subject 对象
        Subject subject = SecurityUtils.getSubject();
        //2 封装请求数据到 token 对象中
        AuthenticationToken token = new UsernamePasswordToken(name, pwd);
        //3 调用 login 方法进行登录认证
        try {
            subject.login(token);
            return "登录成功";
        } catch (AuthenticationException e) {
            log.error("登录失败");
            return "登录失败";
        }
    }

}

3、多个 realm 的认证策略设置

多个realm实现原理

Realm验证,有时候会存在多Realm,不同的角色会有不同的验证逻辑,这个时候会需要多Realm。Shiro 的 ModularRealmAuthenticator 会使用内部的 AuthenticationStrategy 组件判断认 证是成功还是失败。

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

  1. 在所有 Realm 被调用之前
  2. 在调用 Realm 的 getAuthenticationInfo 方法之前
  3. 在调用 Realm 的 getAuthenticationInfo 方法之后
  4. 在所有 Realm 被调用之后

认证策略的另外一项工作就是聚合所有 Realm 的结果信息封装至一个 AuthenticationInfo 实例中,并将此信息返回,以此作为 Subject 的身份信息。

Shiro 中定义了 3 种认证odularRealmAuthenticat策略的实现,Mor 内置的认证策略默认实现是 AtLeastOneSuccessfulStrategy 方式。可以通过配置修改策略。

AuthenticationStrategy class
描述
AtLeastOneSuccessfulStrategy
只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功
FirstSuccessfulStrategy
第一个 Realm 验证成功,整体认证将视为成功,且后续 Realm 将被忽略
AllSuccessfulStrategy
所有 Realm 成功,认证才视为成功

多个realm代码实现

标题2已经配置一个realm名字为MyRealm,下面继续创建Customer2Realm

@Component
public class Customer2Realm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // userName
        String principal = (String) authenticationToken.getPrincipal();
        // password
        // String credentials = (String) authenticationToken.getCredentials();
        // 根据用户名获取用户信息
        User user = userService.getUserInfoByName("zhangsan1");
        if(user != null){
            System.out.println("app认证成功");
            return new SimpleAuthenticationInfo(user, user.getPwd(), ByteSource.Util.bytes("salt"), principal);
        }else{
            return null;
        }
    }
}

 配置 SecurityManager,添加Customer2Realm(新添加3、5、5.1步)

    // 认证授权方法一
    @Autowired
    private MyRealm myRealm;

    // 认证授权方法二
    @Autowired
    private Customer2Realm customer2Realm;

    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager() {
        //1 创建 defaultWebSecurityManager 对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //2 创建认证对象,并设置认证策略
        ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
        //3 只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功   默认值
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);

        //3 创建加密对象,并设置相关属性
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //3.1 采用 md5 加密
        matcher.setHashAlgorithmName("md5");
        //3.2 迭代加密次数
        matcher.setHashIterations(3);
        //4 将加密对象存储到 myRealm 中
        myRealm.setCredentialsMatcher(matcher);

        //5 封装 myRealm 集合
        List<Realm> list = new ArrayList<>();
        list.add(myRealm);
        //5.1 封装customer2Realm
        list.add(customer2Realm);

        //6 将 myRealm 存入 defaultWebSecurityManager 对象
        defaultWebSecurityManager.setRealms(list);
        //6.5 设置 rememberMe(Shiro 提供了记住我(RememberMe)的功能)
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
        //7 返回
        return defaultWebSecurityManager;
    }

4、remember me(记住我功能)

Shiro 提供了记住我(RememberMe)的功能,比如访问一些网站时,关闭了浏览器, 下次再打开时还是能记住你是谁, 下次访问时无需再登录即可访问。

基本流程

(1) 首先在登录页面选中 RememberMe 然后登录成功;如果是浏览器登录,一般会 把 RememberMe 的 Cookie 写到客户端并保存下来;

(2) 关闭浏览器再重新打开;会发现浏览器还是记住你的;

(3) 访问一般的网页服务器端,仍然知道你是谁,且能正常访问;

(4) 但是,如果我们访问电商平台时,如果要查看我的订单或进行支付时,此时还 是需要再进行身份认证的,以确保当前用户还是你。

代码实现

(1)修改配置类 ShiroConfig,在defaultWebSecurityManager方法放的6步后面添加代码,并且添加两个方法;在shiroFilterFactoryBean方法中添加过滤条件

@Configuration
public class ShiroConfig {

    @Autowired
    private MyRealm myRealm;

    //配置 SecurityManager
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager() {
        //1 创建 defaultWebSecurityManager 对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //2 创建认证对象,并设置认证策略
        ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
        modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
        defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);

        //3 创建加密对象,并设置相关属性
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //3.1 采用 md5 加密
        matcher.setHashAlgorithmName("md5");
        //3.2 迭代加密次数
        matcher.setHashIterations(3);
        //4 将加密对象存储到 myRealm 中
        myRealm.setCredentialsMatcher(matcher);

        //5 封装 myRealm 集合
        List<Realm> list = new ArrayList<>();
        list.add(myRealm);

        //6 将 myRealm 存入 defaultWebSecurityManager 对象
        defaultWebSecurityManager.setRealms(list);
        //6.5 设置 rememberMe(Shiro 提供了记住我(RememberMe)的功能)
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
        //7 返回
        return defaultWebSecurityManager;
    }

    //cookie 属性设置
    public SimpleCookie rememberMeCookie(){
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        //设置跨域
        //cookie.setDomain(domain);
        cookie.setPath("/");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(30*24*60*60);
        return cookie;
    }

    //创建 Shiro 的 cookie 管理对象
    public CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new
                CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());

        cookieRememberMeManager.setCipherKey("1234567890987654".getBytes());
        return cookieRememberMeManager;
    }

    //配置 Shiro 内置过滤器拦截范围
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        // 拦截配置
        Map<String, String> filter = new LinkedHashMap<>();
        // 登录接口放行
        //设置需要进行登录认证的拦截范围
        filter.put("/**", "authc");
        //添加存在用户的过滤器(rememberMe)
        filter.put("/**", "user");
        shiroFilter.setFilterChainDefinitionMap(filter);
        return shiroFilter;
    }

}

(2)修改 controller,新增一个访问参数

package com.syh.shiro.controller;

import com.syh.shiro.Utils.JWT.JwtUtil;
import com.syh.shiro.Utils.relevant.RespStaticEnum;
import com.syh.shiro.Utils.relevant.ResultUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("/api/UserController")
@Slf4j
public class UserController {
    // http://localhost:8080/api/UserController/userLogin/true?name=zhangsan&pwd=z3
    @GetMapping("/userLogin/{rememberMe}")
    public ResultUtils userLogin(String name, String pwd, @PathVariable boolean rememberMe, HttpServletRequest request){
        //1 获取 Subject 对象
        Subject subject = SecurityUtils.getSubject();
        //2 封装请求数据到 token 对象中
        AuthenticationToken token = new UsernamePasswordToken(name, pwd, rememberMe);
        //3 调用 login 方法进行登录认证
        try {
            subject.login(token);
            String jwtToken = JwtUtil.getJwtToken(name, pwd);
            return ResultUtils.success(RespStaticEnum.LOGIN_SUCCESS, jwtToken);
        } catch (AuthenticationException e) {
            log.error("登录失败");
            return ResultUtils.fail(RespStaticEnum.LOGIN_ERROR);
        }
    }

    // http://localhost:8080/api/UserController/get
    /*
    * 测试接口
    * 清理好浏览器cookie,直接访问此方法,会被拦截;
    * 如果先访问登录接口,然后再访问此接口,就可以访问;
    * 此刻关闭浏览器,再打开,然后访问此接口,发现依旧可以访问,这表明浏览器记住了登陆者
    * */
    @GetMapping("/get")
    public String getStr(HttpSession session, HttpServletRequest request) {
        session.setAttribute("user", "rememberMe");
        return "ss";
    }

}

5、登出功能

过滤器中添加如下配置

// 配置登出,请求该地址,shiro清除HTTPSession数据,实现退出登录。
filter.put("/api/UserController/logout", "logout");

6、授权验证-->角色认证

用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判断。这个工具就是Realm的doGetAuthorizationInfo方法进行判断。触发权限判断的有两种方式,这里使用在接口服务中通过注解@Requires****进行判断

通过给接口服务方法添加注解可以实现权限校验,可以加在控制器方法上,也可以加 在业务方法上,一般加在控制器方法上。常用注解如下:

  • @RequiresAuthentication 验证用户是否登录,等同于方法subject.isAuthenticated()
  • @RequiresUser 验证用户是否被记忆: 登录认证成功subject.isAuthenticated()为true 登录后被记忆subject.isRemembered()为true
  • @RequiresGuest 验证是否是一个guest的请求,是否是游客的请求 此时subject.getPrincipal()为null
  • @RequiresRoles 验证subject是否有相应角色,有角色访问方法,没有则会抛出异常 AuthorizationException。 例如:@RequiresRoles(“aRoleName”) void someMethod(); 只有subject有aRoleName角色才能访问方法someMethod()
  • @RequiresPermissions 验证subject是否有相应权限,有权限访问方法,没有则会抛出异常 AuthorizationException。 例如:@RequiresPermissions (“file:read”,”wite:aFile.txt”) void someMethod(); subject必须同时含有file:read和wite:aFile.txt权限才能访问方法someMethod()

(1)数据库

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `r_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名',
  `desc` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`r_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES ('1111', '管理员', '全部权限');

SET FOREIGN_KEY_CHECKS = 1;

(2)实体类

@Getter
@Setter
@TableName("role")
public class Role implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 角色id
     */
    @TableId("r_id")
    private String rId;

    /**
     * 角色名
     */
    @TableField("name")
    private String name;

    /**
     * 描述
     */
    @TableField("desc")
    private String desc;
}

(3)授权校验代码实现

注解生效需要一些配置,在ShiroConfig配置文件中添加如下代码

 /*
    * 如下两个配置可以使shiro的权限校验注解生效
    * */
    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
    //AuthorizationAttributeSourceAdvisor 的作用是匹配所有类 匹配所有加认证注解的方法
    //具体看这篇博客:https://blog.csdn.net/wangjun5159/article/details/51889628
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Autowired DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

(4)MyRealm自定义授权方法

@Component
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    @Autowired
    private IRoleService roleService;

    //自定义授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //1 创建对象,存储当前登录的用户的权限和角色
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //2 获取用户身份信息
        String principal = principalCollection.getPrimaryPrincipal().toString();
        //3 获取用户对应的角色信息
         List<String> roleList = roleService.getRoleName(principal);
        //4 存储角色
        info.addRoles(roleList);
        //5 返回
        return info;
    }

    //自定义登录认证方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1 获取用户身份信息
        String name = token.getPrincipal().toString();
        //2 调用业务层获取用户信息(数据库中)
        User user = userService.getUserInfoByName(name);
        //3 判断并将数据完成封装
        if(user!=null){
            AuthenticationInfo info = new SimpleAuthenticationInfo(
                    token.getPrincipal(),
                    user.getPwd(),
                    ByteSource.Util.bytes("salt"),
                    token.getPrincipal().toString()
            );
            return info;
        }
        return null;
    }
}

(5)controller角色授权验证接口

    /*
    * 角色授权验证接口
    * */
    // 1111:管理员角色
    @RequiresRoles("1111")
    @GetMapping("/userLoginRoles")
    public String userLoginRoles() {
        System.out.println("登录认证验证角色");
        return "验证角色成功";
    }

(6)角色验证失败的返回信息

 数据库中只有1111角色,没有1111222角色

6、授权验证-->角色的权限认证 

(1)数据库

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for permissions
-- ----------------------------
DROP TABLE IF EXISTS `permissions`;
CREATE TABLE `permissions`  (
  `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名',
  `info` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限信息',
  `desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '描述',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of permissions
-- ----------------------------
INSERT INTO `permissions` VALUES ('111111111', 'user:delete', '删除用户', '删除用户信息');
INSERT INTO `permissions` VALUES ('1111111222', 'user:select', '查询用户信息', '查询用户信息');

SET FOREIGN_KEY_CHECKS = 1;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for role_ps
-- ----------------------------
DROP TABLE IF EXISTS `role_ps`;
CREATE TABLE `role_ps`  (
  `id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '编号',
  `rid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色 id',
  `pid` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限 id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色权限映射表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role_ps
-- ----------------------------
INSERT INTO `role_ps` VALUES ('sahksah', '1111', '111111111');

SET FOREIGN_KEY_CHECKS = 1;

(2)实体类

@Getter
@Setter
@TableName("permissions")
public class Permissions implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 编号
     */
    @TableId("id")
    private String id;

    /**
     * 权限名
     */
    @TableField("name")
    private String name;

    /**
     * 权限信息
     */
    @TableField("info")
    private String info;

    /**
     * 描述
     */
    @TableField("desc")
    private String desc;
}
@Getter
@Setter
@TableName("role_ps")
public class RolePs implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 编号
     */
    @TableId("id")
    private String id;

    /**
     * 角色 id
     */
    @TableField("rid")
    private String rid;

    /**
     * 权限 id
     */
    @TableField("pid")
    private String pid;
}

(4)MyRealm授权方法中添加权限信息

/*
* 自定义认证授权方法
* */
@Component
public class MyRealm extends AuthorizingRealm {

    // 用户
    @Autowired
    private UserService userService;

    // 角色
    @Autowired
    private IRoleService roleService;

    // 角色对应的权限
    @Autowired
    private IRolePsService rolePsService;

    //自定义授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //1 创建对象,存储当前登录的用户的权限和角色
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //2 获取用户身份信息
        String principal = principalCollection.getPrimaryPrincipal().toString();
        //3 获取用户对应的角色信息
        List<String> roleList = roleService.getRoleName(principal);
        //4 存储角色
        info.addRoles(roleList);
        //5 获取用户权限信息
        List<String> jurisdictionList = rolePsService.getRolejurisdiction(roleList);
        //6 储存权限信息
        info.addStringPermissions(jurisdictionList);
        //7 返回
        return info;
    }

    //自定义登录认证方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //1 获取用户身份信息
        String name = token.getPrincipal().toString();
        //2 调用业务层获取用户信息(数据库中)
        User user = userService.getUserInfoByName(name);
        //3 判断并将数据完成封装
        if(user!=null){
            AuthenticationInfo info = new SimpleAuthenticationInfo(
                    token.getPrincipal(),
                    user.getPwd(),
                    ByteSource.Util.bytes("salt"),
                    token.getPrincipal().toString()
            );
            return info;
        }
        return null;
    }
}

(5)controller角色的权限认证接口

    /*
    * 角色的权限验证接口
    * */
    @RequiresPermissions("user:delete")
    @GetMapping("/userJurisdiction")
    public String userJurisdiction() {
        System.out.println("角色权限验证");
        return "角色权限验证成功";
    }

7、授权验证-异常处理

集成到springboot全局异常处理中

/*
* 全局异常处理类
* */
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
    
    /*
    * 全局异常捕获
    * */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public ResultUtils javaExceptionHandler(Exception ex){
        return ResultUtils.fail(RespStaticEnum.FAIL, ex.getMessage());
    }

    /*
    * shiro框架权限校验失败异常捕获
    * */
    @ResponseBody
    @ExceptionHandler(UnauthorizedException.class)
    public ResultUtils unauthorizedException(Exception ex){
        log.error("无权限:" + ex.getLocalizedMessage());
        return ResultUtils.fail(RespStaticEnum.FAIL);
    }
    @ResponseBody
    @ExceptionHandler(AuthorizationException.class)
    public ResultUtils authorizationException(Exception ex){
        log.error("权限认证失败:" + ex.getLocalizedMessage());
        return ResultUtils.fail(RespStaticEnum.FAIL);
    }
}

8、Shiro整合EhCache,实现缓存

springboot整合EhCache学习请移步(1条消息) EhCache缓存框架_SUN Y H的博客-CSDN博客

添加依赖

Shiro官方提供了shiro-ehcache,实现了整合EhCache作为Shiro的缓存工具。可以缓 存认证执行的Realm方法,减少对数据库的访问,提高认证效率

        <!--Shiro 整合 EhCache-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.7</version>
        </dependency>

添加shiro配置文件

在 resources 下添加配置文件 ehcache/ehcache-shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="ehcache" updateCheck="false">
 <!--磁盘的缓存位置-->
 <diskStore path="java.io.tmpdir"/>
 <!--默认缓存-->
 <defaultCache
 maxEntriesLocalHeap="1000"
 eternal="false"
 timeToIdleSeconds="3600"
 timeToLiveSeconds="3600"
 overflowToDisk="false">
 </defaultCache>
 <!--登录认证信息缓存:缓存用户角色权限-->
<cache name="loginRolePsCache"
 maxEntriesLocalHeap="2000"
 eternal="false"
 timeToIdleSeconds="600"
 timeToLiveSeconds="0"
 overflowToDisk="false"
 statistics="true"/>
</ehcache>

修改配置类 ShiroConfig

6.6步为新添加,其他为之前代码

 @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager() {
        //1 创建 defaultWebSecurityManager 对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //2 创建认证对象,并设置认证策略
        ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
        //3 只要有一个(或更多)的 Realm 验证成功,那么认证将视为成功   默认值
        modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);

        //3 创建加密对象,并设置相关属性
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //3.1 采用 md5 加密
        matcher.setHashAlgorithmName("md5");
        //3.2 迭代加密次数
        matcher.setHashIterations(3);
        //4 将加密对象存储到 myRealm 中
        myRealm.setCredentialsMatcher(matcher);

        //5 封装 myRealm 集合
        List<Realm> list = new ArrayList<>();
        list.add(myRealm);
        //5.1 封装customer2Realm
        list.add(customer2Realm);

        //6 将 myRealm 存入 defaultWebSecurityManager 对象
        defaultWebSecurityManager.setRealms(list);
        //6.5 设置 rememberMe(Shiro 提供了记住我(RememberMe)的功能)
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
        //6.6 设置缓存管理器
        defaultWebSecurityManager.setCacheManager(getEhCacheManager());
        //7 返回
        return defaultWebSecurityManager;
    }

    //缓存管理器
    public EhCacheManager getEhCacheManager(){
        EhCacheManager ehCacheManager = new EhCacheManager();
        InputStream is = null;
        try {
            is = ResourceUtils.getInputStreamForPath(
                    "classpath:ehcache/ehcache-shiro.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        CacheManager cacheManager = new CacheManager(is);
        ehCacheManager.setCacheManager(cacheManager);
        return ehCacheManager;
    }

代码测试

此处不贴运行结果图了,打开sql日志打印。登录之后,访问权限验证接口,第一次会查询数据库打印sql语句,第二次访问会走缓存,看不到sql语句打印。

缓存更新问题

当某个用户的权限被更新,应该同步更新EhCache中缓存

9、会话管理

SessionManager

会话管理器,负责创建和管理用户的会话(Session)生命周期,它能够在任何环境中 在本地管理用户会话,即使没有Web/Servlet/EJB容器,也一样可以保存会话。默认情况 下,Shiro会检测当前环境中现有的会话机制(比如Servlet容器)进行适配,如果没有(比如独立应用程序或者非Web环境),它将会使用内置的企业会话管理器来提供相应的会 话管理服务,其中还涉及一个名为SessionDAO的对象。SessionDAO负责Session的持久化操 作(CRUD),允许Session数据写入到后端持久化数据库。

会话管理实现

SessionManager由SecurityManager管理。Shiro提供了三种实现

  • DefaultSessionManager:用于JavaSE环境
  • ServletContainerSessionManager:用于web环境,直接使用Servlet容器的会话
  • DefaultWebSessionManager:用于web环境,自己维护会话(不使用Servlet容器的
    会话管理)

获得session方式

  1. 实现 :Session session = SecurityUtils.getSubject().getSession();session.setAttribute(“key”,”value”)
  2. 说明 Controller 中的 request,在 shiro 过滤器中的 doFilerInternal 方法,被包装成 ShiroHttpServletRequest;  SecurityManager 和 SessionManager 会话管理器决定 session 来源于 ServletRequest 还是由 Shiro 管理的会话;  无论是通过 request.getSession 或 subject.getSession 获取到 session,操作 session,两者都是等价的。

四、shiro拦截器

1、shiro默认拦截器

Shiro内置了很多默认的拦截器,比如身份验证、授权等相关的。默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器:

注意:anon, authc, authcBasic, user 是第一组认证过滤器,perms, port, rest, roles, ssl 是第二组授权过滤器,要通过授权过滤器,就先要完成登陆认证操作(即先要完成认证才能前去寻找授权) 才能走第二组授权器(例如访问需要 roles 权限的 url,如果还没有登陆的话,会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的 url
 

shiro默认拦截器
默认拦截器名拦截器类说明(括号里的表示默认值)
身份验证相关的
authc

org.apache.shiro.web.filter.authc

.FormAuthenticationFilter

基于表单的拦截器;如“/**=authc”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username);  passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe);  loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure);
authcBasic

org.apache.shiro.web.filter.authc

.BasicHttpAuthenticationFilter

Basic HTTP身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application);
logout

org.apache.shiro.web.filter.authc

.LogoutFilter

退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/);示例“/logout=logout”
user

org.apache.shiro.web.filter.authc

.UserFilter

用户拦截器,用户已经身份验证/记住我登录的都可;示例“/**=user”
anon

org.apache.shiro.web.filter.authc

.AnonymousFilter

匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例“/static/**=anon”
授权相关的
roles

org.apache.shiro.web.filter.authz

.RolesAuthorizationFilter

角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例“/admin/**=roles[admin]”
perms

org.apache.shiro.web.filter.authz

.PermissionsAuthorizationFilter

权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例“/user/**=perms["user:create"]”
port

org.apache.shiro.web.filter.authz

.PortFilter

端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样
rest

org.apache.shiro.web.filter.authz

.HttpMethodPermissionFilter

rest风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll);
ssl

org.apache.shiro.web.filter.authz

.SslFilter

SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样;
其他
noSessionCreation

org.apache.shiro.web.filter.session

.NoSessionCreationFilter

不创建会话拦截器,调用 subject.getSession(false)不会有什么问题,但是如果 subject.getSession(true)将抛出 DisabledSessionException异常;

2、自定义jwt拦截器

学习该模块时,看的其他博主的帖子。试验了一下无一成功。应该是理解有误,然后放弃网上的方法,按照自己的理解写了这篇自定义jwt拦截器。

自定义iwt拦截器

(1)用到的jwt工具类

(3条消息) jjwt工具类_SUN Y H的博客-CSDN博客

(2)创建自定义拦截器,实现BasicHttpAuthenticationFilter

import com.syh.shiro.Utils.JWT.JwtUtil;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author ojj
 * @title: JwtFilter
 * @projectName test
 * @description: 拦截器
 * @date 2022/1/6 14:57
 */
public class JwtFilter extends BasicHttpAuthenticationFilter {

    /**
     * 拦截器的前置  最先执行的 这里只做了一个跨域设置
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        res.setHeader("Access-control-Allow-Origin", req.getHeader("Origin"));
        res.setHeader("Access-control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        res.setHeader("Access-control-Allow-Headers", req.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            res.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * preHandle 执行完之后会执行这个方法
     * 再这个方法中 我们根据条件判断去去执行isLoginAttempt和executeLogin方法
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        /**
         * 先去调用 isLoginAttempt方法 字面意思就是是否尝试登陆 如果为true
         * 执行executeLogin方法
         */
        if (isLoginAttempt(request, response)) {
            executeLogin(request, response);
            return true;
        }

        return false;
    }


    /**
     * 这里我们只是简单去做一个判断请求头中的token信息是否为空
     * 如果没有我们想要的请求头信息则直接返回false
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("X-Token");
        return token != null;
    }

    /**
     * 执行登陆
     * 因为已经判断token不为空了,所以直接执行登陆逻辑
     * 将token放入JwtToken类中去
     * 然后getSubject方法是调用到了MyRealm的 执行方法  因为上面我是抛错的所有最后做个异常捕获就好了
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String jwtToken = req.getHeader("X-Token");
        // 校验token是否有效
        if (JwtUtil.checkToken(jwtToken)) {
            return true;
        } else {
            return false;
        }
    }


}

(3)ShiroConfig类中引入自定义jwt拦截器,并配置拦截路径

    //配置 Shiro 内置过滤器拦截范围
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 拦截配置
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        //引入自定义拦截器,命名为jwt
        filterMap.put("jwt", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        Map<String,String> filterRuleMap=new LinkedHashMap<>();
        // 登录接口放行
        filterRuleMap.put("/api/UserController/userLogin/**", "anon");
        // 配置登出,请求该地址,shiro去清除session,实现退出登录。
        filterRuleMap.put("/api/UserController/logout", "logout");
        // 配置自定义jwt认证
        filterRuleMap.put("/api/UserController/jwtLan", "jwt");
        // 设置需要进行登录认证的拦截范围
        filterRuleMap.put("/**", "authc");
        // 添加存在用户的过滤器(rememberMe)
        filterRuleMap.put("/**", "user");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return shiroFilterFactoryBean;
    }

(4)登录接口将生成的token返回给前端

    //配置 Shiro 内置过滤器拦截范围
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 拦截配置
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        //引入自定义拦截器,命名为jwt
        filterMap.put("jwt", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);

        Map<String,String> filterRuleMap=new LinkedHashMap<>();
        // 登录接口放行
        filterRuleMap.put("/api/UserController/userLogin/**", "anon");
        // 配置登出,请求该地址,shiro去清除session,实现退出登录。
        filterRuleMap.put("/api/UserController/logout", "logout");
        // 配置自定义jwt认证
        filterRuleMap.put("/api/UserController/jwtLan", "jwt");
        // 设置需要进行登录认证的拦截范围
        filterRuleMap.put("/**", "authc");
        // 添加存在用户的过滤器(rememberMe)
        filterRuleMap.put("/**", "user");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return shiroFilterFactoryBean;
    }

(5)测试,该访问路径会经过自定义jwt拦截器

    /*
    * 自定义jwt认证
    * */
    @GetMapping("/jwtLan")
    public String jwtLan() {
        System.out.println("自定义jwt认证");
        return "自定义jwt认证";
    }

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

S Y H

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值