0


Shiro使用

Shiro可以帮助我们完成认证、授权、加密、会话管理、与Web集成、缓存等

Shiro的使用方法

基本功能模块

  • Authentication:身份认证/登录,验证用户是不是拥有相应的身份。
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情。
  • Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话 中;会话可以是普通JavaSE环境的,也可以是如Web环境的。
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。
  • Web Support:Shiro 的 web 支持的 API 能够轻松地帮助保护 Web 应用程序。
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。
  • Concurrency:Apache Shiro 利用它的并发特性来支持多线程应用程序。
  • Testing:测试支持的存在来帮助你编写单元测试和集成测试,并确保你的能够如预期的一样安全。
  • “Run As”:一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用
  • “Remember Me”:记住我。

内部结构

在这里插入图片描述

  • Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;
  • SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
  • Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
  • Authrizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
  • Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
  • SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据; 给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
  • SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
  • CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。

使用方法

导入shiro坐标

认证与授权

将安全数据存入到shiro进行保管,登陆认证的主要目的,比较用户名和密码是否与数据库中的一致
shiro登陆与传统登陆:
1.传统登录
前端发送登录请求 => 接口部分获取用户名密码 => 程序员在接口部分手动控制
2.shiro登录
前端发送登录请求 => 接口部分获取用户名密码 => 通过subject.login => realm域的认证方法

授权:即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情。授权的主要目的就是根据认证数据获取到用户的权限信息

认证流程

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

授权流程

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

测试认证与授权:

