shiro框架学习

文章目录

  • 简介
    • 优势
    • Shiro与Spring Security的对比
    • 基本功能
  • 基本使用
    • 用户授权
      • 授权方式
      • 授权流程
    • shiro加密
    • 登录认证
    • shiro自定义认证
      • 一个普通的maven项目
      • 整合springboot
    • 多个realm的认证策略
      • 实现原理
    • remember me功能
    • 权限与角色
      • 接口服务注解
      • 获取角色进行验证
      • 获取权限进行验证
    • EhCache实现缓存
    • 会话管理
      • SessionManager
      • 会话管理实现

简介

Apache·Shiro是一个功能强大且易于使用的Java权限框架。shiro可以完成认证、授权、加密、会话管理、与web集成、缓存等。
shiro官网地址

优势

1. 易于使用:使用市容构建系统安全框架简单,能快速掌握。
2. 全面:市容包含系统安全框架所需要的功能。
3. 灵活:可以在任何应用程序环境中工作。虽然可以在web,EJB和IOC环境下工作,但不需要依赖他们,它不强制要求任何规范。
4. 强力支持web:shiro具有出色的web应用程序支持,可以基于应用程序URl和web协议创建灵活的安全策略,同时还提供了一组jsp库来控制页面输出。
5. 兼容性强:shiro的设计模式使其易于与其他框架和应用程序(Spring、Grails等)集成。

Shiro与Spring Security的对比

1. Spring Security基于Spring开发,项目若使用Spring做基础,配合SpringSecurity更方便。
2. Spring Security功能,社区资源比shiro丰富
3. shiro配置和使用易于Spring Security
4. shiro依赖性低,可以不依赖任何框架和容器,独立运行,而Spring Security依赖Spring容器

基本功能

shiro框架学习_第1张图片

‍1. Subject:应用代码直接交互的对象,代表了当前“用户”,即与当前应用交互的任何东西,而与Subject的所有交互会委托给SecurityManager。
‍2. SecurityManager:安全管理器,所有与安全有关的操作都会与SecurityManager交互,所有Subject由它管理,是shiro的核心,相当于SpringMVC中的DispatcherServlet。
‍ 3. Realm:shiro从Realm获取安全数据(如用户、角色、权限),当SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户,然后确定用户身份是否合法,也需要从Realm得到用户相应的角色权限进行验证用户是否能进行操作,可以把Realm看成DataSource。

基本使用

用户授权

✨授权,控制谁访问哪些资源
✨主体(Subject),访问应用的用户
✨资源(Resource),在应用中用户可以访问的URL
✨权限(Permission),在应用中用户能否访问某个资源,shiro支持用户模块的所有权限和某个用户的权限
✨角色(Role),权限的集合,一般情况下会赋予用户角色而非权限,而不同的角色会拥有一组不同的权限,赋权方便

授权方式

‍编程式:通过if/else授权实现

 if(subject.hasRole("admin")){
 			//有权限
 }else{
 			//无权限
 }

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

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

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

<shiro:hasRole name="admin">
	<!-有权限->
</shiro:hasRole>

授权流程

先调用Subject.isPermitted/hasRole,它会委托给SecurityManager,SecurityManager接着委托给Authorizer
Authorizer是真正的授权者,若调用isPermitted(“user:view”),它会先通过PermisssionResolver把字符串转换成相应的Permission实例
Authorizer会判断Realm的角色是否和传入的角色匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,若匹配,则isPermitted返回true
shiro框架学习_第2张图片

shiro加密

md5是一个摘要算法, 任何长度得出的结果都是128位二进制数, 一般以16进制展示

public class MD5 {
    public static void main(String[] args) {
        String pw="z3";
        Md5Hash md5Hash=new Md5Hash(pw);
        System.out.println("直接加密:"+md5Hash.toHex());
        Md5Hash md5Hash1=new Md5Hash(pw,"salt");
        System.out.println("带盐加密:"+md5Hash1.toHex());
        Md5Hash md5Hash2=new Md5Hash(pw,"salt",3);
        System.out.println("带盐的三次加密:"+md5Hash2.toHex());
        SimpleHash simpleHash=new SimpleHash("MD5",pw,"salt",3);//可以指定加密方式
        System.out.println("Md5Hash的父类加密:"+simpleHash.toHex());
    }
}

shiro框架学习_第3张图片

登录认证

