springboot2.x shiro整合

前言

  这篇博客旨在完成Springboot2.x与shiro与JWT的整合,先从Springboot2.x整合shiro开始,再将JWT与Shiro整合,其中JWT部分用到了缓存Redis。

正文

介绍

shiro介绍网上已经很多了,我们这里引用一下其中一个博客 Shiro框架简介:

Apache Shiro是Java的一个安全框架。对比另一个安全框架Spring Sercurity,它更简单和灵活。Shiro可以帮助我们完成:认证、授权、加密、会话管理、Web集成、缓存等。
springboot2.x shiro整合_第1张图片

  1. Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
  2. Authorization:授权,即验证权限,验证某个已认证的用户是否拥有某个权限; 即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限。
  3. Session Management:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通的JavaSE环境的,也可以是Web环境的。
  4. Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
  5. Web Support:Web支持,可以非常容易的集成到Web环境中
  6. Caching:缓存,比如用户登录后,其用户信息,拥有的角色/权限不必每次去查,提高效率。
  7. Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  8. Testing:提供测试支持
  9. Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问
  10. Rember Me:记住我,这是非常常见的功能,即一次登录后,下次再来的话不用登录了。

原理

我们看完基本的介绍后,要首先了解一下shiro是怎么工作的:
springboot2.x shiro整合_第2张图片

  • Subject: 用户主体(把操作交给SecurityManager) 这个可以当成一个用户、一个爬虫…
  • SecurityManager:安全管理器(关联Realm)。进行认证和授权操作
  • Realm:Shiro连接数据的桥梁 ,这里就是获取数据库中密码、权限的地方。

流程(为了方便理解需要仔细读一下):

  1. 用户访问登录接口,传入username、password(或者其他的字段)后,为了能让shiro完成校验(账号密码认证、权限认证等),我们需要提供用户的真实密码,用户的权限信息,而这些信息需要我们重写Realm类中的两个方法来获取。
  2. 然后shiro先进行账号密码校验,正确后会返回seesionId信息(或者其他的,这个没有仔细看过),shiro还会把这些信息存放在内存中(session),用户下次访问带上这个sessioinId进行访问。
  3. 当用户携带sessionId进行访问有权限限制的资源时,shiro首先会根据我们提供的用户真实的密码验证sessionId是否正确(这里每次请求都要访问数据库似乎会大大提高服务器的开销,这里后面会提到),验证通过后会进一步根据我们提供(重写)的获取权限的方法进行权限验证,通过后才可正常访问。

所以我们只需要完成::

  1. 获取用户密码(我们需要把用户传过来的密码和数据库中的密码传给shiro,shiro帮我们完成了密码对比
  2. 获取用户的权限(权限认证shiro也会帮我们完成,我们只需要标注下访问某个接口需要什么权限就好了)

引入

首先我们需要引入pom文件:

<dependency> 
    <groupId>org.apache.shirogroupId> 
    <artifactId>shiro-springartifactId>    
    <version>1.4.0version>
dependency>

配置

我们引入后需要进行配置,也就是原理中提到的 Subject->SecurityManager->Realm,我们记得顺序。然后开始编写 ShiroConfig 类:

ShiroConfig


/**
 * @Author: goodtimp
 * @Date: 2019/10/7 20:46
 * @description :  shiro配置类
 */
@Configuration
public class ShiroConfig {

	/**
	 * 创建ShiroFilterFactoryBean, 在这里我们可以写入我们的拦截器,例如我们做权限验证的 SecurityManager
	 */
	@Bean
	public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
                return null; // 我们等下再来完成这里的内容
	}
	
	/**
	 * 创建DefaultWebSecurityManager 就是SecurityManager
	 */
	@Bean(name="securityManager")
	public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm")ShiroRealm shiroRealm){
		DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
		//关联realm
		securityManager.setRealm(shiroRealm);
		return securityManager;
	}
	
	/**
	 * 创建Realm 这里需要我们自己完成UserRealm的编写,即用户信息获取、权限获取
	 */
	@Bean(name="userRealm")
	public ShiroRealm getRealm(){
		return new ShiroRealm();
	}
}

Realm


下面我们来编写ShiroRealm类,这个类是用于用户信息获取、权限获取,至于 认证 shiro会帮我们完成。
注意: 密码验证时有一个坑点在下面会提到。

/**
 * @Author: goodtimp
 * @Date: 2019/10/7 20:46
 * @description : UserRealm类
 */

public class ShiroRealm extends AuthorizingRealm {

    // 没必要提供user表信息,这个根据大家需求自己定吧
    @Autowired
    private UserService userServiceImpl;