publicclassShiroTest{privateSecurityManager securityManager;@Beforepublicvoidinit(){//1.根据配置文件创建SecurityManagerFactoryFactory<SecurityManager> factory =newIniSecurityManagerFactory("classpath:shiro-test.ini");//2.通过工厂获取SecurityManagerSecurityManager securityManager = factory.getInstance();//3.将SecurityManager绑定到当前运行环境SecurityUtils.setSecurityManager(securityManager);}@TestpublicvoidtestLogin(){Subject subject =SecurityUtils.getSubject();String username ="zhangsan";String password ="123456";UsernamePasswordToken token =newUsernamePasswordToken(username,password);//执行login-->realm域中的认证方法
        subject.login(token);//授权:-->realm域中的授权方法System.out.println(subject.hasRole("role1"));System.out.println(subject.isPermitted("user:save"));}}

基于配置的授权

//配置请求连接过滤器配置//匿名访问(所有人员可以使用)
filterMap.put("/user/home","anon");//具有指定权限访问
filterMap.put("/user/find","perms[user-find]");//认证之后访问(登录之后可以访问)
filterMap.put("/user/**","authc");//具有指定角色可以访问
filterMap.put("/user/**","roles[系统管理员]");

基于配置的方式进行授权,一旦操作用户不具备操作权限,目标地址不会被执行。会跳转到指定的url连接地址。所以需要在连接地址中更加友好的处理未授权的信息提示。

基于注解的授权

@RequiresPermissions(value = “权限名”)配置到方法上,表明执行此方法必须具有指定的权限
@RequiresRoles(value = “角色名”) 配置到方法上,表明执行此方法必须具有指定的角色
一旦操作用户不具备操作权限,目标方法不会被执行,而且会抛出AuthorizationException 异常

Realm域

Realm域:Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源

自定义realms对象类继承AuthorizingRealm
重写方法

  • doGetAuthorizationInfo:授权 - 获取到用户的授权数据(用户的权限数据)- 构造认证数据
  • doGetAuthenticationInfo:认证 - 获取登陆uptoken- 根据用户名密码登录,将用户数据保存(安全数据)
publicclassPermissionRealmextendsAuthorizingRealm{/**
     * 自定义realm名称
     */publicvoidsetName(String name){super.setName("permissionRealm");}//授权:授权的主要目的就是根据认证数据获取到用户的权限信息/**
     * principalCollection:包含了所有已认证的安全数据
     * AuthorizationInfoInfo:授权数据
     */protectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollection principalCollection){System.out.println("执行授权方法");//1.获取安全数据  username,用户idString username =(String)principalCollection.getPrimaryPrincipal();//构造认证数据//2.根据id或者名称查询用户//3.查询用户的角色和权限信息List<String> perms =newArrayList<>();
        perms.add("user:save");
        perms.add("user:update");List<String> roles =newArrayList<>();
        roles.add("role1");
        roles.add("role2");//4.构造返回SimpleAuthorizationInfo info =newSimpleAuthorizationInfo();//设置权限集合,将查询的权限数据保存到simpleAuthorizationInfo
        info.addStringPermissions(perms);//设置角色集合,将查询的角色数据保存到simpleAuthorizationInfo
        info.addRoles(roles);return info;}//认证:认证的主要目的,比较用户名和密码是否与数据库中的一致//将安全数据存入到shiro进行保管//参数:authenticationToken登录构造的usernamepasswordtokenprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationToken authenticationToken)throwsAuthenticationException{System.out.println("执行认证方法");//1.构造uptokenUsernamePasswordToken upToken =(UsernamePasswordToken) authenticationToken;//2.获取输入的用户名密码String username = upToken.getUsername();String password =newString(upToken.getPassword());//3.根据用户名查询数据库,正式系统查询//4.比较密码和数据库中的密码是否一致(密码可能需要加密)if("123456".equals(password)){//5.如果成功,向shiro存入安全数据SimpleAuthenticationInfo info =newSimpleAuthenticationInfo(username,password,getName());//1.安全数据,2.密码。3。当前realm域名称return info;}else{//6.失败,抛出异常或返回nullthrownewRuntimeException("用户名或密码错误");}}}

SecurityManager

用于协调内部的多个组件完成全部认证授权的过程。通过调用realm
完成认证与登录。
配置ShiroConfiguration类

@ConfigurationpublicclassShiroConfiguration{//1.创建realm@BeanpublicCustomRealmgetRealm(){returnnewCustomRealm();}//2.创建安全管理器@BeanpublicSecurityManagergetSecurityManager(CustomRealm realm){//使用默认的安全管理器DefaultWebSecurityManager securityManager =newDefaultWebSecurityManager();//将自定义的realm交给安全管理器统一调度管理
        securityManager.setRealm(realm);return securityManager;}//开启对shior注解的支持@BeanpublicAuthorizationAttributeSourceAdvisorauthorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor advisor =newAuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);return advisor;}}

过滤器

Filter解释anon无参,开放权限,可以理解为匿名用户或游客authc无参,需要认证logout无参,注销,执行后会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的urlauthcBasic无参,表示 httpBasic 认证user无参,表示必须存在用户,当登入操作时不做检查ssl无参,表示安全的URL请求,协议为 httpsperms[user]参数可写多个,表示需要某个或某些权限才能通过,多个参数时写 perms[“user,admin”],当有多个参数时必须每个参数都通过才算通过roles[admin]参数可写多个,表示是某个或某些角色才能通过,多个参数时写 roles[“admin,user”],当有多个参数时必须每个参数都通过才算通过rest[user]根据请求的方法,相当于 perms[user:method],其中 method 为 post,get,delete 等port[8081]当请求的URL端口不是8081时,跳转到当前访问主机HOST的8081端口
anon, authc, authcBasic, user 是第一组认证过滤器,perms, port, rest, roles, ssl 是第二组授权过滤器,要通过授权过滤器,就先要完成登陆认证操作(即先要完成认证才能前去寻找授权) 才能走第二组授权器(例如访问需要 roles 权限的 url,如果还没有登陆的话,会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url )

在ShiroConfiguration中配置shiro的过滤器工厂:

/**
     * 再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制
     *
     */@BeanpublicShiroFilterFactoryBeanshiroFilter(SecurityManager securityManager){//1.创建过滤器工厂ShiroFilterFactoryBean filterFactory =newShiroFilterFactoryBean();//2.设置安全管理器
        filterFactory.setSecurityManager(securityManager);//3.通用配置(跳转登录页面,为授权跳转的页面)
        filterFactory.setLoginUrl("/autherror?code=1");//跳转url地址
        filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授权的url//4.设置过滤器集合/**
         * 设置所有的过滤器:有顺序map
         *     key = 拦截的url地址
         *     value = 过滤器类型
         *
         */Map<String,String> filterMap =newLinkedHashMap<>();
  
        filterMap.put("/user/**","authc");//当前请求地址必须认证之后可以访问//设置过滤器
        filterFactory.setFilterChainDefinitionMap(filterMap);return filterFactory;}

会话管理SessionManager

shiro提供的会话,不依赖于任何底层容器,可以独立使用。管理所有Subject的session包括创建、维护、删除、失效、验证等工作。SessionManager由SecurityManager管理

三种默认实现:

  1. DefaultSessionManager:用于JavaSE环境
  2. ServletContainerSessionManager:用于Web环境,直接使用servlet容器的会话。
  3. DefaultWebSessionManager:用于Web环境,自己维护会话(自己维护着会话,直接废弃了Servlet容器的会话管理)。

在web程序中,通过shiro的Subject.login()方法登录成功后,用户的认证信息实际上是保存在HttpSession中。

自定义会话管理器

会话管理器可以指定sessionId的生成以及获取方式

一般结合redis进行会话管理 -->自定义shiro会话管理器extends DefaultWebSessionManager–>自定义SessionDao实现,指定Redis配置

  • 头信息中具有sessionid - 请求头:Authorization: sessionid- 指定sessionId的获取方式
publicclassCustomSessionManagerextendsDefaultWebSessionManager{protectedSerializablegetSessionId(ServletRequest request,ServletResponse response){//获取请求头Authorization中的数据String id =WebUtils.toHttp(request).getHeader("Authorization");if(StringUtils.isEmpty(id)){//如果没有携带,生成新的sessionIdreturnsuper.getSessionId(request,response);}else{//返回sessionId;        
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,"header");//sessionId具体是什么
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);return id;}}}

