栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

基于springboot的shiro基本操作

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

基于springboot的shiro基本操作

文章目录

Shiro执行逻辑Shiro的组织架构Shiro身份验证

演示Realm配置 Shiro授权

授权方式演示 Springboot集成Shiro

拦截用户请求认证用户密码加密授权

Shiro执行逻辑

Application Code:编写的应用程序Subject: 主体,代表了当前 “用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等;即一个抽象概念;所有 Subject 都绑定到 SecurityManager,与 Subject 的所有交互都会委托给 SecurityManager;可以把 Subject 认为是一个门面;SecurityManager 才是实际的执行者;SecurityManager:安全管理器;即所有与安全有关的操作都会与 SecurityManager 交互;且它管理着所有 Subject;可以看出它是 Shiro 的核心,它负责与后边介绍的其他组件进行交互,如果学习过 SpringMVC,你可以把它看成 DispatcherServlet 前端控制器;Realm:领域,Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色 / 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。

最简单的一个Shiro应用

    应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。
Shiro的组织架构

由图看出,Security Manager是最复杂的系统

Security Manager内部

Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得 Shiro 默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;Authorizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;SessionManager:如果写过 Servlet 就应该知道 Session 的概念,Session 呢需要有人去管理它的生命周期,这个组件就是 SessionManager;而 Shiro 并不仅仅可以用在 Web 环境,也可以用在如普通的 JavaSE 环境、EJB 等环境;所以呢,Shiro 就抽象了一个自己的 Session 来管理主体与应用之间交互的数据;这样的话,比如我们在 Web 环境用,刚开始是一台 Web 服务器;接着又上了台 EJB 服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到 Memcached 服务器);SessionDAO:DAO 大家都用过,数据访问对象,用于会话的 CRUD,比如我们想把 Session 保存到数据库,那么可以实现自己的 SessionDAO,通过如 JDBC 写到数据库;比如想把 Session 放到 Memcached 中,可以实现自己的 Memcached SessionDAO;另外 SessionDAO 中可以使用 Cache 进行缓存,以提高性能;CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能Cryptography:密码模块,Shiro 提供了一些常见的加密组件用于如密码加密 / 解密的。

领域Realm的实现

可以有 1 个或多个 Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是 JDBC 实现,也可以是 LDAP 实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户 / 权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的 Realm

Shiro身份验证

身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份 ID 一些标识信息来表明他就是他本人,如提供身份证,用户名 / 密码来证明。

shiro中进行身份验证,需要提供principals(身份)和credentials(证明)

身份告诉shiro你是什么用户,证明用来证明你是这个身份。

最常见的principals和credentials就是用户名和密码了

principals = 用户名/邮箱/手机号等等

credentials = 密码/验证码/数字凭证等等

下面利用springboot环境来演示Shiro如何进行身份验证

演示

准备环境依赖

	
        org.apache.shiro
        shiro-core
        1.2.2
    

准备用户身份

采用shiro.ini配置文件指定两个subject,一个用户名为wang,密码123,一个为zhang,密码123

[users]
wang = 123
zhang = 123

测试代码

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoApplicationTests {

    @Test
    public void MainTest(){
//        初始化SecurityManager工厂,这里用ini文件作为realm
        IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//        利用工厂产生一个SecurityManager实例
        SecurityManager securityManager = factory.getInstance();
//        将sm实例绑定到shiro提供的工具类中,下面的操作要利用此工具类
        SecurityUtils.setSecurityManager(securityManager);
//        获得主体,这里的主体是ini文件中的两个用户
        Subject subject = SecurityUtils.getSubject();
//        利用shiro提供的类创建一个虚拟用户用来登录
        UsernamePasswordToken token = new UsernamePasswordToken("wang", "123");

        try {
//		  对身份进行验证
            subject.login(token);
        } catch (AuthenticationException e) {
            System.out.println("用户名和密码错误,错误信息:"+e.getMessage());
        }
//        判断用户是否验证通过,通过返回true,不通过返回false
        boolean authenticated = subject.isAuthenticated();
         if(authenticated) System.out.println("登录成功");
        else System.out.println("登录失败");
//        退出登录
        subject.logout();
    }
}

对29行错误信息的解释

如果身份验证失败请捕获 AuthenticationException 或其子类,常见的如: DisabledAccountException(禁用的帐号)、LockedAccountException(锁定的帐号)、UnknownAccountException(错误的帐号)、ExcessiveAttemptsException(登录失败次数过多)、IncorrectCredentialsException (错误的凭证)、ExpiredCredentialsException(过期的凭证)等,具体请查看其继承关系;对于页面的错误消息展示,最好使用如 “用户名 / 密码错误” 而不是 “用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;

从如上代码可总结出身份验证的步骤:

    收集用户身份 / 凭证,即如用户名 / 密码;调用 Subject.login 进行登录,如果失败将得到相应的 AuthenticationException 异常,根据异常提示用户错误信息;否则登录成功;最后调用 Subject.logout 进行退出操作。

