Shiro从精通到陌生

1.什么是Shiro,为什么用它

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
安全框架常用还有Spring Security 但是Spring Security与Spring依赖太高,而且还复杂
Shiro简单,容易上手

2.如何使用

2.1了解Shiro架构

在这里插入图片描述
Shiro架构从外面看主要分为
Subject
用户
SecurityManager
安全管理
Realm
领域 充当Shiro与应用程序安全数据之间的桥梁
在这里插入图片描述
这里举个梨子
在这里插入图片描述
小王就是Subject,网吧就是SecurityManager安全管理器,去公安局获取数据就是Realm
shiro主要负责的就是认证与鉴权

2.1.1 SecurityManager

安全管理器:即所有与安全有关的操作都会与其交互,管理着subject;可以看出他是shiro的核心,与其他组件进行交互。
在这里插入图片描述
由上图可以看出SecurityManager里面有

Authenticator:负责Subject认证,是一个拓展点,可以自定义实现;可以使用认证,策略。
Authorizer:授权器,用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的哪些功能;

Realm:可以有1个多个Realm,可以认为是安全实体数据源,即用于安全实体的;可以是JDBC实现,由用户提供,所以一般在应用中都需要实现自己的Realm;
SessionManager:管理Session生命周期的组件;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境
CacheManager:缓存控制器,来管理如用户,角色,权限等的缓存的;因为这些数据基本上很少改变,放到缓存

2.2导入依赖

 <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.4.RELEASE</version>
    </parent>
    <!-- 修改参数 -->
    <properties>
        <!-- 修改JDK的编译版本为1.8 -->
        <java.version>1.8</java.version>
        <!-- 修改thymeleaf的版本 -->
        <thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
        <thymeleaf-layout-dialect.version>2.0.4</thymeleaf-layout-dialect.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- 导入thymeleaf依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>


        <!-- shiro与spring整合依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!-- shiro与redis整合依赖 -->
        <dependency>
            <groupId>org.crazycake</groupId>
            <artifactId>shiro-redis</artifactId>
            <version>3.0.0</version>
            <exclusions>
                <exclusion>
                    <artifactId>shiro-core</artifactId>
                    <groupId>org.apache.shiro</groupId>
                </exclusion>
            </exclusions>
        </dependency>

    </dependencies>

在这里插入代码片

2.3配置文件

@Configuration
public class ShiroConfig {

    /**
     * 创建ShiroFilterFactoryBean  过滤器
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        /**
         * 设置安全管理器
         */
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        /**
         * Shiro内置过滤器,可以实现权限相关的拦截器
         *    常用的过滤器:
         *       anon: 无需认证(登录)可以访问
         *       authc: 必须认证才可以访问
         *       user: 如果使用rememberMe的功能可以直接访问
         *       perms: 该资源必须得到资源权限才可以访问
         *       role: 该资源必须得到角色权限才可以访问
         */


        shiroFilterFactoryBean.setLoginUrl("/admin/toLogin");
        //设置未授权提示页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/admin/toUnauthorized");
        
