浅析Shiro安全框架

Shiro功能介绍

浅析Shiro安全框架

下面介绍一下各功能点的意思:

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

Authorization: 授权,即权限验证,验证某个已认证的用户是否拥有某个权限;就是说判断这个用户是否能做事情,例如:细粒度的验证某个用户对某个资源是否具有某个权限。

Session Management: 回话管理,即用户登录后就是一次会话,在没有退出之前,他的所有信息都在会话中;会话可以是javase环境,也可以是web环境。

Cryptography: 加密,保护数据的安全性;例如:密码加密存储到数据库,而不是明文存储。

Web Support: web支持,可以非常容易的集成到web环境。

Caching: 缓存,比如用户登录后,用户信息、拥有的角色\权限不必每次都去查,都可以在缓存取,提供效率。

Concurrency: shiro支持多线程的并发验证,即,如在一个线程中开启另一个线程,可以把权限自动传播过去。

Testing: 提供测试支持;

Run As: 允许一个用户假装为另一个用户(如果他们允许)进行访问。

Remember Me: 记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

Shiro架构

①Shiro是Java的一个安全框架

可以完成的功能:认证登录(Authentication)、授权(Authorization)、加密(cryptography)、会话管理(session management)、集成web(web support)、缓存(caching)、记住密码(remember me)。

②如何使用Shiro完成工作

浅析Shiro安全框架
subject shiro对外API核心就是Subject 。Subject代表当前用,这个登录对象不一定是人,可以是网络爬虫,机器人等。与Subject所有交互都会委托给SecurityManager,Subject是门面,SecurityManager才是真正的执行者。

SecurityManager 安全管理器:
所有与安全有关的操作都会放在SecurityManager中交互;
管理所有的Subject;
它是Shiro的核心,负责与shiro的其他组件进行交互,相当于SpringMVC中的DispatcherServlet。

Realm Shiro从Realm中获取安全数据(角色、用户、权限),它要验证用户身份,需要从Realm中获取相应的用户进行比较确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作,可以把Realm看成一个DataSource。
在Realm中,数据先从缓存中获取,如果在缓存中取不到再到数据库中获取,然后将数据放入缓存中。

一. Subject对象:

Subject subject = SecurityUtils.getSubject();
通过获取subjecet对象后,Subject可以做很多事:

  1. subject.getSession();获取session对象,在获取到session后,就可以往session中存放session值了。
  2. subject.isAuthenticated();判断当前对象是否被认证,即是否登录。
  3. subject.login(token);登陆操作
  4. subject.hasRole('role');对当前用户判断是否有一些角色信息.
  5. subject.isPermitted('user:delete:zhangsan') ; 判断当前主体是否有权限.判断用户是否有某种行为.当前代表user能够对zhangsan进行delete操作.
  6. subject.logout() ; 执行登出操作.

在subject调用login()方法的时候 , 会出现一些异常,这时候需要我们进行手动捕获这些异常,这些异常可能有 用户名不存在 UnknownAccountException 密码不正确IncorrectCredentialsException 账户被锁定LockedAccountException以及其他的认证异常(AuthenticationException).

二. token对象():

Shiro给我们提供了一个UsernamePasswordToken对象,其中封装了有username password rememberMe host 属性,其中我们可以对token对象进行继承并自定义,如添加是否是手机登录,添加email,别名等.subject判断当前登录主体是否被认证,如果发现没有登录,则需要将当前subject对象的信息放入到token中,subject对token进行登录验证.

Shiro认证

浅析Shiro安全框架

身份认证流程
  • 1、首先调用 Subject.login(token) 进行登录,其会自动委托给 SecurityManager

  • 2、SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份验证;

  • 3、Authenticator 才是真正的身份验证者,Shiro API 中核心的身份 认证入口点,此处可以自定义插入自己的实现;

  • 4、Authenticator 可能会委托给相应的 AuthenticationStrategy 进 行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证;

  • 5、Authenticator 会把相应的 token 传入 Realm,从 Realm 获取 身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处 可以配置多个Realm,将按照相应的顺序及策略进行访问。

