首页 > 其他分享 >2流高手速成记(之五):Springboot整合Shiro实现安全管理

2流高手速成记(之五):Springboot整合Shiro实现安全管理

时间:2022-10-29 00:45:28浏览次数:84  
标签:Springboot hellospringboot org user 之五 import com example Shiro

废话不多说,咱们直接接上回

上一篇我们讲了如何使用Springboot框架整合Nosql,并于文章最后部分引入了服务端Session的概念

而早在上上一篇中,我们则已经讲到了如何使用Springboot框架整合Mybatis/MybatisPlus实现业务数据的持久化(写入数据库)

本篇我们把关注点放在一个于这两部分有共同交集的内容——安全管理,并且引入我们今天的主角——Shiro框架

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

—— 来自百度百科

Shiro框架包含三个核心组件:

Subject —— 泛指当前与Shiro交互中的实体,可以是用户或者某后台进程

SecurityManager —— Shiro的核心组件,对内管理各种组件实例,对外提供各种安全服务

Realm —— Shiro与安全数据之间的桥接器

Shiro框架还包含有其他诸多概念,为降低大家的心智负担,这些我们暂且不谈,文末会给大家推荐延展阅读的相关文章

还是老规矩直接上干货,以完整的实例让大家对【如何基于Shiro实现权限的细粒度控制】有一个整体上的认知

 

 

 

不知道大家会不会觉得项目结构突然变复杂?别担心,接下来我会给大家逐一拆解

1. 创建数据表

首先是角色表——role

 

 

 

 

 然后是用户表——user

 

 

 

 最后是权限表——permission

 

 2. 创建三个对应的Mapper

package com.example.hellospringboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.hellospringboot.model.Role;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface RoleMapper extends BaseMapper<Role> {
}
package com.example.hellospringboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.hellospringboot.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
}
package com.example.hellospringboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.hellospringboot.model.Permission;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

@Mapper
@Repository
public interface PermissionMapper extends BaseMapper<Permission> {
}

这里我们用到了上上一节讲到的内容

这里的Mapper会辅助于后续的安全数据读取

3. 接下来是Service及其实现类

package com.example.hellospringboot.service;

import com.example.hellospringboot.model.Role;

public interface RoleService {
    Role findRoleById(int id);
}
package com.example.hellospringboot.service.impl;

import com.example.hellospringboot.mapper.RoleMapper;
import com.example.hellospringboot.model.Role;
import com.example.hellospringboot.service.RoleService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RoleServiceImpl implements RoleService {

    @Autowired
    RoleMapper mapper;

    public Role findRoleById(int id){
        Role role = mapper.selectById(id);
        return role;
    }
}
package com.example.hellospringboot.service;

import com.example.hellospringboot.model.User;

public interface UserService {
    boolean checkUserByUsernameAndPassword(String userName, String passWord);
    User findUserByUserName(String userName);
}
package com.example.hellospringboot.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.hellospringboot.mapper.UserMapper;
import com.example.hellospringboot.model.User;
import com.example.hellospringboot.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserMapper mapper;

    public boolean checkUserByUsernameAndPassword(String userName, String passWord){
        QueryWrapper<User> wrapper = new QueryWrapper<User>();
        wrapper = wrapper.eq("user_name", userName).eq("pass_word",passWord);
        List<User> userList = mapper.selectList(wrapper);
        return userList.size() > 0;
    }

    public User findUserByUserName(String userName){
        QueryWrapper<User> wrapper = new QueryWrapper<User>();
        wrapper = wrapper.eq("user_name", userName);
        User user = mapper.selectOne(wrapper);
        return user;
    }

}
package com.example.hellospringboot.service;

import com.example.hellospringboot.model.Permission;

import java.util.List;

public interface PermissionService {
    List<Permission> findPermissionsByRoleId(int roleId);
}
package com.example.hellospringboot.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.hellospringboot.mapper.PermissionMapper;
import com.example.hellospringboot.model.Permission;
import com.example.hellospringboot.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PermissionServiceImpl implements PermissionService {

    @Autowired
    PermissionMapper mapper;

    public List<Permission> findPermissionsByRoleId(int roleId){
        QueryWrapper<Permission> wrapper = new QueryWrapper<>();
        wrapper = wrapper.eq("role_id", roleId);
        List<Permission> list = mapper.selectList(wrapper);
        return list;
    }

}

ok,我们已经准备好了所有的安全数据,及对应的读取方法