          //设置路径映射,注意这里要用LinkedHashMap 保证有序
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");
        //对所有页面进行认证
        filterChainDefinitionMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);


        return shiroFilterFactoryBean;
    }


   /* @Bean
    public FilterRegistrationBean delegatingFilterProxy(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName("shiroFilter");
        filterRegistrationBean.setFilter(proxy);
        return filterRegistrationBean;
    }*/

    /**
     * 创建 DefaultWebSecurityManager 
     */

    @Bean
    public SecurityManager getSecurityManager(MyShiroRealm myShiroRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        securityManager.setRealm(myShiroRealm);
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(cacheManager());
        return securityManager;
    }

    /**
     * 加密算法
     *
     * @return
     */
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        //采用MD5 进行加密
        hashedCredentialsMatcher.setHashAlgorithmName("MD5");
        //加密次数
        hashedCredentialsMatcher.setHashIterations(1);
        return hashedCredentialsMatcher;
    }


    @Bean
    public MyShiroRealm getRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {

        UserRealm shiroRealm = new UserRealm();
        //校验密码用到的算法
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);
        return shiroRealm;
    }


    /**
     * 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
     * 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
     *
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启aop注解支持
     *
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Value("${spring.redis.host}")
    String host;
    @Value("${spring.redis.port}")
    int port;
    @Value("${spring.redis.password}")
    String passWord;

    /**
     * redis管理类
     */
    public RedisManager redisManager() {
        //Redis集群使用RedisClusterManager,单个Redis使用RedisManager
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        //redisManager.setPassword(passWord);
        return redisManager;
    }

    /**
     * sessionDao
     */


    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO sessionDao = new RedisSessionDAO();
        sessionDao.setRedisManager(redisManager());
        return sessionDao;
    }

    /**
     * session管理
     */
    public DefaultWebSessionManager sessionManager() {
        CustomSessionManger customSessionManger = new CustomSessionManger();
        customSessionManger.setSessionDAO(redisSessionDAO());
        //不显示JSESSIONID
        customSessionManger.setSessionIdUrlRewritingEnabled(false);
        return customSessionManger;
    }

    /**
     *缓存管理区
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager=new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }


}

2.4 授权

在shiro中可以使用过滤器的方式配置目标地址的请求权限

2.4.1 基于配置授权
2.4.1.1 默认过滤器
    /**
     * anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
     * <p>
     * authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数
     * <p>
     * roles(角色):例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
     * <p>
     * perms(权限):例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
     * <p>
     * rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
     * <p>
     * port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString
     * <p>
     * 是你访问的url里的?后面的参数。
     * <p>
     * authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证
     * <p>
     * ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
     * <p>
     * user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查
     */
2.4.1.2 匹配规则

1."?":匹配一个字符,如“/login?”将匹配"/login123123123","/login654321",但是不匹配“/login”
2."*":匹配零个或者多个字符,如“/login*”将匹配"/login1","/login",但是不匹配“/login/1”
3."**":匹配多个或者零个路径,如“/login/* *”(markdown 问题 * * 之间应该是没有空格的),将匹配“/login/1”,“/login/2”

2.4.1.3 匹配顺序

按顺序匹配,如果第一个匹配了,就直接放行

2.4.2 基于注解

1.RequiresPermissions
配置到方法上,表明执行此方法必须具有指定的权限

  //查询
    @RequiresPermissions(value = "user-find")
    public String find() {
        return "查询用户成功";
   }

2.RequiresRoles
配置到方法上,表明执行此方法必须具有指定的角色

  @RequiresRoles(value = "root")
    public String find() {
        return "查询用户成功";
   }

2.4自定义会话管理

2.4.1什么是Shiro的会话管理

SecurityManager是shiro架构核心,协调内部安全组件(如登录,授权,数据源等),用来管理所有的subject

2.4.2自定义shiro会话管理

public class CustomSessionManger extends DefaultWebSessionManager {

    private static final String AUTHORIZATION = "Authorization";


    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
         String sessionId= WebUtils.toHttp(request).getHeader(AUTHORIZATION);
         if(sessionId==null||sessionId=="")
         {
             //第一次登录
          return super.getSessionId(request,response);
         }else
             {
                 //从哪里来
                 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
                 //值是什么
                 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
                 //要不要验证
                 request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
                 return sessionId;
             }

    }
}

2.5自定义Realm

Realm(领域): Shiro从Realm获取安全数据(如用户、角色、权限)

public class UserRealm extends AuthorizingRealm{

    @Override
    public void setName(String name) {
        super.setName("myShiroRealm");
    }



    @Autowired
    private LoginService loginService;



    /**
     * 执行认证逻辑
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        if (authenticationToken.getPrincipal() == null) {
            return null;
        }

        UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
        String userName = upToken.getUsername();
        String md5PassWord = MD5Utils.MD5Low(new String(upToken.getPassword()));
        // 可做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        UserShiro userShiro = loginService.selUserForName(userName);
        if (userShiro == null) {
            return null;
        }


        return new SimpleAuthenticationInfo(userShiro, userShiro.getPassWord(), getName());

    }


    @Autowired
    RoleService roleService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        /*简单授权信息*/
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    /*    Subject subject = SecurityUtils.getSubject();
        UserLoginVO userLoginVO=(UserLoginVO)principalCollection.getPrimaryPrincipal();
    */    /*info.addStringPermission("delete");*/
        /*info.addRole();*/
        /*获取已认证的用户数据*/
        UserShiro user = (UserShiro) principalCollection.getPrimaryPrincipal();
        List<Menu> menus = roleService.selUserPermission(user.getUserId());
        for (Menu menu : menus) {
            info.addStringPermission(menu.getUrl());
            for (MenuButton menuButton : menu.getMenBtn()) {
                info.addStringPermission(menuButton.getRequestUrl());
            }
        }
        // info.addRole();
        return info;
    }

    /**
     * 清楚缓存权限
     */
    public void clearCachedAuthorizationInfo() {
        this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
    }

}

