Shiro学习

724 阅读4分钟

序言

之前没有接触过安全框架,只是知道一些RBAC权限设计的思路,然后自己用过滤器来实现的项目权限认证,功能也七七八八能够实现,但是使用框架更香呀对吧,更少的配置,更少的代码,来吧!

一. shiro的核心架构

shiro.png

其中最重要的是三个部分:

SecurityManager

SecurityManager的设计运用了设计模式的外观模式,对框架内部的模块进行了一个封装,提供一个统一的接口来供外部进行调用。它是shiro框架的核心,负责对内的各个组件进行调度。

Subject

subject代表的是当前操作的用户主体。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。

Reaml

Realm是subject认证和授权道路上的关键,必须经过Realm,用户每次访问接口都会经过Realm的认证授权方法。

shiro如何实现认证

shiro中有一个全局的安全工具类SecurityUtils,可以通过这个安全工具类来获取到我们的认证主体Subject

这时,魔法就慢慢开始了。 用户在前端页面中传入对应的用户名和密码,我们在后端接受到用户名和密码之后,将用户名和密码封装成一个UsernamePasswordToken,认证主体Subject再拿着这个token去登录就可以了(subject.login(token))。

登录验证会进入到SecurityManager中,然后再进入到Realm中调用它的认证方法,对用户进行验证。 验证成功后就会返回一个被认证成功的对象。

当然在实际业务中,我们往往还是会自定义登录的业务逻辑。 我们通过实现AuthenticationToken这个接口,自定义我们的认证Token。然后再自定义一个ShiroFilter来对访问的请求进行拦截,转化token为我们自定义的token用于Realm认证即可。

shiro如何实现授权

要实现授权,核心是我们在之前就要通过RBAC权限设计来提前设计好我们的权限和用户,角色之间的关系。 在用户认证成功以后,通过用户的用户名或者是id来查询他所拥有的权限,然后对这些让这些资源对用户放行即可。

shiro整合springboot实战演练

1. 导入shiro的依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.7.0</version>
</dependency>
//shiro与thymeleaf进行整合
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
//shiro使用encache进行缓存
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.5.3</version>
</dependency>

2. 自定义Realm实现认证授权逻辑

    1. 用户登录的/login接口不设置拦截,通过自定义方式进行登录,用户登录成功以后将token存放在数据库中,并携带返回前端。
    1. 后续用户访问的接口都需要进行授权和认证。所以都将会通过Realm中的认证和授权两个方法。
    1. Realm中通过验证token来判断是否能够通过验证,通过验证以后创建一个认证对象
    1. 在Realm的授权方法中:通过认证对象来从数据库中查询其所拥有的权限。然后对接口上进行判断。
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xinyou.shiro.entity.TUser;
import com.xinyou.shiro.service.TUserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class MyRealm extends AuthorizingRealm {
  @Autowired
  private TUserService tUserService;

  /***********************************
   * 授权 ********************************************
   * @param principalCollection
   * @return
   */
  @Override
  protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();
    SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
    //从数据库中查询该用户的权限
    if("admin".equals(primaryPrincipal)){
       //用户包含的角色
      simpleAuthorizationInfo.addRole("admin");
      //用户包含的某个权限
      simpleAuthorizationInfo.addStringPermission("user:*:001");
    }
    return simpleAuthorizationInfo;
  }

  /*************************************
   * 认证********************************
   * @param authenticationToken
   * @return
   * @throws AuthenticationException
   */
  @Override
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    String principal = (String) authenticationToken.getPrincipal();
    //根据用户名从数据库中进行查询
    QueryWrapper<TUser> wrapper = new QueryWrapper<>();
    wrapper.eq("username",principal);
    TUser tUser = tUserService.getOne(wrapper);
    if(tUser!=null){
      //用户存在
      System.out.println("用户存在........");
      return new SimpleAuthenticationInfo(principal,tUser.getPassword(), ByteSource.Util.bytes(tUser.getSalt()),this.getName());

    }
    System.out.println("用户不存在..");
    return null;
  }
}

3.配置shiro的配置类

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import java.util.*;

import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 整合shiro的配置类
 */