Shiro权限注解

  • @RequiresAuthentication:表示当前Subject已经通过login 进行了身份验证;即 Subject. isAuthenticated() 返回 true • @RequiresUser:表示当前 Subject 已经身份验证或者通过记 住我登录的。
  • @RequiresGuest:表示当前Subject没有身份验证或通过记住 我登录过,即是游客身份。
  • @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND):表示当前 Subject 需要角色 admin 和user
  • @RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR):表示当前 Subject 需要权限 user:a 或 user:b。

Shiro拦截器规则

浅析Shiro安全框架

浅析Shiro安全框架

ShiroConfig

注:本文config类一定要交给spring容器管理

①ShiroFilterFactoryBean
  • Shiro提供了与Web集成的支持,其通过一个ShiroFilter入口来拦截需要安全控制的URL,然后进行相应的控制

  • 实例:

@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager){
    ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
    // Shiro的核心安全接口,这个属性是必须的(securityManager之前直接交给spring容器管理,所以直接从中获取)
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    //身份认证失败,则跳转到登录页面的配置
    shiroFilterFactoryBean.setLoginUrl("/login");
    //登录成功默认跳转页面,不配置则跳转至"/"
    shiroFilterFactoryBean.setSuccessUrl("");
    //没有权限默认跳转的页面
    shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
    //自定义配置拦截器链,注意顺序(不同顺序有影响)--枚举名称字段请查看拦截规则
    //Shiro验证URL时,URL匹配成功便不再继续匹配查找(所以要注意配置文件中的URL顺序,尤其在使用通配符时)
    Map<String, String> map = new LinkedHashMap<>(16,1);
    map.put("/logout", "logout");
    map.put("/submit","anon");
    map.put("/login", "anon");
    map.put("/gifCode", "anon");
    map.put("/css/**", "anon");
    map.put("/fonts/**", "anon");
    map.put("/img/**", "anon");
    map.put("/js/**", "anon");
    map.put("/lib/**", "anon");
    //所有请求需要user认证
    map.put("/**", "user");
    shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
    return shiroFilterFactoryBean;
}
②DefaultWebSecurityManager
  • DefaultWebSecurityManager类主要定义了设置subjectDao,获取会话模式,设置会话模式,设置会话管理器,是否是http会话模式等操作,它继承了DefaultSecurityManager类,实现了WebSecurityManager接口

  • 实例:

    @Bean
    public DefaultWebSecurityManager getSecurityManager(){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        //setRealm(realm)->getUserRealm(HashedCredentialsMatcher matcher)方法详见下段代码
        securityManager.setRealm(getUserRealm(hashedCredentialsMatcher()));
        return securityManager;
    }
    
③Realm(此处不详细介绍后面有关于Realm的详细介绍)
  • Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户 进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/ 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource

  • 实例:

    @Bean
    //UserRealm是由自己定义继承的AuthorizingRealm(继承后重写的方法详见Realm详解)
    public UserRealm getUserRealm(HashedCredentialsMatcher matcher){
        UserRealm userRealm=new UserRealm();
        userRealm.setCredentialsMatcher(matcher);
        return userRealm;
    }
    
④HashedCredentialsMatcher
  • Shiro 提供了用于加密密码和验证密码服务的 CredentialsMatcher 接口,而 HashedCredentialsMatcher 正是 CredentialsMatcher 的一个实现类。写项目的话,总归会用到用户密码的非对称加密,目前主流的非对称加密方式是 MD5 ,以及在 MD5 上的加盐处理,而 HashedCredentialsMatcher 也允许我们指定自己的算法和盐

  • 实例:

    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //此处设置加密方式(SysConstant.ALGORITHNAME=MD5)
        credentialsMatcher.setHashAlgorithmName(SysConstant.ALGORITHNAME);
        //此处设置加密次数(SysConstant.HASHNUM=10)
        credentialsMatcher.setHashIterations(SysConstant.HASHNUM);
        return credentialsMatcher;
    }
    
⑤AuthorizationAttributeSourceAdvisor
  • 开启Shiro的注解支持,比如:@RequireRoles @RequireUsers

  • 实例:

    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
    

Realm详解

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

  • Realm继承关系

浅析Shiro安全框架