✨ 1. 收集用户身份/凭证,如用户名、密码
✨ 2. 调用Subject.login进行登录,失败将得到相应异常,并提示用户错误信息
✨ 3. 创建自定义Realm类,继承org.apache.shiro.realm.AuthorizingRealm类,实现doGetAuthenticationInfo()方法

shiro自定义认证

shiro默认的登录认证不加密,若想实现加密认证需要自定义登录认证,自定义Realm。

一个普通的maven项目

//相关依赖,若导入失败可切换版本
<dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.5.2</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>
    </dependencies>
//自定义的Realm
public class MyRealm extends AuthenticatingRealm {
    //shiro的login方法底层会调用该方法进行认证,该方法只获取需要认证的信息,认证逻辑仍需shiro的低层认证逻辑完成
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取身份信息
        String s = authenticationToken.getPrincipal().toString();
        //获取凭证信息
        String password = new String((char[]) authenticationToken.getCredentials());
        //获取数据库中数据
        if (s.equals("zhangsan")){
            String pwd="7174f64b13022acd3c56e2781e098a5f";
            //创建封装要校验逻辑的对象,封装数据返回
            AuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(
                    authenticationToken.getPrincipal(),pwd,
                    ByteSource.Util.bytes("salt"),authenticationToken.getPrincipal().toString());
            return authenticationInfo;
        }
        return null;
    }
}
//要让自定义Realm生效,需要在ini配置文件或springboot中进行配置
[main]
md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3
;指定自定义Ream的路径
myrealm=com.qjy.shiro.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm

[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1,role2
lisi=l4
[roles]
role1=user:insert,user:select
//登录认证
public class ShiroRun {
    public static void main(String[] args) {
        //1初始化获取SecurityManager
        IniSecurityManagerFactory iniSecurityManagerFactory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager instance = iniSecurityManagerFactory.getInstance();//获取相关实例,拿到认证管理
        SecurityUtils.setSecurityManager(instance);//将认证管理器存到工具
        //2通过工具获取Subject对象
        Subject subject = SecurityUtils.getSubject();
        //3创建token对象,web应用用户名密码从页面传递
        AuthenticationToken token = new UsernamePasswordToken("zhangsan", "z3");
        //4完成登录
        try {
            subject.login(token);
            boolean role1 = subject.hasRole("role1");
            System.out.println("登录成功");
            System.out.println("是否有该角色:"+role1);
            boolean permitted = subject.isPermitted("user:insert");
            System.out.println("是否有该权限:"+permitted);
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            System.out.println("该用户不存在");
        } catch (IncorrectCredentialsException e) {
e.printStackTrace();
            System.out.println("密码错误");
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }
    }
}

shiro框架学习_第4张图片

返回false正常,因为这时候doGetAuthorizationInfo里没有给权限,权限和角色不是从ini取的(弹幕是个好东西)

整合springboot

‍先搭建好环境,建好数据库

//相关依赖
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.1</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</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>
    </dependencies>
# yml配置
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&useSSL=false
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root

shiro框架学习_第5张图片

//controller层
@RestController
@RequestMapping("/user")
public class UserController {
  
    @GetMapping("/login")
    public String userLogin(String name, String pwd) {
        //获取subject对象
        Subject subject = SecurityUtils.getSubject();
        //封装请求的数据到token
        AuthenticationToken token = new UsernamePasswordToken(name, pwd);
        //调用login方法进行验证
        try {
            subject.login(token);
            return "登录成功";
        } catch (AuthenticationException e) {
            e.printStackTrace();
            System.out.println("登录失败");
            return "登录失败";
        }
    }
}

shiro内置过滤器详解

//shiro的过滤拦截
@Configuration
public class ShiroConfig {
    @Resource
    private MyRealm myRealm;

    //配置SecurityManager
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager() {
        //创建DefaultWebSecurityManager对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //创建加密对象,设置相关属性
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");//表示采用md5加密
        matcher.setHashIterations(3);//设置加密次数
        //将加密对象储存在MyRealm
        myRealm.setCredentialsMatcher(matcher);
        //将MyRealm储存在DefaultWebSecurityManager对象
        defaultWebSecurityManager.setRealm(myRealm);
        return defaultWebSecurityManager;
    }
    //配置shiro内置过滤器拦截
    @Bean
    public DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        //设置不认证即可访问的资源
        definition.addPathDefinition("/user/login","anon");
        //设置需要登录认证的拦截范围
        definition.addPathDefinition("/**","authc");
        return definition;
    }
    
    @Bean
    public DefaultWebSecurityManager securityManager(MyRealm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        // [重点]解决报错 org.apache.shiro.UnavailableSecurityManagerException
        ThreadContext.bind(securityManager);
        return securityManager;
    }

}

