Shiro框架学习——代码+笔记

如果你不相信努力和时光,那么成果就会是第一个选择辜负你的。不要去否定你自己的过去,也不要用你的过去牵扯你现在的努力和对未来的展望。不是因为拥有希望你才去努力,而是去努力了,你才有可能看到希望的光芒。Shiro框架学习——代码+笔记,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

shiro 框架入门:

Shiro是Apache旗下的一个开源框架,它提供了一套通用的安全认证框架,可以快速地实现用户身份认证、权限授权、加密、会话管理等功能,并且可以运行在web应用、非web应用、集群分布式应用中。

Shiro的核心部分是SecurityManager,它负责安全认证与授权。Shiro的认证过程主要包括以下步骤:
首先用户提供用户名和密码,接着Shiro通过Realm获取存储在数据库或其他数据源中的用户信息,然后将用户提供的密码和数据库中的密码进行匹配,如果匹配成功,则认证通过。Realm是Shiro从中获取安全数据(如用户、角色、权限)的数据源。

另外,Shiro提供了一个Subject对象来代表当前操作的用户或程序,它可以获取当前用户的身份和权限信息,还可以进行登录、注销、检查权限等操作。同时,Shiro也提供了一些其他的核心组件和接口,如Authenticator、Authorizer、SessionManager等。

总的来说,Shiro的底层实现原理主要包括SecurityManager、Realm、Subject等核心组件和接口,以及相应的认证和授权流程,它们共同构成了Shiro框架的基础。

shiro和springSecurity的对比

  1. spring Security时基于Spring开发,项目若使用Spring作为基础,配合SpringSecurity做权限更加方便,而Shiro需要和Spring进行整合开发。
  2. Spring Security功能比Shiro更加丰富,而Shiro需要和Spring进行整合开发。
  3. Spring Security 社区资源对比Shiro更加丰富。
  4. Shiro的配置和使用比较简单,Spring Security上手复杂一点。可以队列运行,Spring Security 依赖Spring 容器。
  5. Shiro依赖性低,不需要任何框架和容器,可以独立运行,Spring Security 依赖Spring 容器。
  6. shiro不仅仅可以使用在web中,也可以工作在任何应用环境中,在集群会话时Shiro最重要的一个好处或许是它的回话是独立于容器的。

Shiro相关介绍和功能:

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

登陆验证

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

shiro外部架构

在这里插入图片描述

  • Subject:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心 就是 Subject。
  • SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager交互;且其管理着所有 Subject;是 Shiro 的核心,负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 中 DispatcherServlet 的角色
  • Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户 进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/ 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource
    在这里插入图片描述
    在Shiro中,用户认证的流程是:

用户在登录时,输入用户名和密码,Shiro会将这些信息封装成一个Token对象,然后通过SecurityManager对Token进行认证,SecurityManager会委托给Realm进行认证,Realm在认证时会从数据源(如数据库)中获取用户信息,然后将获取到的信息与Token中的信息进行比对,最后得出认证结果,如果认证通过,则会将用户信息存储在Session中。

总体来说,Shiro的内部结构可以概括为以下几个部分:

SecurityManager:负责安全认证和授权,委托给Realm处理安全相关数据。

Realm:负责从数据源中获取安全相关数据,并将这些数据提供给SecurityManager进行处理。

Subject:代表当前用户的安全操作,如登录、注销、角色判断等操作。

Token:用户登录的标识,封装了用户的身份信息。

Session:存储用户信息的容器。

Authorizer:授权器、即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;

Authenticator:负责 Subject 认证,是一个扩展点,可以自定义实现;可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过了;

CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据 基本上很少改变,放到缓存中后可以提高访问的性能

Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于如密码加密/解密。

通过这些组件的协作,Shiro提供了全面的安全保障,为Java应用程序提供了方便、灵活、高效的安全解决方案。

简单搭建一个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>

在resources文件下创建ini文件

[users]
zhangsan=admin
fuck=123456

zhangsan是账号或者姓名,
admin则是密码

登录认证

① 身份验证:一般需要提供如身份ID等一些标识信息来表明登录者的身份,如提供email,用户名/密码来证明。