一般继承 AuthorizingRealm(授权)即可;其继承了 AuthenticatingRealm(即身份验证),而且也间接继承了 CachingRealm(带有缓存实现),重写里面doGetAuthenticationInfo 认证和doGetAuthorizationInfo授权方法。

  • doGetAuthorizationInfo授权方法

  • 授权过程:
    浅析Shiro安全框架

  • 实例:

    @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            /**
             * 1. 获取当前登录用户信息
             * 2. 获取当前用户关联的角色、权限等
             * 3. 将角色、权限封装到AuthorizationInfo对象中并返回
             */
            User user = (User) SecurityUtils.getSubject().getPrincipal();
            SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
            //以下注释代码主要是角色:权限控制
            /**
             *    //获取用户角色权限
             *         /**
             *          for(PositionVO position : positions){
             *             //职位
             *             roleStrings.add(position.getName());
             *             for(PermissionVO permission : position.getPermissions()){
             *                 //职位对应的权限
             *                 permissionStrings.add(position.getName() + ":" +  		   permission.getName());
             *             }
             *         }
             * 			authorizationInfo.setRoles(roleStrings);
             * 			authorizationInfo.setStringPermissions(permissionStrings);
             */
            //这里是主要对角色进行控制
            List<Role> userRole = roleService.findUserRole(user.getId());
            simpleAuthorizationInfo.addStringPermission(userRole.get(0).getRoleName());
            return simpleAuthorizationInfo;
        }
    
  • doGetAuthenticationInfo认证方法(doGetAuthenticationInfo实现一些业务,例如判断用户是否锁定等等)

  • 认证过程:

浅析Shiro安全框架

  • 实例:

     @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
         //AuthenticationToken代表登录时使用的票据。在shiro中只有一种实现就UsernamePasswordToken
           if (!(authenticationToken instanceof UsernamePasswordToken)) {
                throw new UnknownAccountException();
            }
            /**
             * 1. 从token中获取输入的用户名
             * 2. 通过username查询数据库得到用户对象
             * 3. 调用Authenticator进行密码校验
             */
            //获取用户名
            String username= (String) authenticationToken.getPrincipal();
            User byNameUser = userService.findByName(username);
            // Session session = SecurityUtils.getSubject().getSession(); 
            //session.setAttribute(SESSION_LOGIN_CUSTOMER_ID, user.getId());
            //可以用session将用户信息存在shiro的session当中,方便获取(存储在服务端)。JWT用户信息存于		   token(客户端)
            if (byNameUser==null){
                throw new UnknownAccountException(String.valueOf(UavStatesEnums.ACCOUNT_UNKNOWN.getMessage()));
            }
            /**
             * 交给Shiro进行密码的解密校验
             * 调用SecurityUtils.getSubject().getPrincipal() 遇到类型转换问题,报错 String !=> User
             * 请参考这篇文章:{@link https://blog.csdn.net/qq_35981283/article/details/78634575}
             */
            return new SimpleAuthenticationInfo(byNameUser,byNameUser.getPassword(), ByteSource.Util.bytes(byNameUser.getSalt()),getName());
        }
    

    登陆Controller

    • 在登陆层可以做很多操作,这里只是实现了简单的登陆功能

    • 实例:

    public ResponseCode tologin(@RequestBody User user){
        try {
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken token=new 		 UsernamePasswordToken(user.getUsername(),user.getPassword());
            subject.logout();
            subject.login(token);
            return ResponseCode.success(super.getToken());
        } catch (UnknownAccountException e){
            return ResponseCode.accountunkonw();
        }catch (IncorrectCredentialsException e){
            return ResponseCode.usererror();
        }
    }
    

    总结

    Shiro是一个功能很齐全的框架,使用起来也很容易,但是要想用好却有相当难度,希望如果用的不复杂就不要把Shiro代码结构写的太复杂,以上代码是Shiro简单实现登陆的整套代码,可以直接使用,本人才学疏浅,如有错误请指出。

总而言之:你不够优秀

浅析Shiro安全框架浅析Shiro安全框架 Java-扫地僧 发布了14 篇原创文章 · 获赞 4 · 访问量 422 私信 关注
上一篇:权限管理总结


下一篇:java-片段中的领域生命周期