shiro笔记

链接:https://www.w3cschool.cn/shiro/co4m1if2.html

shiro功能:

    认证,授权,加密管理,缓存,会话管理,与web集成

  1.Authentication: 身份认证/登录 验证用户是不是拥有相应的身份

  2.Authorization:授权,权限验证,验证某个已认证的用户是否拥有某个权限

  3.Session Management:会话管理,用户登录后就是一次会话

  4.Cryptography:加密,保护数据的安全性

  5.Web Support:Web支持

   6.Caching:缓存,比如用户登录后,其用户信息,拥有的角色不用每次都去查

    7.test:测试

    8.Run As:允许一个用户假装另一个用户的身份进行访问

    9.Remember me:记住我功能

    Subject:主体

    SecurityManager:安全管理器

    Realm:域,数据源

    Shiro身份验证

        需要提供principals和credentials给shiro,从而应用验证用户身份

        首先通过 new IniSecurityMangaerFactory 指定一个ini配置

        @TestpublicvoidtestHelloworld(){

    //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager      Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

    //2、得到SecurityManager实例 并绑定给SecurityUtils    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();

    SecurityUtils.setSecurityManager(securityManager);

    //3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)    Subject subject = SecurityUtils.getSubject();

    UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");

    try {

        //4、登录,即身份验证        subject.login(token);

    } catch (AuthenticationException e) {

        //5、身份验证失败    }

    Assert.assertEquals(true, subject.isAuthenticated()); //断言用户已经登录    //6、退出    subject.logout();

}

身份调用流程:

    1.先调用Subject.login(token) 进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils.setSecurityManager()设置

    2.SecurityManager负责身份逻辑验证,委托给Authenticator进行身份验证

    3.Authenticator是真正的身份验证者,shiro API中的核心身份认证入口,可以插入自己的实现

    4.Authenticator会委托给相应的AuthenticationStrategy进行Realm身份验证,默认ModuleRealAtuhenticator会调用AuthenticationStrategy进行身份验证

    5.Authenticator会把相应的token传入Realm,从Realm回去身份验证信息

Authenticator的职责是验证用户账号

    public AuthenticationInfo authenticate(AuthenticationToken authenticationToken) throws AuthenticationException;

    FirstSuccessfulStrategy:只有有一个Realm验证成功即可,只返回第一个成功的realm身份验证成功的认证信息

    AtLesatOneSuccessfulStrategy:全部都成功 才可以

    也可以自定义AuthenticationStrategy

    AuthenticationInfo beforeAllAttempts,beforeAttempt,afterAttempt,afterAllAttempts

shiro 授权

    访问控制:主体,资源,权限,角色

    资源:在应用中用户可以访问的url

    权限:安全策略中的原子授权单位

    角色:角色代表了操作集合,可以理解为权限的集合

    隐式角色:直接通过角色来验证用户有没有操作权限

    显示角色:在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合

  shiro支持三种方法的授权

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

        Subject subject = SecurityUtils.getSubject();

        if(subject.hasRole("admin")){

            //有权限

        }else{

            //无权限

        }

    注解式:

        @RequiresRoles("admin")

        public void hello(){}

    JSP/GSP通过标签

         <shiro:hasRole name="admin">

          </shiro:hasRole>

   Permission:权限

         subject().checkPermissions("system:user:update,delete");

   自定义Realm

        推荐继承AuthorizingRealm

        因为AuthenticationInfo中的

        doGetAuthenticationInfo(AuthenticationToken token):表示获取身份验证信息

        doGetAuthorizationInfo(PrincipalCollection principals):表示根据用户身份获取授权信息

      编码加密:

             base64:

                String str = "hello"

                String base64Encoded = Base64.encodeToString(str.getBytest());

                String str = base64.decodeToString(base64Encoded);

            散列算法:

                常见的散列算法如MD5,SHA等,一般进行散列时最好提供一个salt

                因为如果直接对密码进行三类相对来说破解比较容易,所以我们增加了salt,这样的散列值相对来说更难破解

          String str = "hello";

          String  salt = "123";

           String sha1 = new SHA256Hash(str,salt).toString();

           shiro 还提供了对称式加密、解密算法的支持,如AES,Blowfish等

        密码重试次数限制:

            Element element = passwordRetryache.get(username);

            AtomicInteger retryConunt = element.getObjectValue();

            retryCount.incrementAndGet() 重试的次数

Realm:

    UserRealm父类AuthorizingRealm建获取Subject相关信息分为两步:

        获取身份验证信息以及授权信息

    doGetAuthenticationInfo 获取身份验证信息

    doGetAuthentizationInfo 获取授权信息

    AuthenticationInfo:

        如果Realm是AuthenticatingRealm子类,则提供给AuthenticatingRealm内部使用的CredentialsMathcer进行凭据验证

        提供给SecurityManger来创建Subject

        MergableAuthenticationInfo用于提供多Realm时合并AuthenticationInfo的功能,主要合并Principal

        SimpleAccountRealm提供了相关API来动态维护SimpleAccount,动态来增删改查SimpleAccount

        Object getPrincipal()

        PrincipalCollection getPrincipalse();

        void loign(AuthenticationToken token) throws AuthenticationException

        boolean isAuthenticated();

        boolean isRemembered();

    RunAs 实现允许A假设为B的身份进行访问

    步骤:身份验证,授权,将相应的数据存储到会话,切换身份,退出

    Shiro Web集合

            通过一个ShiroFilter入口来控制拦截需要安全控制的url,然后进行相应的控制

          url使用Ant风格

            Ant路径通配符支持?..


会话管理:

    会话即用户访问应用时保持的连接关系,在多次交互中能够识别出当前用户是谁,且在多次交互中保留一些数据,且在用户退出之前,识别当前用户是谁

    获取会话方法

     Subject subject = SecurityUtils.getSubject();

      Session session = subject.getSession();

    session.getId();

    获取会话唯一id

    session.getHost();

     获取主机地址

    设置会话过期时间

    获取 / 设置当前 Session 的过期时间;如果不设置默认是会话管理器的全局过期时间。

    session.getTimeout();

    session.setTimeout(毫秒);

    设置当前Session的过期时间

     session.getStartTimestamp();

      session.getLastAccessTime()

    获取会话的启动时间及最后访问时间

    session.touch()

    更新最后的访问时间

    session.stop()

    销毁会话

会话管理器

    SecurityManager提供的接口

    Session start(SessionContext context);启动会话

    Session getSession(SessionKey key) throws SessionException; 根据会话key获取会话

会话管理

    DefaultSessionManager:DefaultSecurityManager使用的默认实现,用于JavaSE环境

    ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话

    DefaultWebSessionManager:用于Web环境的实现,可以代替ServlertContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理

    如果使用ServletContainerSessionManager进行会话管理,Session的超时依赖于底层Servlet容器的超时时间,可以在web.xml中配置其会话的超时时间

  <session-config>           

      <session-timeout>30</session-timeout>      

  </session-config>

在Servlet容器中,默认使用JSESSIONID Cookie维护会话,且会话默认是跟容器绑定的,在某些情况下可能需要使用自己的会话机制,

    sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie    //sessionManager创建会话Cookie的模板

    sessionIdManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager//指定Manager来维护会话

    sessionIdCookie.name=sid //cookie的名字,默认为JSESSIONID

    sessionIdCookie.maxAge=1800//设置Cookie的过期时间,秒为单位,默认-1表示关闭浏览器时过期

    sessionIdCookie.httpOnly=true//如果设置true,则客户端不会暴露给客户端脚本代码,使用httpOnly cookie有助于减少某些类型的跨站点脚本攻击,使用httpOnly有助于减少某些类型的跨站点脚本攻击

    sessionManager.sessionIdCookieEnabled//是否启用、禁用 Session Id Cookie,默认是启用的;如果禁用后,将不会使用Session Cookie

会话监听器

    方法继承SessionListener会有三个继承方法

        onStart //会话创建时触发

        onExpiration //会话过期时触发

        onStop //退出、会话过期时 触发

    只监听某一个时间,可以继承SessionListenerAdapter实现

    会话存储/持久化

        提供SessionDao用于会话CRUD,即DAO模式实现

            Serializable create(Session session);

            Session readSession(Serializable sessionId)  获取会话

            void update(Session session) 更新会话

            void delete(Session session) 删除会话

            Collection<Session> getActiveSessions(); 回去当前所有活跃用户

        AbstractSessionDAO提供了SessionDao的基础实现

        CachingSessionDAO提供了对开发者透明的会话缓存功能,只需设置相应的CacheManager即可

        MemorySessionDAO直接在内存中进行会话维护,EnterpriseCacheSessionDAO提供了缓存功能的会话维护

    默认条件下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话

            sessionDAO.activeSessionCacheName:设置Session缓存名字,默认就是shiro-activeSessionCache;

            cacheManager:缓存管理器,用于管理缓存的,此处使用Ehcache

            cacheManager.cacheManagerConfigFile:设置ehcache缓存的配置文件

            securtiyManager.cacheManager:设置 SecurityManager 的 cacheManager,会自动设置实现了 CacheManagerAware 接口的相应对象,如 SessionDAO 的 cacheManager;

            自定义SesiionDao,继承CachingSessionDAO即可

            doCreate/doUpdate/doDelete/doReadSession 分别代表创建 / 修改 / 删除 / 读取会话;此处通过把会话序列化后存储到数据库实现;接着在 shiro-web.ini 中配置:

sessionDAO=com.github.zhangkaitao.shiro.chapter10.session.dao.MySessionDAO

其他设置和之前一样,因为继承了 CachingSessionDAO;所有在读取时会先查缓存中是否存在,如果找不到才到数据库中查找。

会话验证:

    如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro提供了会话验证调度器SessionValidationScheduler来做这件事情

sessionValidationScheduler=org.apache.shiro.session.mgt.ExecutorServiceSessionValidationSchedulersessionValidationScheduler.interval = 3600000sessionValidationScheduler.sessionManager=$sessionManagersessionManager.globalSessionTimeout=1800000sessionManager.sessionValidationSchedulerEnabled=truesessionManager.sessionValidationScheduler=$sessionValidationScheduler

    设置含义:

        sessionValidationScheduler:会话验证调度器

        sessionValidationScheduler.interval:设置调度时间间隔,单位毫秒

        sessionValidationScheduler.sessionManager:设置会话调度器进行会话验证时的会话管理器

        sessionManager.globalSessionTimeout:设置全局会话超越时间,默认30分钟

        sessionManager.sessionValidationSchedulerEnabled:是否开启会话验证器,默认是开启的;

        sessionManager.sessionValidationScheduler:设置会话验证调度器,默认就是使用 ExecutorServiceSessionValidationScheduler。

sessionFactory是创建会话的工程,根据相应的subject上下文信息来创建会话,默认提供了SimpleSessionFactory来创建SimpleSession会话‘’    

   SessionFactor创建会话工厂的。onlineSession用于保存当前登录用户的在线状态

public class OnlineSessionFactory implements SessionFactory {

    @Override public Session createSession(SessionContext initData) {

        OnlineSession session = new OnlineSession();

        if (initData != null && initData instanceof WebSessionContext) {

            WebSessionContext sessionContext = (WebSessionContext) initData;

            HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest();

            if (request != null) {

                session.setHost(IpUtils.getIpAddr(request));                                                                                                                                     session.setUserAgent(request.getHeader("User-Agent"));

                session.setSystemHost(request.getLocalAddr() + ":" +         request.getLocalPort()); } }

               return session;

    }

}

    根据会话上下文创建相应的onlineSession


缓存机制

    shiro提供了类似spring的cache抽象,即shiro本身不实现Cache,但是对Cache进行了又抽象,方便底层的Cache实现

Realm缓存

    shiro提供了CachingRealm,其实现了CacheManagerAware接口,提供了缓存的一些基础实现

    另外AuthenticatingRealm及AuthorizingRealm分别提供了AuthenticationInfo和AuthorizationInfo信息的缓存

   userRealm.cachingEnabled:启用缓存,默认 false;

    userRealm.authenticationCachingEnabled:启用身份验证缓存,即缓存 AuthenticationInfo 信息,默认 false;

    userRealm.authenticationCacheName:缓存 AuthenticationInfo 信息的缓存名称;

    userRealm. authorizationCachingEnabled:启用授权缓存,即缓存 AuthorizationInfo 信息,默认 false;

    userRealm. authorizationCacheName:缓存 AuthorizationInfo 信息的缓存名称;

    cacheManager:缓存管理器,此处使用 EhCacheManager,即 Ehcache 实现,需要导入相应的 Ehcache 依赖,请参考 pom.xml;


    @TestpublicvoidtestClearCachedAuthenticationInfo(){

    login(u1.getUsername(), password);    //登录成功

    userService.changePassword(u1.getId(), password + "1");        //修改密码

    RealmSecurityManager securityManager =

    (RealmSecurityManager) SecurityUtils.getSecurityManager();  //清空缓存

    UserRealm userRealm = (UserRealm) securityManager.getRealms().iterator().next();       userRealm.clearCachedAuthenticationInfo(subject().getPrincipals());

    login(u1.getUsername(), password + "1");

}

Session缓存:

    Shiro Spring集成

shiro权限注解:

    @RequiresAuthentication:

        表示当前Subject已经通过login进行了身份验证,即Subject.isAuthenticated()返回true

   @RequiresUser

        表示当前Subject已经身份验证或者通过记住我的登录

    @GequiresGuest

        游客身份

    @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)

        当前角色需要admin和user

    @RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)

        表示当前Subject需要权限user:a或者user:b