② 在shiro中,用户需要提供principals(身份)和credentials(证明)给shiro,从而应用能验证用户身份;

③principals:身份,即主体的标识属性,可以是任何属性,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/邮箱/手机号。

④ credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

最常见的principals和credentials组合就是用户名/密码

登录认证基本流程

① 收集用户身份/凭证,即如用户名/密码

② 调用 Subject.login 进行登录,如果失败将得到相应 的 AuthenticationException异常,根据异常提示用户错误信息;否则登录成功

③ 创建自定义的 Realm 类,继承 org.apache.shiro.realm.AuthenticatingRealm类,实现
doGetAuthenticationInfo() 方法

在这里插入图片描述

第一个shiro案例

  1. 初始化获取SecurityManager
  2. 获取subject对象
  3. 创建token对象,web应用用户名密码从页面传递
  4. 完成登录
public class ShiroRun{
    public static void main(String[] args){
//        1.初始化获取SecurityManager
        //创建工厂,写入配置文件 shiro.ini的路径
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        //通过工厂获取相关实例,即factory.getInstance(),然后获取securityManager对象
        SecurityManager securityManager = factory.getInstance();
        //通过工具将securityManager塞入
        SecurityUtils.setSecurityManager(securityManager);
//        2.通过工具,获取subject对象
        Subject subject = SecurityUtils.getSubject();
//        3.创建token对象,web应用用户名密码从页面传递
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("fate", "admin");
//        4.完成登录
        subject.login(usernamePasswordToken);
        System.out.println(usernamePasswordToken);
    }
}

角色,授权

授权概念:

① 授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面
操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限 (Permission)、角色(Role)。

② 主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权 后才允许访问相应的资源。

③ 资源(Resource):在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑 某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。

④ 权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户 有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用
户列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控 制)等。权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允 许。

⑤ Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限, 即实例级别的)

⑥ 角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等 都是角色,不同的角色拥有一组不同的权限

授权方式:

编程方式:

subject.hashRole("admin");

注解方式:

@RequiresRoles("admin")

JSP/GSP标签:

<shiro:hasRole name="admin">
</shiro:hasRole>

授权流程

①首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给
Authorizer;

②Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;

③ 在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;

④ Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回 true,否则返回false表示授权失败;

在这里插入图片描述

在ini文件中添加角色配置

[users]
zhangsan=admin,role1,role2
fuck=123456

在ini配置文件中增加权限配置

[roles]
role1=user:insert,user:select
public class ShiroRun{
    public static void main(String[] args){
        // 1.初始化获取SecurityManager
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        assert securityManager != null;
        SecurityUtils.setSecurityManager(securityManager);
//        2.获取subject对象
        Subject subject = SecurityUtils.getSubject();
//        3.创建token对象,web应用用户名密码从页面传递
        UsernamePasswordToken token = new UsernamePasswordToken("fate", "zero");
//        4.完成登录
        subject.login(token);
        System.out.println("登录成功");
        //5.判断角色
        boolean hasRole = subject.hasRole("role1");
        System.out.println("是否拥有此角色 = " + hasRole);
        //6.判断权限
        boolean permitted = subject.isPermitted("user:insert");
        System.out.println("是否拥有此权限 = " + permitted);
 //     也可以用checkPermission方法,但没有返回值,没权限抛AuthenticationException
        subject.checkPermission("user:update");
        
    }
}

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

public class ShiroMD5 {
    public static void main(String[] args) {
        //密码明文
        String password = "admin";
        //使用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带盐的3次加密 = " + md5Hash3.toHex());
        //使用父类进行加密
        SimpleHash simpleHash = new SimpleHash("MD5",password,"salt",3);
        System.out.println("父类带盐的3次加密 = " + simpleHash.toHex());
    }
}

自定义登陆认证

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

public class MyRealm extends AuthenticatingRealm {
    //自定义登录认证方法,shiro的login方法的底层会调用该类的认证方法进行认证
    //需要配置自定义的realm生效,应该在ini文件中配置,如果有springboot框架,也可以在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 = "b073e9301c412431159e7075340c4c66";
            //4.创建封装校验逻辑的对象,封装数据返回
            AuthenticationInfo info = new SimpleAuthenticationInfo(
                    authenticationToken.getPrincipal(),
                    pwdInfo,
                    ByteSource.Util.bytes("salt"),
                    authenticationToken.getPrincipal().toString()
            );
            return info;
        }
        
        return null;
    }
}