到这里,我们就算是做好了所有的准备工作

接下来看我们如何通过Shiro框架来运用这些已经装配好的枪炮子弹

4. 引入Shiro框架相关依赖(pom.xml)

        <!-- 引入shiro框架依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.10.0</version>
        </dependency>

这次pom.xml终于不是第一步了,哈哈哈。。。

5. 创建Realm嫁接Shiro框架及安全数据(realm/MyAuthorizingRealm)

package com.example.hellospringboot.realm;

import com.example.hellospringboot.model.Permission;
import com.example.hellospringboot.model.Role;
import com.example.hellospringboot.model.User;
import com.example.hellospringboot.service.PermissionService;
import com.example.hellospringboot.service.RoleService;
import com.example.hellospringboot.service.UserService;
import org.apache.shiro.authc.*;
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.springframework.beans.factory.annotation.Autowired;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class MyAuthorizingRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    @Autowired
    RoleService roleService;

    @Autowired
    PermissionService permissionService;

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        String userName = token.getUsername();
        String passWord = String.valueOf(token.getPassword());
        if (!userService.checkUserByUsernameAndPassword(userName, passWord)) {//判断用户账号是否正确
            throw new UnknownAccountException("用户名或密码错误!");
        }
        return new SimpleAuthenticationInfo(userName, passWord, getName());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        String userName = principalCollection.getPrimaryPrincipal().toString();
        User user = userService.findUserByUserName(userName);
        if (user == null) {
            throw new UnknownAccountException("用户名或密码错误!");
        }
        List<Integer> rolesList = user.rolesList();
        Set<String> roles = new HashSet<>();
        Set<String> permissions = new HashSet<>();
        for (Integer roleId : rolesList) {
            Role role = roleService.findRoleById(roleId);
            roles.add(role.getName());
            List<Permission> permissionList = permissionService.findPermissionsByRoleId(roleId);
            for (Permission permission : permissionList) {
                permissions.add(permission.getName());
            }
        }
        info.setRoles(roles);
        info.setStringPermissions(permissions);
        return info;
    }
}

Realm的创建对于整个Shiro安全验证体系搭建而言是至关重要的一步!

其中两个抽象方法

doGetAuthenticationInfo —— 用于校验用户名及密码的合法性

doGetAuthorizationInfo —— 用于赋予实体对应的角色及交互权限

6. 测试用Controller创建

package com.example.hellospringboot.controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/user")
@RestController
public class UserController {

    @PostMapping("/login")
    public String login(String user, String pass) {
        UsernamePasswordToken token = new UsernamePasswordToken(user, pass);
        Subject subject = SecurityUtils.getSubject();
        if(!subject.isAuthenticated()) {
            try {
                subject.login(token);
            } catch (AuthenticationException e) {
                return e.getMessage();
            }
        }
        return "ok";
    }

    @PostMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        if(subject.isAuthenticated()) {
            try {
                subject.logout();
            } catch (AuthenticationException e) {
                return e.getMessage();
            }
        }
        return "ok";
    }

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

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

}

内容很简单:

login——登录方法

logout——登出方法

admin、user——两个测试方法,用于测试不同角色对于不同方法可访问的细粒度控制

7. ShiroConfig配置类创建,实现用户访问权限的细粒度控制

package com.example.hellospringboot.configure;

import com.example.hellospringboot.realm.MyAuthorizingRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    @Bean
    public SecurityManager securityManager(Realm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    @Bean
    public MyAuthorizingRealm getRealm() {
        MyAuthorizingRealm realm = new MyAuthorizingRealm();
        return realm;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        Map<String, String> filterChainMap = new LinkedHashMap<String, String>();
        filterChainMap.put("/user/login", "anon");
        filterChainMap.put("/user/logout", "anon");
        filterChainMap.put("/user/admin", "authc,roles[admin],perms[admin:read]");
        filterChainMap.put("/user/user", "authc,roles[user],perms[user:read]");
        shiroFilter.setFilterChainDefinitionMap(filterChainMap);
        return shiroFilter;
    }
}

 

securityManager 和 getRealm 显示指定了Shiro两大组件的实例声明

shiroFilterFactoryBean 则是实现角色访问权限控制的重要方法

        filterChainMap.put("/user/login", "anon"); // 代表login方法可以匿名访问

        filterChainMap.put("/user/logout", "anon"); // 代表logout方法可以匿名访问

        filterChainMap.put("/user/admin", "authc,roles[admin],perms[admin:read]"); // 代表admin方法需要用户满足admin角色,同时具备admin:read权限

        filterChainMap.put("/user/user", "authc,roles[user],perms[user:read]"); // 代表user方法需要用户满足user角色,同时具备user:read权限