✨最后一个bean注入可以解决【Exception】org.apache.shiro.UnavailableSecurityManagerException(解决问题的文章)

//自定义的shiro
@Component
public class MyRealm extends AuthorizingRealm {
    @Resource
    private IUserService userService;
    //自定义授权方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }
    //自定义登录认证方法
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取用户登录信息
        String name=authenticationToken.getPrincipal().toString();
        //从业务层获取用户信息(即从数据库获取)
        User user = userService.getByName(name);
        //非空判断,将数据封装返回
        if (user!=null){
            AuthenticationInfo authenticationInfo=new SimpleAuthenticationInfo(
                    authenticationToken.getPrincipal(),user.getPwd(),
                    ByteSource.Util.bytes("salt"),
                    authenticationToken.getCredentials().toString()
            );
            return authenticationInfo;
        }
        return null;
    }
}
//即查询数据库
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    @Resource
    private UserMapper userMapper;
    @Override
    public User getByName(String name) {
        LambdaQueryWrapper<User> queryWrapper=new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getName,name);
        System.out.println();
        return userMapper.selectOne(queryWrapper);
    }
}

多个realm的认证策略

实现原理

当应用程序配置多个realm时(r如账号密码校验,手机号校验等),shiro的ModularRealmAuthenticator会使用内部的AuthenticationStrategy判断认定成功还是失败
AuthenticationStrategy是一个无状态组件组件,他在身份验证中被询问四次(四次交互需要的必要状态都将被作为方法参数):
✨在所有realm被调用之前
✨在调用realm的getAuthenticationInfo方法之前
✨在调用realm的getAuthenticationInfo方法之后
✨在所有realm被调用之后
认证策略的另外一项工作就是聚合所有realm的结果信息封装至AuthenticInfo实例中,并将此信息返回,以此作为Subject的身份信息
shiro框架学习_第6张图片

//代码实现
 @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager() {
        //创建DefaultWebSecurityManager对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //创建认证对象,并设置认证策略
        ModularRealmAuthenticator modularRealmAuthenticator=new ModularRealmAuthenticator();
        //认证方式需全部realm都成功
        modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
        defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);
        //封装myRealm集合
        List<Realm> list=new ArrayList<>();
        list.add(myRealm1);
        list.add(myRealm2);
        //将myRealm存入defaultWebSecurityManager对象
        defaultWebSecurityManager.setRealms(list);
        return defaultWebSecurityManager;
    }

remember me功能

即再次访问相同资源时可以无需登录
流程:
✨首先在登录页面选中rememberMe,然后登录成功,如果是浏览器登录,一般会把RememberMe的cookie写到客户端并保存
✨关闭浏览器再打开,浏览器还会记住你
✨访问一般的网页,仍能识别你,且能正常访问
✨如果在电商平台进行支付时,仍需查验身份

//shiroConfiguration添加的相关配置
  //配置SecurityManager
    @Bean
    public DefaultWebSecurityManager defaultWebSecurityManager() {
        //创建DefaultWebSecurityManager对象
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //创建加密对象,设置相关属性
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        matcher.setHashAlgorithmName("md5");//表示采用md5加密
        matcher.setHashIterations(3);//设置加密次数
        //将加密对象储存在MyRealm
        myRealm.setCredentialsMatcher(matcher);
        //将MyRealm储存在DefaultWebSecurityManager对象
        defaultWebSecurityManager.setRealm(myRealm);
        //设置rememberMe
        defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
        return defaultWebSecurityManager;
    }
    //cookie属性设置
    public SimpleCookie rememberMeCookie(){
        SimpleCookie cookie=new SimpleCookie("rememberMe");
        //设置跨域
        cookie.setPath("/");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(30*24*60*60);
        return cookie;
    }
    public CookieRememberMeManager rememberMeManager(){
        CookieRememberMeManager meManager=new CookieRememberMeManager();
        meManager.setCookie(rememberMeCookie());
        //注意 setCipherKey的参数必须是16位,否则后面rememberMe不成功
        meManager.setCipherKey("1234567890987654".getBytes());
        return meManager;
    }
    //配置shiro内置过滤器拦截
    @Bean
    public DefaultShiroFilterChainDefinition defaultShiroFilterChainDefinition(){
        DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
        //设置不认证即可访问的资源
        definition.addPathDefinition("/user/login","anon");
        //设置需要登录认证的拦截范围
        definition.addPathDefinition("/**","authc");
		//添加存在用户的过滤器(rememberMe)
        definition.addPathDefinition("/**","user");
        return definition;
    }