添加配置信息,让shiro知晓你使用的是自定义的Realm

[main]
#在shiro.ini中添加配置信息
md5CredentialsMatcher=org.apache.shiro.authc.cre 
dential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3
myrealm=com.xh.shiro.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm
 
[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1,role2
lisi=l4 
 
[roles]
role1=user:insert,user:select

整合SpringBoot

引入依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
    </parent>
<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-web-starter</artifactId>
        <version>1.9.0</version>
    </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>5.1.46</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

配置文件 application.yml

# mybatis配置
mybatis-plus:
  configuration:
  # 日志
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:mapper/*.xml
 
 
spring:
# 数据库配置
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shirodb?characterEncoding=utf-8&useSSL=false
    password: root
    username: 123456
# json格式
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
 
shiro:
# 登录接口  默认链接
  loginUrl: /myController/login

SpringBoot启动类

@SpringBootApplication
@MapperScan("com.fate.shiro.mapper")
public class ShiroApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShiroApplication.class, args);
    }
}

登录认证实现
后端接口服务实现

  1. 数据库表
create table user
(
    id   bigint auto_increment comment '编号'
        primary key,
    name varchar(30) null comment '用户名',
    pwd  varchar(50) null comment '密码',
    rid  bigint      null comment '角色编号'
)
    comment '用户表' charset = utf8;

在这里插入图片描述
2. 创建配置类entity下的User,对应数据库名

@Data  //并不推荐这种方式,我个人还是推荐手动将构造方法和get以及set方法注入
@NoArgsConstructor
@AllArgsConstructor
public class User{
    private Integer id;
    private String name;
    private String pwd;
    private Integer rid;
}
  1. 创建mapper文件
@Repository
public interface UserMapper extends BaseMapper<User>{
}
  1. 创建service
public interface UserService{
    //用户登录
    User getUserInfoByName(String name);
}
  1. 创建service实现类UserServiceImpl
@Service
public class UserServiceImpl implements UserService{
    @Autowired
    private UserMapper userMapper;
 
    @Override
    public User getUserInfoByName(String name){
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("name",name);
        User user = userMapper.selectOne(wrapper);
        return user;
    }
}
  1. 自定义realm
@Component
public class MyRealm extends AuthorizingRealm{
 
    @Autowired
    private UserService userService;
 
    //自定义授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
 
    //自定义登录认证方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1.获取用户身份信息
        String name = authenticationToken.getPrincipal().toString();
        //2.调用业务层获取用户信息(数据库)
        User user = userService.getUserInfoByName(name);
        //3.非空判断,将数据封装返回
        if(user != null){
            AuthenticationInfo info = new SimpleAuthenticationInfo(
                    authenticationToken.getPrincipal(),
                    user.getPwd(),
                    ByteSource.Util.bytes("salt"),
                    authenticationToken.getPrincipal().toString()
            );
            return info;
        }
        return null;
    }
}
  1. 配置类
@Slf4j
@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;
    }
    //配置Shiro内置过滤器拦截范围
    @Bean
    public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition = new DefaultShiroFilterChainDefinition();
//        设置不认证就可以访问的资源
        defaultShiroFilterChainDefinition.addPathDefinition("/myController/userLogin","anon");
        defaultShiroFilterChainDefinition.addPathDefinition("/login","anon");
//        设置需要进行登录认证的拦截范围
        defaultShiroFilterChainDefinition.addPathDefinition("/**","authc");
        return defaultShiroFilterChainDefinition;
    }
}

8.controller类

@Controller
@RequestMapping("myController")
public class MyController{
    
    @GetMapping("userLogin")
    @ResponseBoby
    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){
            e.printStackTrace();
            System.out.println("登录失败");
            return "登录失败";
        }
}

汇总前端知识

在resources/templates下创建login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>Shiro 登录认证</h1>
    <br>
    <form action="/myController/userLogin">
        <div>用户名:<input type="text" name="name" value=""></div>
        <div>密码:<input type="password" name="pwd" value=""></div>
        <div><input type="submit" value="登录"></div>
    </form>
</body>
</html>
  1. 添加main界面
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
	<head>
		<meta charset="UTF-8"> 
		<title>Title</title>
	</head> 
	<body>
		<h1> 
		<br>
			Shiro 登录认证后主页面
		</h1>
			登录用户为: <span th:text="${session.user}">		</span>
	</body>
  1. 继续编写controller
@Controller
@RequestMapping("myController")
public class MyController{
 
    //跳转登录页面
    @GetMapping("login") 
    public String login(){ 
        return "login"; 
    }
    
    @GetMapping("userLogin")
    public String userLogin(String name,String pwd,HttpSession session){
        //1.获取subject对象
        Subject subject = SecurityUtils.getSubject();
        //2.封装请求数据到token
        AuthenticationToken token = new UsernamePasswordToken(name,pwd);
        //3.调用login方法进行登录认证
        try{        
            subject.login(token);
            //return "登录成功";
            session.setAttribute("user",token.getPrincipal().toString());
            return "main";
        }catch(AuthenticationException e){
            e.printStackTrace();
            System.out.println("登录失败");
            return "登录失败";
        }
}

多个 realm 的认证策略设置

实现原理
当应用程序配置多个 Realm 时,例如:用户名密码校验、手机号验证码校验等等。 Shiro 的 ModularRealmAuthenticator 会使用内部的AuthenticationStrategy 组件判断认证是成功还是失败。

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

① 在所有 Realm 被调用之前

② 在调用 Realm 的 getAuthenticationInfo 方法之前

③ 在调用 Realm 的 getAuthenticationInfo 方法之后

④ 在所有 Realm 被调用之后

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

Shiro 中定义了 3 种认证策略的实现:
在这里插入图片描述
ModularRealmAuthenticator 内置的认证策略默认实现是 AtLeastOneSuccessfulStrategy 方式。可以通过配置修改策略。
代码实现:

@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){ 
//1 创建  defaultWebSecurityManager 对象
    DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2 创建认证对象,并设置认证策略
    ModularRealmAuthenticator modularRealmAuthenticator = new 
ModularRealmAuthenticator();
    modularRealmAuthenticator.setAuthenticationStrategy(new 
AllSuccessfulStrategy());
    defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator) 
;
//3 封装  myRealm 集合
    List<Realm> list = new ArrayList<>(); 
    list.add(myRealm);
    list.add(myRealm2);
//4 将 myRealm 存入 defaultWebSecurityManager 对象
    defaultWebSecurityManager.setRealms(list);
//5 返回
    return defaultWebSecurityManager;
}

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

基本流程

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

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

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

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

配置类更新

@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);
//4.5 设置  rememberMe
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
//5 返回
        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 DefaultShiroFilterChainDefinition
    shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition definition = new
                DefaultShiroFilterChainDefinition();
//设置不认证可以访问的资源
        definition.addPathDefinition("/myController/userLogin", "anon");
        definition.addPathDefinition("/myController/login", "anon");
//设置需要进行登录认证的拦截范围
        definition.addPathDefinition("/**", "authc");
//添加存在用户的过滤器(rememberMe) 
        definition.addPathDefinition("/**", "user");
        return definition;
    }
}

controller更新

@Controller
@RequestMapping("myController")
public class MyController{
 
    //跳转登录页面
    @GetMapping("login") 
    public String login(){ 
        return "login"; 
    }
    
    @GetMapping("userLogin")
    public String userLogin(String name,String pwd,
                @RequestParam(defaultValue = "false")boolean rememberMe,
                HttpSession session){
        //1.获取subject对象
        Subject subject = SecurityUtils.getSubject();
        //2.封装请求数据到token
        AuthenticationToken token = new UsernamePasswordToken(name,pwd,rememeberMe);
        //3.调用login方法进行登录认证
        try{        
            subject.login(token);
            //return "登录成功";
            session.setAttribute("user",token.getPrincipal().toString());
            return "main";
        }catch(AuthenticationException e){
            e.printStackTrace();
            System.out.println("登录失败");
            return "登录失败";
        }
}
 
    //登录认证验证  rememberMe
    @GetMapping("userLoginRm")
    public String userLogin(HttpSession session) {
        session.setAttribute("user", "rememberMe");
        return "main";
    }

login.html更新

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>Shiro 登录认证</h1>
    <br>
    <form action="/myController/userLogin">
        <div>用户名:<input type="text" name="name" value=""></div>
        <div>密码:<input type="password" name="pwd" value=""></div>
        <div>记住用户:<input type="checkbox" name="rememberMe" value="true"></div>
        <div><input type="submit" value="登录"></div>
    </form>
</body>
</html>

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

代码实现
修改main.html

<body>
<h1>Shiro 登录认证后主页面</h1> 
<br>
登录用户为:<span th:text="${session.user}"></span> 
<br>
<a href="/logout">登出</a> 
</body>

配置类中添加logout过滤器

@Bean
public DefaultShiroFilterChainDefinition shiroFilterChainDefinition(){ 
    DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
    //设置不认证可以访问的资源
    definition.addPathDefinition("/myController/userLogin","anon"); 
    definition.addPathDefinition("/myController/login","anon"); 
    //配置登出过滤器
    definition.addPathDefinition("/logout","logout"); 
    //设置需要进行登录认证的拦截范围
    definition.addPathDefinition("/**","authc"); 
    //添加存在用户的过滤器(rememberMe) 
    definition.addPathDefinition("/**","user"); 
    return  definition;
}

授权、角色认证

用户登录后,需要验证是否具有指定角色指定权限。Shiro也提供了方便的工具进行判断,这个工具就是Realm的doGetAuthorizationInfo方法进行判断。触发权限判断的有两种:

① 在页面中通过shiro:属性判断

② 在接口服务中通过注解@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()

授权验证-获取角色进行验证
修改MyRealm

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 
    System.out.println("进入自定义授权方法");
//1 创建对象,存储当前登录的用户的权限和角色
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 
//2 存储角色
    info.addRole("admin"); 
//返回
    return info; 
}

添加数据库

CREATE TABLE `role` (
    `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', 
    `name` VARCHAR(30) DEFAULT NULL COMMENT '角色名',
    `desc` VARCHAR(50) DEFAULT NULL COMMENT '描述',
    `realname` VARCHAR(20) DEFAULT NULL COMMENT '角色显示名', 
    PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色表';
 
CREATE TABLE `role_user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', 
`uid` BIGINT(20)  DEFAULT NULL COMMENT '用户  id',
`rid` BIGINT(20)  DEFAULT NULL COMMENT '角色  id', 
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色用户映射表';

在这里插入图片描述
在这里插入图片描述

  1. mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
    @Select("SELECT NAME FROM role WHERE id IN (SELECT rid FROM role_user WHERE uid=(SELECT id FROM USER WHERE NAME=#{principal}))")
    List<String> getUserRoleInfoMapper(@Param("principal") String principal);
}
  1. service
@Override
public List<String> getUserRoleInfo(String principal) { 
    return userMapper.getUserRoleInfoMapper(principal);
}

5.MyRealm方法改造

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    System.out.println("进入自定义授权方法"); 
    //获取当前用户身份信息
    String principal = principalCollection.getPrimaryPrincipal().toString();
    //调用接口方法获取用户的角色信息
    List<String> roles = userService.getUserRoleInfo(principal); 
    System.out.println("当前用户角色信息:"+roles);
    //创建对象,存储当前登录的用户的权限和角色
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 
    //存储角色
    info.addRoles(roles);
    //返回 
    return info;
}

授权验证-获取权限进行验证
添加数据库表

CREATE TABLE `permissions` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', 
`name` VARCHAR(30) DEFAULT NULL COMMENT '权限名',
`info` VARCHAR(30) DEFAULT NULL COMMENT '权限信息', 
`desc` VARCHAR(50) DEFAULT NULL COMMENT '描述', 
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='权限表';
 
CREATE TABLE `role_ps` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '编号', 
`rid` BIGINT(20)  DEFAULT NULL COMMENT '角色  id',
`pid` BIGINT(20)  DEFAULT NULL COMMENT '权限  id', 
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='角色权限映射表';

mapper添加接口

    @Select({
    "<script>",
    "select info FROM permissions WHERE id IN ", 
    "(SELECT pid FROM role_ps WHERE rid IN (", 
    "SELECT id FROM role WHERE NAME IN ",
    "<foreach collection='roles' item='name' open='(' separator=',' close=')'>",
    "#{name}", 
    "</foreach>", 
    "))",
    "</script>"
    })
    List<String> getUserPermissionInfoMapper(@Param("roles")List<String> roles);

service

@Override
public List<String> getUserPermissionInfo(List<String> roles) { 
    return userMapper.getUserPermissionInfoMapper(roles);
}

MyRealm

//自定义授权方法:获取当前登录用户权限信息,返回给 Shiro 用来进行授权对比
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { 
    System.out.println("进入自定义授权方法");
    //获取当前用户身份信息
    String principal = principalCollection.getPrimaryPrincipal().toString();
    //调用接口方法获取用户的角色信息
    List<String> roles = userService.getUserRoleInfo(principal); 
    System.out.println("当前用户角色信息:"+roles);
    //调用接口方法获取用户角色的权限信息
    List<String> permissions = userService.getUserPermissionInfo(roles);
    System.out.println("当前用户权限信息:"+permissions); 
    //创建对象,存储当前登录的用户的权限和角色
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); 
    //存储角色
    info.addRoles(roles);
    //存储权限信息
    info.addStringPermissions(permissions);
    //返回 
    return info;
}

controller

//登录认证验证权限
@RequiresPermissions("user:delete") 
@GetMapping("userPermissions")
@ResponseBody
public String userLoginPermissions() { 
    System.out.println("登录认证验证权限"); 
    return "验证权限成功";
}

main.html更新

<body>
<h1>Shiro 登录认证后主页面</h1> 
<br>
登录用户为:<span th:text="${session.user}"></span> 
<br>
<a href="/logout">登出</a> 
<br>
<a href="/myController/userLoginRoles">测试授权-角色验证</a> 
<br>
<a href="/myController/userPermissions">测试授权-权限验证</a> 
</body>

前端页面授权验证
添加依赖

<dependency>
	<groupId>com.github.theborakompanioni</groupId> 
		<artifactId>thymeleaf-extras-shiro</artifactId> 
	<version>2.0.0</version>
</dependency>

配置类
用于解析 thymeleaf 中的 shiro:相关属性

@Bean
public ShiroDialect shiroDialect(){
    return new ShiroDialect(); 
}

Thymeleaf 中常用的 shiro:属性

guest 标签

<shiro:guest>
</shiro:guest>

用户没有身份验证时显示相应信息,即游客访问信息。

user 标签

<shiro:user> 
</shiro:user>

用户已经身份验证/记住我登录后显示相应的信息。

authenticated 标签

<shiro:authenticated> 
</shiro:authenticated>

用户已经身份验证通过,即 Subject.login 登录成功,不是记住我登录的。

notAuthenticated 标签

<shiro:notAuthenticated> 
</shiro:notAuthenticated>

用户已经身份验证通过,即没有调用 Subject.login 进行登录,包括记住我自动登录的也属于未进行身份验证。

principal 标签

<shiro: principal/>
<shiro:principal property="username"/>

相当于((User)Subject.getPrincipals()).getUsername()。

lacksPermission 标签

<shiro:lacksPermission name="org:create"> 
</shiro:lacksPermission>

如果当前 Subject 没有权限将显示 body 体内容。

hasRole 标签

<shiro:hasRole name="admin"> 
</shiro:hasRole>

如果当前 Subject 有角色将显示 body 体内容。

hasAnyRoles 标签

<shiro:hasAnyRoles name="admin,user"> 
</shiro:hasAnyRoles>

如果当前 Subject 有任意一个角色(或的关系)将显示 body 体内容。

lacksRole 标签

<shiro:lacksRole name="abc"> 
</shiro:lacksRole>

如果当前 Subject 没有角色将显示 body 体内容。

hasPermission 标签

<shiro:hasPermission name="user:create"> 
</shiro:hasPermission>

如果当前 Subject 有权限将显示 body 体内容

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/197973.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!