2.6操作

2.6.1登录


    /**
     * 登录逻辑处理
     */
    @PostMapping("/login")
    @ResponseBody
    @Override
    public ResponseResult login(UserLoginDTO user, HttpServletRequest request) {

   /**
         * 使用Shiro编写认证操作
         */
        //1.获取Subject
        Subject subject = SecurityUtils.getSubject();
        //2.封装用户数据
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassWord());
        //3.执行登录方法
        subject.login(token);
        return new ResponseResult(CommonCode.SUCCESS);

    }

2.6.2获取session

 //获取session中的安全数据
        Subject subject = SecurityUtils.getSubject();
        //1.subject获取所有的安全数据集合
        PrincipalCollection principals = subject.getPrincipals();
        if (principals != null && !principals.isEmpty()) {
            //2.获取安全数据
            UserShiro result = (UserShiro) principals.getPrimaryPrincipal();
            this.userId = result.getUserId();
            this.userName = result.getUserName();
            this.realName = result.getRealName();
        }

3.分析Shiro源码

再了解一下shiro的基本架构
在这里插入图片描述
在这里插入图片描述
我们先进入口

   subject.login(token);
    public void login(AuthenticationToken token) throws AuthenticationException {
        clearRunAsIdentitiesInternal();
        Subject subject = securityManager.login(this, token);

        PrincipalCollection principals;

        String host = null;

        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject) subject;
            //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:
            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }

        if (principals == null || principals.isEmpty()) {
            String msg = "Principals returned from securityManager.login( token ) returned a null or " +
                    "empty value.  This value must be non null and populated with one or more elements.";
            throw new IllegalStateException(msg);
        }
        this.principals = principals;
        this.authenticated = true;
        if (token instanceof HostAuthenticationToken) {
            host = ((HostAuthenticationToken) token).getHost();
        }
        if (host != null) {
            this.host = host;
        }
        Session session = subject.getSession(false);
        if (session != null) {
            this.session = decorate(session);
        } else {
            this.session = null;
        }
    }

我们可以看出里面又调用了securityManager.login(this, token);
我们先回顾一下securityManager最主要是用来干嘛的?
主要用来认证与鉴权
再来了解一下SecurityManager下面子类
在这里插入图片描述

DefaultSecurityManager

  public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
            info = authenticate(token);
        } catch (AuthenticationException ae) {
            try {
                onFailedLogin(token, ae, subject);
            } catch (Exception e) {
                if (log.isInfoEnabled()) {
                    log.info("onFailedLogin method threw an " +
                            "exception.  Logging and propagating original AuthenticationException.", e);
                }
            }
            throw ae; //propagate
        }

        Subject loggedIn = createSubject(token, info, subject);

        onSuccessfulLogin(token, info, loggedIn);

        return loggedIn;
    }

    private Authenticator authenticator;

 
    public AuthenticatingSecurityManager() {
        super();
        this.authenticator = new ModularRealmAuthenticator();
    }
     public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }

里面的authenticate(token); 是用来认证的,我们可以看出this.authenticator = new ModularRealmAuthenticator();
在这里插入图片描述
AbstractAuthenticator

    public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {

        if (token == null) {
            throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
        }

        log.trace("Authentication attempt received for token [{}]", token);

        AuthenticationInfo info;
        try {
            info = doAuthenticate(token);
            if (info == null) {
                String msg = "No account information found for authentication token [" + token + "] by this " +
                        "Authenticator instance.  Please check that it is configured correctly.";
                throw new AuthenticationException(msg);
            }
        } catch (Throwable t) {
            AuthenticationException ae = null;
            if (t instanceof AuthenticationException) {
                ae = (AuthenticationException) t;
            }
            if (ae == null) {
                //Exception thrown was not an expected AuthenticationException.  Therefore it is probably a little more
                //severe or unexpected.  So, wrap in an AuthenticationException, log to warn, and propagate:
                String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " +
                        "error? (Typical or expected login exceptions should extend from AuthenticationException).";
                ae = new AuthenticationException(msg, t);
                if (log.isWarnEnabled())
                    log.warn(msg, t);
            }
            try {
                notifyFailure(token, ae);
            } catch (Throwable t2) {
                if (log.isWarnEnabled()) {
                    String msg = "Unable to send notification for failed authentication attempt - listener error?.  " +
                            "Please check your AuthenticationListener implementation(s).  Logging sending exception " +
                            "and propagating original AuthenticationException instead...";
                    log.warn(msg, t2);
                }
            }


            throw ae;
        }

        log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);

        notifySuccess(token, info);

        return info;
    }

doAuthenticate的实现在ModularRealmAuthenticator中
ModularRealmAuthenticator

    protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        assertRealmsConfigured();
        Collection<Realm> realms = getRealms();
        if (realms.size() == 1) {
            return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
        } else {
            return doMultiRealmAuthentication(realms, authenticationToken);
        }
    }

我们主要看一下一个Realm的时候doSingleRealmAuthentication

 protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        if (!realm.supports(token)) {
            String msg = "Realm [" + realm + "] does not support authentication token [" +
                    token + "].  Please ensure that the appropriate Realm implementation is " +
                    "configured correctly or that the realm accepts AuthenticationTokens of this type.";
            throw new UnsupportedTokenException(msg);
        }
        AuthenticationInfo info = realm.getAuthenticationInfo(token);
        if (info == null) {
            String msg = "Realm [" + realm + "] was unable to find account data for the " +
                    "submitted AuthenticationToken [" + token + "].";
            throw new UnknownAccountException(msg);
        }
        return info;
    }

调用了Realm的方法我们先了解一下Realm的子类
在这里插入图片描述

我们在看一下getAuthenticationInfo 获取身份验证信息
AuthenticatingRealm

    public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
                                     //获取缓存中的     
        AuthenticationInfo info = getCachedAuthenticationInfo(token);
        if (info == null) {
            //otherwise not cached, perform the lookup:
            info = doGetAuthenticationInfo(token);
            log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
            if (token != null && info != null) {
                cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
            log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
        }

        if (info != null) {
            assertCredentialsMatch(token, info);
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
    }

我们先不关心缓存的,看doGetAuthenticationInfo

public class MyShiroRealm extends AuthorizingRealm {

    @Override
    public void setName(String name) {
        super.setName("myShiroRealm");
    }




    /**
     * 执行认证逻辑
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       return null;
    }

    /**
     * 执行授权逻辑
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
      return null;
    }


    /**
     * 清楚缓存权限
     */
    public void clearCachedAuthorizationInfo() {
        this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
    }


}

现在认证看懂了,授权是AuthorizingRealm
现在应该了解 Shiro的配置文件了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值