配置基于redis的会话管理

在ShiroConfiguration配置类中添加Redismanager, RedisCasheManager(缓存机制), RedisSessionDao, 自定义的会话管理DefaultWebSessionManager

@Value("${spring.redis.host}")privateString host;@Value("${spring.redis.port}")privateint port;@Value("${spring.redis.password}")privateString password;/**
     * redis的控制器,操作redis
     */publicRedisManagerredisManager(){RedisManager redisManager =newRedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);//redisManager.setPassword(password);return redisManager;}/**
     * sessionDao
     */publicRedisSessionDAOredisSessionDAO(){RedisSessionDAO sessionDAO =newRedisSessionDAO();
        sessionDAO.setRedisManager(redisManager());return sessionDAO;}/**
     * 会话管理器
     */publicDefaultWebSessionManagersessionManager(){CustomSessionManager sessionManager =newCustomSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());return sessionManager;}/**
     * 缓存管理器
     */publicRedisCacheManagercacheManager(){RedisCacheManager redisCacheManager =newRedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());return redisCacheManager;}

SecurityManager中设置自定义session管理

@BeanpublicSecurityManagergetSecurityManager(CustomRealm realm){//使用默认的安全管理器DefaultWebSecurityManager securityManager =newDefaultWebSecurityManager();
        securityManager.setRealm(realm);//将自定义的会话管理器注册到安全管理器中
        securityManager.setSessionManager(sessionManager());//将自定义的redis缓存管理器注册到安全管理器中
        securityManager.setCacheManager(cacheManager());return securityManager;}

使用细节

  • 存入redis的数据对象类需要实现AuthCachePrincipali接口
  • 在多个微服务中使用,配置公共的未认证未授权的Controller(需要跨域@CrossOrigin)–>作为公共错误跳转
  • 认证授权分离–公共模块下创建授权realm(只处理授权数据),认证方法在登陆模块补全–>配置用户登陆的realm域继承公共realm域 - 获取用户数据- 构造安全数据- 构造方法SimpleAuthenticationInfo( Object principal, Object credentials, String realmName )
  • 在shiro的会话管理中,可以使用请求头中的内容作为sessionid
String id =WebUtils.toHttp(request).getHeader(AUTHORIZATION);if(StringUtils.isEmpty(id)){//如果没有携带id参数则按照父类的方式在cookie进行获取returnsuper.getSessionId(request, response);}else{
    id = id.replace("Bearer ","");//如果请求头中有 authToken 则其值为sessionId

从redis获取session数据

  • Subject subject = SecurityUtils.getSubject();
  • 获取所有的安全数据集合`PrincipalCollection principals = subject.getPrincipals();
  • 获取安全数据(ProfileResult)principals.getPrimaryPrincipal();
标签: java spring boot 安全

本文转载自: https://blog.csdn.net/qq_43460315/article/details/126351542
版权归原作者 我爱松子鱼 所有, 如有侵权,请联系我们删除。

“Shiro使用”的评论:

还没有评论