    /**
     * 获取用户密码
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String username = (String) usernamePasswordToken.getPrincipal(); // 获取username 当然也可能是phone信息,这要看你传过来的是什么
        String password = new String((char[]) (usernamePasswordToken.getCredentials()));  // 无法通过.toString转换 这个密码是我们传过来的密码,明文原密码,但是数据库中的密码是加密后的密码,所以下面还需要处理

        if (StringUtils.isEmpty(username)) {
            throw new AccountException("用户名不能为空");
        }
        if (StringUtils.isEmpty(password)) {
            throw new AccountException("密码不能为空");
        }
        User user = userServiceImpl.getUserByName(username); // 获取数据库中的密码 此处密码已经 加盐加密
      
        // user.getUserPassword() 应该是数据库对应的加密后的密码字段
        return new SimpleAuthenticationInfo(user, user.getUserPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
        // 四个参数的意思为: 数据库中取出的用户主体信息, 用户数据库中密码,加密盐,类名(这个是自己可以点进去看)
        // 记录:至于怎么验证 下面会说到
    }
    
    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        String userId = JwtUtil.getClaim(principalCollection.toString(), PAYLOAD_USER_TAG.getCode());

        // 从redis中获取角色对应的权限数据
        // ------- code --------

        // 将权限字符串添加到这里即可
        simpleAuthorizationInfo.addStringPermission("user:test");

        return simpleAuthorizationInfo;
    }
}

  我们上面将数据库中传进来的密码, 加盐加密验证不通过的问题,在跟踪源码之后发现 shiro 在加密时用的方法不太一样。如果你也碰到了这个问题 可以去看一下我的另外一篇博客:shiro 加盐加密问题

ShiroConfig 中的ShiroFilterFactory

上面在ShiroConfig类中我们做了标记,这个类是作为拦截器使用的,所有的请求都会被他拦截并验证是否需要进行安全控制。

/**
* Shiro内置过滤器,可以实现权限相关的拦截器
*    常用的过滤器:
*       anon: 无需认证(登录)可以访问
*       authc: 必须认证才可以访问
*       user: 如果使用rememberMe的功能可以直接访问
*       perms: 该资源必须得到资源权限才可以访问
*       role: 该资源必须得到角色权限才可以访问
*/
@Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);        //设置安全管理器
   
//        shiroFilterFactoryBean.setLoginUrl("/login");// 	如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
//        shiroFilterFactoryBean.setSuccessUrl("/index"); //  登录成功后要跳转的链接
//        shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");//未授权界面;
//        Map filterMap = new HashMap();
//        filterMap.put("/js/**","anon");
//        filterMap.put("/html/**","anon");
//        filterMap.put("/login/index","authc");
//        filterMap.put("/login/login","anon");
//        filterMap.put("/logout","logout");//配置退出 过滤器,其中的具体的退出代码Shiro已经实现
//        filterMap.put("/**","authc");//过滤链定义,从上向下顺序执行,一般将/**放在最为下边
//        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

这个类我不想多说什么…注释很多了

使用

登录接口

 @GetMapping("/login")
    @ResponseBody
    public ResponseModel login(String name, String password) {
        /**
         * 使用Shiro编写认证操作
         */
        //1.获取Subject
        Subject subject = SecurityUtils.getSubject();
        //2.封装用户数据
        UsernamePasswordToken token = new UsernamePasswordToken(name, password);
        //3.执行登录方法
        try {
            subject.login(token); // 如果不报错则成功
            
            return ResponseModel.success("token", token);
        } catch (AccountException e) {
            return ResponseModel.fail(e.getMessage());
        } catch (IncorrectCredentialsException e) {
            return ResponseModel.fail("Shiro密码错误");
        }
    }

感兴趣的可以自己去调试一下…

权限接口

那我们怎么控制用户访问某个接口必须具有的的权限呢,shiro为我们提供了一个非常方便的注解:@RequiresPermissions(“user:test”)

使用:

   @RequiresPermissions("user:test")
    public String hello() {
        return "hello world!";
    }

这样用户必须要有user:test权限才能访问这个接口,而权限配置则在文章中间的 ShiroRealm类中写了。

权限注解使用: @RequiresPermissions 解释

总结

  本来这篇文章想把shiro jwt整合放在一起写的,后来觉得东西太多所以还是分开吧。这里放上shiro整合jwt的博文链接,欢迎各位做前后端分离的小伙伴前来观看 springboot2.x shiro Jwt 整合。
  
  本文有什么不对的地方还请在评论中指出。

你可能感兴趣的:(springboot,java,shiro)