如上测试的几个问题:

    用户名 / 密码硬编码在 ini 配置文件,以后需要改成如数据库存储,且密码需要加密存储;用户身份 Token 可能不仅仅是用户名 / 密码,也可能还有其他的,如登录时允许用户名 / 邮箱 / 手机号同时登录。

从代码可以看出这些操作好像是subject完成的,但实际上,全部是subject委托给securityManager完成的,而SecurityManager对于身份验证的操作,会交给自己的子模块Authenticator完成,Authenticator将获取到的token(用户信息及凭证)交给realm,realm如果没有抛出异常,那么身份就验证成功了。

可以看出,Realm是验证身份的最终站,因此,我们可以配置多个Realm来多次验证Authenticator交过来的token

Realm配置

Realm是安全数据源,我们刚刚的演示中使用了ini文件作为数据源,那么我们使用的Realm在shiro中就是

org.apache.shiro.realm.text.IniRealm

shiro中定义了Realm接口,他是所有Realm的父类接口,代码如下

String getName(); //返回一个唯一的Realm名字
boolean supports(AuthenticationToken token); //判断此Realm是否支持此Token
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
 throws AuthenticationException;  //根据Token获取认证信息

我们可以自定义Realm,然后通过ini配置文件指定securityManager的Realm实现,这样便可实现多Realm验证

创建一个shiro-multi-realm.ini文件,假设现在我们自定义了两个Realm

那么可以通过下面的配置文件进行多Realm配置

#声明一个realm
myRealm1 = com.whr.realm.MyRealm1
myRealm2 = com.whr.realm.MyRealm2
#$符号表示引用上面已经定义好的对象
#指定securityManager的realms实现,这一行可删除,删除后将按照上面声明的顺序加载
securityManager.realms=$myRealm1,$myRealm2

securityManager 会按照 realms 指定的顺序进行身份认证。此处我们使用显示指定顺序的方式指定了 Realm 的顺序,如果删除 “securityManager.realms=$myRealm1,$myRealm2”,那么securityManager 会按照 realm 声明的顺序进行使用(即无需设置 realms 属性,其会自动发现),当我们显示指定 realm 后,其他没有指定 realm 将被忽略,如 “securityManager.realms=$myRealm1”,那么 myRealm2 不会被自动设置进去。

Shiro提供的Realm

以后一般继承 AuthorizingRealm(授权)即可;其继承了 AuthenticatingRealm(即身份验证),而且也间接继承了 CachingRealm(带有缓存实现)。

Shiro授权

为什么需要授权,因为在页面中,有些资源我们不想让没有权限的人看到,例如在一个后台管理系统登录窗口页面,我们会有几个角色,例如:管理员(admin),用户(user)等等,那么管理员和用户能看到的页面和进行的操作一定是不同的。这时我们就需要对不同的角色进行授权。

在授权中需了解的几个关键对象:

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

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

权限(Permission) 安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如: 访问用户列表页面 查看/新增/修改/删除用户数据(即很多时候都是 CRUD(增查改删)式权限控制)打印文档等。如上可以看出,权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro 不会去做这件事情,而是由实现人员提供。

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

授权方式

Shiro 支持三种方式的授权:

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

Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
    //有权限
} else {
    //无权限
}

注解式:通过在执行的 Java 方法上放置相应的注解完成:

@RequiresRoles("admin")
public void hello() {
    //有权限
}

没有权限将抛出相应的异常;

JSP/GSP 标签:在 JSP/GSP 页面通过相应的标签完成:




演示

基于角色的演示控制

[users]
wang = 123 ,admin,user
zhang = 123 ,user

给wang用户设置了两个角色,admin和user,zhang用户只有user角色

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class DemoApplicationTests {

    
    static {
        SecurityManager securityManager = new IniSecurityManagerFactory("classpath:shiro.ini").
                getInstance();
        SecurityUtils.setSecurityManager(securityManager);
    }
    
    @Test
    public void MainTest(){
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken zhang = new UsernamePasswordToken("zhang", "123");
        subject.login(zhang);
        if(subject.hasRole("admin")){
            System.out.println("have permission");
        }else{
            System.out.println("don't have permission");
        }
//        退出登录
        subject.logout();
    }


}

上述示例只演示了测试此用户是否拥有某一角色,下面给此角色赋予权限

[users]
zhang=123,admin,user
wang=123,user
[roles]
admin = admin:create,admin:delete,admin:update
user = user:read
@Test
    public void MainTest(){
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken zhang = new UsernamePasswordToken("zhang", "123");
        subject.login(zhang);
        if(subject.hasRole("user")){
            System.out.println("have role");
//            判断此用户是否拥有删除权限,没有则报错
            try{
                subject.checkPermission("admin:delete");
            }catch(UnauthorizedException e){
                System.out.println(e.getMessage());
            }
        }else{
            System.out.println("don't have role");
        }
//        退出登录
        subject.logout();
    }
