springboot-mybatisplus集成Shiro

发布于:2022-12-17 ⋅ 阅读:(540) ⋅ 点赞:(0)

Shiro 概述

Apache Shiro 是一款 Java 安全框架,不依赖任何容器,可以运行在 Java SE 和 Java EE 项目中,它的主要作用是用来做身份认证、授权、会话管理和加密等操作。

什么意思?大白话就是判断用户是否登录、是否拥有某些操作的权限等。

其实不用 Shiro,我们使用原生 Java API 就可以完成安全管理,很简单,使用过滤器去拦截用户的各种请求,然后判断是否登录、是否拥有某些权限即可。

我们完全可以完成这些操作,但是对于一个大型的系统,分散去管理编写这些过滤器的逻辑会比较麻烦,不成体系,所以需要使用结构化、工程化、系统化的解决方案。

任何一个业务逻辑,一旦上升到企业级的体量,就必须考虑使用系统化的解决方案,也就是框架,否则后期的开发成本是相当巨大的,Shiro 就是来解决安全管理的系统化框架。

Shiro 核心组件

1、UsernamePasswordToken,Shiro 用来封装用户登录信息,使用用户的登录信息创建令牌 Token,登录的过程即 Shiro 验证令牌是否具有合法身份以及相关权限。

2、 SecurityManager,Shiro 的核心部分,负责安全认证与授权。

3、Subject,Shiro 的一个抽象概念,包含了用户信息。

4、Realm,开发者自定义的模块,根据项目的需求,验证和授权的逻辑在 Realm 中实现。

5、AuthenticationInfo,用户的角色信息集合,认证时使用。

6、AuthorizationInfo,角色的权限信息集合,授权时使用。

7、DefaultWebSecurityManager,安全管理器,开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效。

8、ShiroFilterFactoryBean,过滤器工厂,Shiro 的基本运行机制是开发者定制规则,Shiro 去执行,具体的执行操作就是由 ShiroFilterFactoryBean 创建一个个 Filter 对象来完成。

Shiro 的运行机制如下图所示:

以上都是cv来的

首先在pom文件导入依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
    <version>1.4.1</version>
</dependency> 

然后改配置
@Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator(){
    DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();

    /**
     * setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
     * 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,会导致该方法无法映射请求,导致返回404。
     * 加入这项配置能解决这个bug
     */
    defaultAdvisorAutoProxyCreator.setUsePrefix(true);
    return defaultAdvisorAutoProxyCreator;
}

@Bean
public Realm shiroRealm(){
    return new ShiroRealm();
}

@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){
    DefaultShiroFilterChainDefinition sfcd = new DefaultShiroFilterChainDefinition();
    //定义 某个路径 使用 哪个过滤器来处理
    //过滤器列表参考:org.apache.shiro.web.filter.mgt.DefaultFilter
    //警告:过滤器定义有顺序
    sfcd.addPathDefinition("/","anon");
    sfcd.addPathDefinition("/login","anon");
    sfcd.addPathDefinition("/login.html","anon");
    sfcd.addPathDefinition("/css/**","anon");
    sfcd.addPathDefinition("/js/**","anon");
    sfcd.addPathDefinition("/images/**","anon");
    sfcd.addPathDefinition("/fonts/**","anon");
    sfcd.addPathDefinition("/html/**","anon");
    //logout 登出
    sfcd.addPathDefinition("/logout","logout");
    //其他则需要认证
    sfcd.addPathDefinition("/**","user");

    return sfcd;
}

认证过滤器: anon:无需认证即可访问,游客身份。 authc:必须认证(登录)才能访问。 authcBasic:需要通过 httpBasic 认证。 user:不一定已通过认证,只要是曾经被 Shiro 记住过登录状态的用户就可以正常发起请求,比如 rememberMe。 授权过滤器: perms:必须拥有对某个资源的访问权限(授权)才能访问。 role:必须拥有某个角色权限才能访问。 port:请求的端口必须为指定值才可以访问。 rest:请求必须是 RESTful,method 为 post、get、delete、put。 ssl:必须是安全的 URL 请求,协议为 HTTPS。

再写他的realm

@Slf4j
public class ShiroRealm extends AuthorizingRealm {
    @Resource
    UserMapper userMapper;

    @Resource
    RoleMapper roleMapper;

    @Resource
    PermMapper permMapper;

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        User user =(User) principalCollection.getPrimaryPrincipal();

        //查找该用户所有的角色
        List<Role> roleList = roleMapper.selectByUserId(user.getUserId());
        Set<String> strRoles = roleList.stream()
                .map(r -> r.getRoleName())
                .collect(Collectors.toSet());
        List<String> permissions = new ArrayList<>();
        if (roleList.size()>0){
            //查找该用户的所有权限
            permissions = permMapper.selectPermInRoleIds(roleList);
        }
        SimpleAuthorizationInfo authzInfo = new SimpleAuthorizationInfo();
        authzInfo.setStringPermissions(new HashSet<>(permissions));
        authzInfo.setRoles(strRoles);
        return authzInfo;
    }

    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("执行ShiroRealm#doGetAuthenticationInfo {}",authenticationToken);
        Object username = authenticationToken.getPrincipal();
        QueryWrapper<User> qw = new QueryWrapper<>();
        qw.eq("username",username);
        User user = userMapper.selectOne(qw);
        return new SimpleAuthenticationInfo(user,user.getPassword(),getClass().getName());
    }
}

客户端传来的 username 和 password 会自动封装到 token,先根据 username 进行查询

我们创建两个接口需要登录才能访问,如果没登陆的话自动跳转

@RestController
@Slf4j
public class LoginController {

    @PostMapping("/login")
    public String login(String username,String password){

        Subject subject = SecurityUtils.getSubject();
        subject.login(new UsernamePasswordToken(username,password));

        return "success";
    }

    @GetMapping("/pay")
    public String pay(){
        return "success";
    }


}

跳转到这个页面,输入用户名密码

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>html</title>
</head>
<body>
<a href="/logout">登出</a>
<form id="form">
  <input type="text" name="username" id="">
  <input type="password" name="password">
  <input type="button" id="loginBtn" value="提交">
</form>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>

<script>
  $(function () {
    $("#loginBtn").click(function () {

      $.ajax({
        url:"/login",
        type:"post",
        dataType:"json",
        data:$("#form").serialize(),
        success:function (resp) {
          console.log(resp)
        },
        error:function (err) {
          console.error(err);
        }
      })

    })
  })
</script>
</body>
</html>

 配置一下自定义异常

@Slf4j
@RestControllerAdvice //异常的注解
public class GlobalExceptionHandler {

    @ExceptionHandler(AuthorizationException.class)//业务异常
    public Result handleAuthzException(AuthorizationException e){

        log.info("出现授权异常:{}",e.getMessage());
        return Result.error(403,"抱歉,您没有该权限");
    }

    @ExceptionHandler(AuthenticationException.class)//业务异常
    public Result handleAuthenticationException(AuthenticationException e){
        log.info("出现认证异常:{}",e.getMessage());
        String message = null;
        if (e instanceof AccountException){
            message = "账户异常";
        }else if (e instanceof CredentialsException){
            message = "密码不正确,请重新输入";
        }else{
            message="您尚未登陆";
        }

        return  Result.error(403,message);
    }

}

登录失败会报错密码不正确

 

成功后会提示success

 

 此时我们再访问pay接口就会成功


网站公告

今日签到

点亮在社区的每一天
去签到