@Configuration
public class ShiroConfig {

  @Bean(name = "shiroDialect")
  public ShiroDialect shiroDialect(){
    ShiroDialect shiroDialect = new ShiroDialect();
    return shiroDialect;
  }


  /** *************************************
   * shiroFilter,负责拦截所有的请求!***********************************************
   * @param defaultWebSecurityManager
   * @return
   */
  @Bean
  public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
      ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
      // 给filter设置安全管理器
      factoryBean.setSecurityManager(defaultWebSecurityManager);
       //配置系统受限资源和公共资源
      Map<String,String> map = new HashMap<>();
     // anon 代表公共资源,无需认证和授权   authc需要认证和授权 这是shiro不同的拦截器
      map.put("/register","anon");
      map.put("/user/**","anon");
      map.put("/**","authc");  //所有的资源都需要认证和授权

     //默认认证界面路径(未认证的用户都会跳转到这个默认登录界面)
      factoryBean.setLoginUrl("/");
      factoryBean.setFilterChainDefinitionMap(map);  
      return factoryBean;
  }

  /**
   * 配置安全管理器
   * @param realm  我们自定义的realm
   * @return
   */
  @Bean
  public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
    DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
    defaultWebSecurityManager.setRealm(realm);
    return defaultWebSecurityManager;
  }

  /**
   * 自定义realm
   * @return
   */
  @Bean("realm")
  public Realm getRealm(){
    MyRealm myRealm = new MyRealm();
    //修改凭证匹配器,不设置的话默认使用的是简单的匹配认证
    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    hashedCredentialsMatcher.setHashAlgorithmName("MD5");
    hashedCredentialsMatcher.setHashIterations(1024);
    myRealm.setCredentialsMatcher(hashedCredentialsMatcher);

    //设置开启缓存
    myRealm.setCacheManager(new EhCacheManager());
    myRealm.setCachingEnabled(true); //开启全局缓存,开启以后就不会每次都从数据库中查询用户的权限了!
    myRealm.setAuthenticationCachingEnabled(true);
    myRealm.setAuthenticationCacheName("authenticationCache");
    myRealm.setAuthorizationCachingEnabled(true);
    myRealm.setAuthorizationCacheName("authorizationCache");

    return myRealm;
  }

4. 登录,注册,退出接口实现

 @GetMapping("logout")
 public String logout(){
   Subject subject = SecurityUtils.getSubject();
   subject.logout();
   return "redirect:/login";
 }

@PostMapping("/login")
public String login(String username,String password){
  //使用shiro进行验证
   Subject subject = SecurityUtils.getSubject();

   try {
     subject.login(new UsernamePasswordToken(username, password));
     return "index";
   } catch (UnknownAccountException e) {
     e.printStackTrace();
     System.out.println("用户名错误!");
   }catch (IncorrectCredentialsException e){
     e.printStackTrace();
     System.out.println("密码错误");
   }
   return "login";
 }
 // 注册
 @PostMapping("register")
 public String register(TUser user){

   try {
     tUserService.register(user);
     return "login";
   }catch (Exception e){
     e.printStackTrace();
   }
   return "regist";
  
 }

这是注册时业务层的register方法

@Override
public void register(TUser tuser) {
  String salt = SaltUtils.getSalt(8);  //用工具类获取盐值
  tuser.setSalt(salt);
  Md5Hash md5Hash = new Md5Hash(tuser.getPassword(),salt,1024); //对用户密码进行加密!
  tuser.setPassword(md5Hash.toHex());
  baseMapper.insert(tuser);   //保存用户信息
}

thymeleaf继承shiro

导入jar包后,在配置类中需要配置一个方言的插件

  @Bean(name = "shiroDialect")
  public ShiroDialect shiroDialect(){
    ShiroDialect shiroDialect = new ShiroDialect();
    return shiroDialect;
  }

在html页面头上加入约束

<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

这样就可以在thymeleaf中使用shiro的标签了!

结语

使用框架以后真的大大减少了我们的代码量了!我们设置权限模块的唯一麻烦处就是前期要把数据库给设计好,后面编写代码结合框架来使用就十分简单了。

以上就是本期全部内容了!