//报错信息
Subject does not have permission [admin:delete]
Springboot集成Shiro

开始之前,所有页面结构

在pom.xml中添加如下三个依赖

		
		
            org.apache.shiro
            shiro-core
            1.2.2
        
		
        
            org.apache.shiro
            shiro-spring
            1.7.1
        
		
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        

在yml文件中进行配置

spring:
  thymeleaf:
    #配置模板路径,默认是templates,可以不用配置
    prefix: classpath:/templates/
    suffix: .html
    cache: false #关闭页面缓存

自定义一个MyShiroConfig

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class MyShiroConfig {
//        利用securityManager对象来注册shiroFilter
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean
            (@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
//        关联SecurityManager
        shiroFilter.setSecurityManager(defaultWebSecurityManager);
        return shiroFilter;
    }
//        注册SecurityManager对象,其中底层的realm用自己的实现
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//        关联realm
        securityManager.setRealm(userRealm);
        return securityManager;
    }

//		  注册realm,这个realm是自定义的
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
}

自定义一个Realm

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;


public class UserRealm extends AuthorizingRealm {

//    给用户授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行doGetAuthorizationInfo-PrincipalCollection");
        return null;
    }

//    认证用户的token
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行doGetAuthorizationInfo-AuthenticationToken");
        return null;
    }
}

上面的代码自定义了UserRealm,这个说明了SecurityManager需要realm时的访问对象,然后定义了一个过滤器,这个过滤器可以拦截用户的请求并进行页面的跳转

在前端控制器中拿到前端发来的数据后,封装成token,交付给SecurityManager对象,sm再去Realm中去验证用户的身份及权限。

例如,我们拥有下面的一个Controller控制器

 @RequestMapping("/userLogin")
    public String login(String username, String password, Model model){
//        获取当前登录的用户
        Subject subject = SecurityUtils.getSubject();
//        封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try{
//            这里的login将会把token交付给securityManager,sm再去UserRealm中认证用户
            subject.login(token);
//            登录成功跳转
            return "index";
        }catch (UnknownAccountException e){
            //用户名不存在
            model.addAttribute("msg","用户名不存在");
            return "login";
        }catch (IncorrectCredentialsException e){
            //密码不正确
            model.addAttribute("msg","密码错误");
            return "login";
        }
    }

当用户发起/userLogin请求时,前端将用户名和密码封装成一个token,同时调用subject的login方法,会将token交给SM对象,SM对象再访问UserRealm中去验证用户身份

拦截用户请求

如果想要拦截用户请求,那么需要在MyShiroConfig类中修改shiroFilter进行配置

@Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean
            (@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(defaultWebSecurityManager);
//      添加shiro的内置过滤器,对请求进行拦截
        
        Map filterMap = new linkedHashMap<>();

        filterMap.put("/user
        Map filterMap = new linkedHashMap<>();

//        配置权限,配置权限一定要在认证前才能生效
        filterMap.put("/user/add","perms[user:add]");
//        给delete请求设置只有拥有权限才能访问,此时会跳转到realm中
        filterMap.put("/user/delete","perms[user:delete]");
        filterMap.put("/user
public class UserRealm extends AuthorizingRealm {

//    给用户授权,每次过滤器拦截到perms[user:add]都会跳转到此进行授权认证
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行doGetAuthorizationInfo-PrincipalCollection");
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();

//        如果此用户有角色,只需要addRole即可
//        simpleAuthorizationInfo.addRole("role");
        Subject subject = SecurityUtils.getSubject();
//        通过下面这两个方法都可以拿到认证用户方法传来的perms
        String perms = (String) subject.getPrincipal();
//        String perms = principalCollection.getPrimaryPrincipal();
//        将此权限赋予给此用户
        simpleAuthorizationInfo.addStringPermission(perms);
        return simpleAuthorizationInfo;
    }

//    认证用户的交付过来的token
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行doGetAuthorizationInfo-AuthenticationToken");
//        假定的用户名和密码。实际开发中去数据库中取
        String username = "root";
        String password = "123456";
//        假定用户只有add权限没有delete权限
        String perms = "user:add";
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        if (!token.getUsername().equals(username)) {
            return null;
        }
//        将用户的权限perms通过此返回值传输到doGetAuthorizationInfo方法中,实际开发中传输对象
        return new SimpleAuthenticationInfo(perms,password,"");
    }
}

到此springboot的基本操作就演示完毕了,在jsp的页面中,shiro可以利用标签进行一些测试,在thymeleaf中也可以利用标签,只不过需要加载新的jar包,是thymeleaf和shiro整合的jar包,具体的我没有研究,具体用到再说。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/727341.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号