RememberMe

    基本流程:

        1.当选择RememberMe时,会将信息记录到浏览器的Cookie中

        2.下次打开网页的时候浏览器还是记住你的

        3.访问一般网页服务端还是知道你是谁的,且能正常访问

        4.但是当访问淘宝等时,如果要访问订单等,就必须在进行身份验证,以确保当前用户还是你


sessionIdCookie:maxAge = -1表示浏览器关闭失效此Cookie

rememberMeCookie:即记住我的 Cookie,保存时长 30 天;

    /login.jsp = authc                               //表示访问改地址的用户必须通过登录

    /logout = logout

    /authenticated.jsp = authc

    /** = user                                //表示访问该地址的用户必须已经登录或者使用rememberMe

实例:

    如果访问http://localhost:8080/chapter13/,会登录或者rememberMe

    关闭浏览器,cookie消失

    重新打开浏览器访问http://localhost:8080/chapter13/是可以访问的

    访问http://localhost:8080/authenticated.jsp,需要重新登录

如果要自己做 RememeberMe,需要在登录之前这样创建 Token

    Subject subject = SecurtiyUtils.getObject();

    UsernamePasswordToken token = new UsernamePasswordToken(username,password)

    token.setRememberMe(true);

    subject.login(token)

    过滤器使用:

        访问一般网页,如个人在主页之类的,我们使用user拦截器即可,user拦截器只要用户登录(isRemembered()==true)或者isAuthenticated()==true登录,如果是才放行,否则会跳转到登录页面叫你重新登录

        访问特殊页面,如我的订单,需要使用authc拦截器即可,authc拦截器会判断用户是否通过Subject.login(isAuthenticated()==true)登录的,如果是才放行,否则会跳转到登录页面叫你重新登录


 Shiro SSL

       对于SSL的支持,Shiro只是判断当前url是否需要SSL登录,如果需要自动重定向到https进行访问


Shiro单点登录

        单点登录主要用于多系统集成,即在多个系统中,用户只需要到一个中央服务器登录一次即可访问这些系统中的任何一个,无需多次登录

        Jasig CAS单点系统登录分为服务端和客户端,服务器端提供单点登录,多个客户端将调整到该服务器进行验证

         大概流程:

                1.访问客户端需要登录的页面http://localhost:9080/client/,此时会跳到单点登录服务器https://localhost:8443/server/login?service=https://localhost:9443/client/cas;

                2.如果此时单点登录服务器也没有登录的话,会显示登录表单页面,输入用户名 / 密码进行登录;

                3.登录成功后服务器端会回调客户端传入的地址:https://localhost:9443/client/cas?ticket=ST-1-eh2cIo92F9syvoMs5DOg-cas01.example.org,且带着一个 ticket;

                4.客户端会把ticket提交给服务器来验证ticket是否有效,如果有效服务端将返回用户身份

                5.客户端可以再根据这个用户身份获取当前需系统用户,角色,权限信息


角色配置

1.defaultRoles/defaultPermissions:默认添加给所以CAS登录成功用户的角色和权限信息;

2.roleAttributeNames/permissionAttributeName:角色属性/权限属性名称,如果用户的角色/权限信息是从服务端返回的,此时可以使用roleAttributeNames/permissionAttributeNames得到Attributes中的角色/权限数据


Shiro综合实例


           资源:表示菜单元素,页面按钮元素等,菜单显示页面,按钮元素,对页面进行操作

  Shiro OAuth2

    OAuth2集成:

        目前很多开发平台都在使用开发API接口供开发者使用,随之而来的就是第三方应用到开发平台授权的问题,Oauth就是干这个的  

        资源拥有者:能授权访问受保护资源的一个实体,可以是一个人,那我们称之为最终用户

        资源服务者:存储受保护资源,客户端通过access token请求资源,资源服务器响应受保护资源给客户端

        授权服务器:成功验证资源拥有者是否有权限访问,有的话颁发token给客户端

        客户端:第三方应用

     流程:

            1.客户端从资源拥有者那请求授权,授权请求可以直接发给资源拥有者,或间接的通过授权服务器这种中介

            2.客户端收到一个授权许可,代表资源服务器提供的授权

            3.客户端使用它自己的私有证书及授权许可到授权服务器验证

            4.如果验证成功,则下发一个访问令牌

            5.客户端使用访问令牌向资源服务器请求受保护资源

            6.资源服务器会验证访问令牌的有效性,成功就下发访问的资源


并发登录控制

    kickoutSessionControlFilter 用于控制并发登录人数的

Shiro动态url

    通过AOP进行分时的权限管理


无状态WEB:服务器端无状态,不存储像会话这种东西,而是每次请求时带上相应的用户名进行登录

服务器端:不生成会话,而是每次请求都会带上身份进行验证

授予身份和切换身份

    使用shiro的RunAs功能,即允许一个用户假装为另一个用户的身份进行访问

    RunAsController:

        @RequestMapping

public String runasList(@CurrentUser User loginUser, Model model) {

   model.addAttribute("fromUserIds", userRunAsService.findFromUserIds(loginUser.getId()));

     model.addAttribute("toUserIds", userRunAsService.findToUserIds(loginUser.getId()));

    List<User> allUsers = userService.findAll();

    allUsers.remove(loginUser);

     model.addAttribute("allUsers", allUsers);

      Subject subject = SecurityUtils.getSubject();

      model.addAttribute("isRunas", subject.isRunAs());

      if(subject.isRunAs()) {

            String previousUsername = (String)subject.getPreviousPrincipals().getPrimaryPrincipal();

            model.addAttribute("previousUsername", previousUsername);

        }

      return "runas";

}

subject.isRunAs():判断当前用户是否是RunAs用户,即已经切换身份了

subject.getPreviousPrincipals():得到切换身份之前的身份,一个用户可以切换很多次身份

授予身份:把当前用户身份授予给另一个用户,这样另一个用户可以切换身份到该用户

@RequestMapping("/grant/{toUserId}")publicStringgrant( @CurrentUser User loginUser, @PathVariable("toUserId")Long toUserId, RedirectAttributes redirectAttributes){

    if(loginUser.getId().equals(toUserId)) {

        redirectAttributes.addFlashAttribute("msg", "自己不能切换到自己的身份");

        return "redirect:/runas";

    }

    userRunAsService.grantRunAs(loginUser.getId(), toUserId);

    redirectAttributes.addFlashAttribute("msg", "操作成功");

    return "redirect:/runas";

}

1.自己不能授予身份给自己

2.调用userRunAsService.grantRunAs帮当前登录用户的身份授予给相应的用户

回收身份

    把授予给某个用户的身份回收回来

    @RequestMapping("/revoke/{toUserId}")publicStringrevoke( @CurrentUser User loginUser, @PathVariable("toUserId")Long toUserId, RedirectAttributes redirectAttributes){

    userRunAsService.revokeRunAs(loginUser.getId(), toUserId);

    redirectAttributes.addFlashAttribute("msg", "操作成功");

    return "redirect:/runas";

}

切换身份

    subject.runAs(new SimplePrincipalCollection(switchToUser.getUsername(), ""));

切换到上一个身份

    通过 Subject.releaseRunAs() 切换会上一个身份;

此处注意的是我们可以切换多次身份,如 A 切换到 B,然后再切换到 C;那么需要调用两次 Subject. releaseRunAs() 才能切换会 A;即内部使用栈数据结构存储着切换过的用户;Subject. getPreviousPrincipals() 得到上一次切换到的身份,比如当前是 C;那么调用该 API 将得到 B 的身份。

shiro集成验证码

    在做用户登录功能时,很多时候都需要验证码支持,验证码的目的是否为了防止机器人模拟真实用户登录而恶意访问,目前对于用户来说,比较可靠的还是手机验证码

    对于验证码图片的生成,可以借助如JCaptcha这种开源java类库生成验证码图片

推荐阅读更多精彩内容