至此,整个接入流程便结束了

我们再次结合最开始我们配置的数据来对业务逻辑进行分析

用户 admin,同时具备admin、user两种角色

用户 juste,仅具备user一种角色

角色 admin,同时具备admin:write、admin:read两种权限

角色 user,同时具备user:write、user:read两种权限

因此

用户 admin,同时具备admin:write、admin:read、user:write、user:read 四种操作权限

用户 juste,同时具备user:write、user:read两种操作权限

大家理清楚这其中的关系了吗?^ ^

8. 执行Postman验证结果

 

 我们在执行login之前,admin方法无权访问

 

 

 

 

 

 登录admin之后,同时具备admin和user方法的访问权限

 

logout登出,然后login登录普通用户juste

会发现依然具备user方法的访问权限,但是失去了admin方法的访问权限

到此,验证我们基于Shiro框架的细粒度权限控制已经实现

除了Shiro框架,我们还有另一个选择,那就是同样可以通过集成Spring Security框架来达成相同的目的

关于更多Shiro框架的内容,及其和Spring Security之间的异同,大家感兴趣可以参考这篇文章:

Shiro最全基础教程_思月行云的博客-CSDN博客

对于Spring Security框架,我们暂且留个悬念,以后会专门再给大家讲解这部分内容

下一节,我们将把关注点投向微服务领域,SpringCloudAlibaba将会是接下来几个章节的重头戏,敬请期待~

 

 

 

MyAuthorizingRealm

标签:Springboot,hellospringboot,org,user,之五,import,com,example,Shiro
From: https://www.cnblogs.com/itfantasy/p/16837917.html

相关文章

  • Springboot错误:Unable to start embedded Tomcat server
    报错内容/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/bin/java-XX:TieredStopAtLevel=1-noverify-Dspring.output.ansi.enabled=always-javaagent:/......
  • shiro不出网的打法
    shiro不出网的打法Author:Mi2ac1e0x01环境搭建安装NGINXaptupdateaptinstallnginx修改配置文件:如果你找不到自己的nginx配置文件,需要使用/usr/sbin/nginx-......
  • SpringBoot Starter缘起
    SpringBoot通过SpringBootStarter零配置自动加载第三方模块,只需要引入模块的jar包不需要任何配置就可以启用模块,遵循约定大于配置的思想。那么如何编写一个SpringBootSt......
  • springboot启动默认连接数据库问题
    这里是给新手介绍的springboot默认启动是连接数据库的经常很多人蒙蔽,//无数据源启动@SpringBootApplication(exclude={DataSourceAutoConfiguration.class,DataSo......
  • springboot像springnvc那样访问视图
    1.SpringBoot访问静态资源的位置(优先级按以下顺序)classpath默认就是resources,所以classpath:/static/就是resources/static/classpath:/META‐INF/resources/cl......
  • 【SpringBoot】引入mybatis及连接Mysql数据库
    创建一个SpringBoot项目其他不赘叙了,引入MyBaties、MySql依赖编辑 创建mysql表CREATETABLEsp_users(`id`INTPRIMARYKEY,`username`VARCHAR(30),`age`INT);刚......
  • SpringBoot 没有为 Service 创建代理类的一个原因
    最近一个SpringBoot 项目,最开始需要在一个Service 中使用AopContext.currentProxy(),谁知返回一个空,导致空指针异常再跟踪发现,原来spring 并没有为这个Service 创......
  • SpringBoot 为登录设置拦截器
    在学习一个项目的过程中有这样一个需求就是用户未登录是不可以访问其他主页的所以我们有两种做法:使用拦截器或者过滤器来实现这样的功能,这里就通过过滤器来记录下学习心......
  • springboot-slf4j日志输出级别简要配置
    springboot-slf4j日志输出级别简要配置1.前言springboot使用ymal、yml配置,更好的将相关的配置节点聚合在一起,方便查看和增改配置项。配置可以写在application.yml、spring......
  • java springboot 2.x 环境搭建
    环境:javaversion1.8ApacheMaven3.2.2 ide:ideaspringboot:2.1.3.RELEASE创建步骤:1file=>new=>mudule...2选择SpringInitializr:modulesdk选择1.7,其他默认......