✨在登录方法中添加参数**@RequestParam(defaultValue = “false”) boolean rememberMe**,通过前端传过来的值判断是否“记住我”
✨登出,在前端设置相关按钮,在shiroConfig设置拦截

definition.addPathDefinition(“/logout”,“logout”);

权限与角色

用户登录成功后,需要验证其具有的的权限,而shiro提供了Realm的doGetAuthorizationInfo方法进行判断,触发显现判断的两个方法:
✨在页面属性通过shiro:*****(标签)属性判断
✨在接口服务通过注解@Requires****(关键字)进行判断

接口服务注解

‍验证用户是否登录=等同于subject.isAuthenticated()(成功返回true)

@RequiresAuthentication

‍验证用户是否被记忆=subject.isRemembered()(被记忆返回true)

@RequiresUser

‍验证是否是一个guest请求,此时subject.getPrincipal()为空

@RequiresGuest

‍验证subject是否有相应角色,有则访问,无则抛异常AuthorzationException

@RequiresRoles
shiro框架学习_第7张图片

‍验证subject是否有相应权限,有则访问,无则抛异常AuthorzationException

@RequiresPermissions
shiro框架学习_第8张图片

获取角色进行验证

✨先在数据库中建立相关表

	//mapper层查询数据库
    @Select("select role FROM roles in id=(SELECT rId FROM role_user WHERE uId=(SELECT id FROM `user`WHERE name=#{name}));")
    List<String> getRoles(@Param(value = "name")String name);
    
   //service层调用
    public List<String> getRoles(String name) {
        return userMapper.getRoles(name);
    }

 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //创建对象
        SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo();
        String s = principalCollection.getPrimaryPrincipal().toString();
        List<String> roles = userService.getRoles(s);
        //储存角色
        authorizationInfo.addRoles(roles);
        //返回信息
        return authorizationInfo;
    }

获取权限进行验证

与获取角色信息验证类似,只不过自定义授权方法中通过authorizationInfo.addStringPermissions();方法接收保存权限信息的list

EhCache实现缓存

EhCache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存,JavaEE和轻量级容器。可以和大部分Java 项目整合,它支持内存和磁盘存储,默认存储在内存中,内存不够时可把数据同步到磁盘中。EhCache支持基于Filter的Cache实现,也支持Gzip压缩算法。
EhCache直接在JVM虚拟机中缓存,速度快,效率高
EhCache缺点时缓存共享麻烦,集群分布式应用使用不方便。

//相关依赖
 		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
               <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
    		<groupId>commons-io</groupId>
   			<artifactId>commons-io</artifactId>
    		<version>2.6</version>
		</dependency>

✨在resource中添加ehcache配置{相关配置),也可自行在网上查找

 //缓存管理器
    public EhCacheManager getCacheManager() {
        EhCacheManager manager = new EhCacheManager();
        InputStream is=null;
        try {
            //通过该路径获取数据流
            is= ResourceUtils.getInputStreamForPath("classpath:ehcache/ehcache-shiro.xml");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //如果存在错误可能是导包错误
        CacheManager cacheManager = new CacheManager(is);
        manager.setCacheManager(cacheManager);
        return manager;
    }

✨将该manager利用setCacheManager方法配置到SecurityManager中

会话管理

SessionManager

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

会话管理实现

SessionManager由SecurityManager管理。shiro提供了三种实现:
shiro框架学习_第9张图片
✨session的实现

Session session=SecurityUtils.getSubject().getSession();
session.setAttribute(“key”,“value”)

✨说明

Controller中的request,在shiro.过滤器中的doFilerInternal方法,被包装成ShiroHttpServletRequest。
SecurityManager和SessionManager.会话管理器决定session来源于ServletReques.t还是由Shiro管理的会话。
无论是通过request.getSession或 subject.getSession获取到session,操作session,两者都是等价的。e

shiro框架学习_第10张图片
会不会不知道,反正跟着尚硅谷看完了,撒花,撒花,撒花

你可能感兴趣的:(学习,系统安全,java)