手把手教你用SpringBoot整合Shiro(Shiro的前世今生)

手把手教你用SpringBoot整合Shiro

1、shiro的前世今生

1.1、shiro是什么?

在这里插入图片描述
  上面是Apache公司对Shiro的一个简单的介绍。简单总结,Shiro是Apache公司的一个产品,它是一款易于使用的Java安全框架(不仅可以使用在JavaEE上,也可以使用在JavaSE上哦)。它的主要功能有:登录认证、权限认证、加密、会话管理。

1.2、为什么要使用shiro?

在这里插入图片描述
  我觉得Shiro框架主要是解决Web应用的路径访问的限制问题。举个例子,在一个简单的Web项目,怎么防止未登录或者授权的用户,通过输入路径,来非法访问系统内部的资源呢?当然可以通过自定义filter结合session来实现。但是这方面,Shiro做得明显更好。既然有摩托车,为什么还要使用自行车呢?其次,Shiro还可以对会话进行管理,结合token,还可以解决session在分布式中储存问题,也可以避免伪站请求攻击的问题。所以学习Shiro还是有必要的。

2、Shiro的运作流程

在这里插入图片描述
  在Shiro当中,主要有以下几个模块:
  Subject:当前与程序进行交互的实体(可以是用户,第三方服务,计划任务等)的特定于安全性的“视图”。
  Security Manager:SecurityManager是Shiro体系结构的核心。它作为一个环境,用于协调其托管组件以确保它们能够顺利协同工作。它还管理Shiro对每个应用程序用户的视图,因此它知道如何对每个用户执行安全性操作。
  Authenticator:登录认证器。
  Authorizer:授权认证器。
  Session Manager:创建和管理用户Session生命周期的组件容器。
  Cache Manager:创建和管理Cache组件使用实例的生命周期的组件容器。
  Session DAO:可以通过Cache储存Session。
  Realm:Realm充当Shiro与应用程序的安全数据之间的“桥梁”或“连接器”。
  Cryptography:Shiro的crypto软件中包含易于使用和理解的加密算法,可用于对密码的加密解密。
在这里插入图片描述
  通过上面的图,我们可以清晰地看到Shiro的执行流程。首先用户通过获取Shiro中的Subject对象,使用Subject对象的login方法进行登录,在构建好的Security Manager环境中,我们通过Realm进行登录的校验和权限的分配。
  上面有提到Realm是Shiro与应用程序的安全数据之间的“桥梁”或“连接器”。为什么这样说?是因为我们通过Subject对象,获取登录的账号名,再通过账号名,查询数据库,找到对应的正确密码,然后把账号和密码存放在Realm里。当Subject对象执行login方法登录时,可以进行账号密码的校验。如果正确,则分配具体的权限。如果失败,则返回相应的异常。
  那配置Security Manager有什么用呢?全局就只有一个Security Manager,它作为作为Shiro运行的环境,可以绑定许多模块,例如Shiro的拦截器、Realm、SessionManager等。所以一定要配置它。

3、实战–SpringBoot如何整合Shiro

3.1、准备数据库

  登录账号表
在这里插入图片描述
  角色表
在这里插入图片描述
  权限表
在这里插入图片描述
  登陆账号、角色关系表
在这里插入图片描述
  角色、权限关系表
在这里插入图片描述

3.2、引入依赖

  pom文件引入依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
3.3、编写底层的CRUD方法

  LoginMapper.java

@Repository
public interface LoginMapper {

	//根据账号查找用户
    @Select("select * from login where username = #{name}")
    public Login findone(String name);

	//根据编号查找用户
    @Select("select * from login where id = #{id}")
    public Login findbyid(int id);

	//根据账号和密码查找用户
    @Select("select * from login where username = #{name} and password = #{pwd}")
    public Login findbynameandpwd(String name,String pwd);
}

  PermissionMapper.java

@Repository
public interface PermissionMapper {

	//使用左外连接,连接角色表和权限表
	//根据角色的编号,查找角色对应的权限集合
    @Select("SELECT permission.id,permission.`name`,permission.url " +
            "FROM role_permission LEFT JOIN permission ON role_permission.p_id=permission.id " +
            "WHERE role_permission.r_id= #{roleid}")
    public List<Permission> findPermissionListByRoleId(int roleid);
}

  RoleMapper.java

@Repository
public interface RoleMapper {

	//多表查询,使用左外连接,连接登录表、角色表、权限表
	//根据用户名称查找用户对应的角色以及角色对应的权限
    @Select("SELECT role.id,role.name,role.description " +
            "FROM user_role LEFT JOIN role on user_role.role_id=role.id WHERE user_role.user_id= #{userid}")
    @Results(
            value = {
                    @Result(id = true,property = "id",column = "id"),
                    @Result(property = "name",column = "name"),
                    @Result(property = "description",column = "description"),
                    @Result(property = "permissionList",column = "id",
                            many = @Many(select = "com.example.denglu.dao.PermissionMapper.findPermissionListByRoleId",fetchType = FetchType.DEFAULT))
            }
    )
    public List<Role> findRoleListByid(int userid);
}
3.4、设置CustomRealm
public class CustomRealm extends AuthorizingRealm {
    @Autowired
    private LoginMapper loginMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private PermissionMapper permissionMapper;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获得登录账号的账号名称
        String name=(String)principalCollection.getPrimaryPrincipal();
        //根据账号查找账号对应的角色和权限
        Login login=loginMapper.findone(name);
        List<Role> rolelist=new ArrayList<>();
        List<Permission> permissionList=new ArrayList<>();
        List<String> roles=new ArrayList<>();
        List<String> permissions=new ArrayList<>();
        //通过mybatis接口获取角色和权限
        rolelist=roleMapper.findRoleListByid(login.getId());
        for(Role role:rolelist){
            permissionList=permissionMapper.findPermissionListByRoleId((int)role.getId());
            for(Permission permission:permissionList){
                permissions.add(permission.getName());
            }
            roles.add(role.getName());
        }
        //将登录账号对应的角色和权限返回给customrealm
        SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(roles);
        simpleAuthorizationInfo.addStringPermissions(permissions);
        System.out.println("roles"+roles);
        System.out.println("permissions"+permissions);
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获得登录账号的账号名称
        String username=(String)authenticationToken.getPrincipal();
        Login login=loginMapper.findone(username);
        String password=login.getPassword();
        //判断密码是否为空
        if(password==null||"".equals(password)){
            return null;
        }
        //把正确的账号密码返回给customrealm
        SimpleAuthenticationInfo simpleAuthenticationInfo=new SimpleAuthenticationInfo(username,password,getName());
        return simpleAuthenticationInfo;
    }
}

3.5、设置ShiroConfig配置类
@Configuration
public class ShiroConfig {

    //设置shiro拦截器
    @Bean
    public ShiroFilterFactoryBean ShiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean();
        //绑定securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        //设置登录不成功时的返回路径
        shiroFilterFactoryBean.setLoginUrl("/pub/need_login");
        //设置登录成功时的返回路径
        shiroFilterFactoryBean.setSuccessUrl("/");
        //设置权限不足时的返回路径
        shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit");
        //构建一个map集合存放拦截器规则
        //必须使用LinkedHashMap,因为这是有序的,过滤规则是从上到下匹配。
        Map<String,String> filterChainDefintionMap=new LinkedHashMap<>();
        //用户退出
        filterChainDefintionMap.put("/logout","logout");
        //游客模式,表示这里的请求任何人都能访问
        filterChainDefintionMap.put("/pub/**","anon");
        //需要先登录,只有登录了才有资格访问这里的请求
        filterChainDefintionMap.put("/authc/**","authc");
        //需要符合角色,只有拥有这个角色身份的人才可以访问下列路径
        filterChainDefintionMap.put("/admin/**","roles[admin]");
        //需要符合权限,只有拥有这个权限的人可以访问下列路径
        filterChainDefintionMap.put("/video/update","perms[video_update]");
        //防止有路径遗漏
        filterChainDefintionMap.put("/**","authc");
        //绑定过滤器规则的map
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefintionMap);
        return shiroFilterFactoryBean;
    }

    //设置shiro的securitymanager环境
    @Bean
    public SecurityManager securityManager(){
        DefaultSecurityManager defaultSecurityManager=new DefaultWebSecurityManager();
        //绑定sessionmanager
        defaultSecurityManager.setSessionManager(sessionManager());
        //绑定realm
        defaultSecurityManager.setRealm(customRealm());
        return defaultSecurityManager;
    }

    //设置realm
    @Bean
    public CustomRealm customRealm(){
        CustomRealm customRealm=new CustomRealm();
        //绑定密码使用的加密方式
        //customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customRealm;
    }

    //设置sessionmanager
    @Bean
    public SessionManager sessionManager(){
        CustomSessionManager customSessionManager=new CustomSessionManager();
        //设置token过期时间为20s
        customSessionManager.setGlobalSessionTimeout(20000);
        return customSessionManager;
    }

    //设置密码使用的加密方式
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher credentialsMatcher=new HashedCredentialsMatcher();
        //设置使用的加密方式是md5
        credentialsMatcher.setHashAlgorithmName("md5");
        //加密2次
        credentialsMatcher.setHashIterations(2);
        return credentialsMatcher;
    }
}

3.6、设置CustomSessionManager
public class CustomSessionManager extends DefaultWebSessionManager {
    private static final String AUTHORIZATION="token";
    public CustomSessionManager(){
        super();
    }

    //重写获得sessionid的方法
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        //前端请求头必须传入token值
        String sessionid=WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        //如果请求头中有token值,则把token值设置为sessionid
        if(!StringUtils.isEmpty(sessionid)){
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,sessionid);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);
            return sessionid;
        }else {
            //如果没有,则按照原来的从cookie中读取sessionid
            return super.getSessionId(request,response);
        }
    }

}

3.7、编写对应的Controller

  PublicController.java

@RestController
@RequestMapping("/pub")
public class PublicController {
    @RequestMapping("/need_login")
    public JsonData needlogin(){
        return JsonData.buildSucess(-2,"请先登录");
    }

    @RequestMapping("/not_permit")
    public JsonData notpermit(){
        return JsonData.buildSucess(-3,"没有权限");
    }

    @RequestMapping("/index")
    public JsonData index(){
        List<String> videolist=new ArrayList<>();
        videolist.add("假面骑士");
        videolist.add("奥特曼");
        return JsonData.buildSucess(videolist);
    }

    @PostMapping("/login")
    public JsonData login(@RequestBody UserQuery userQuery, HttpServletRequest request, HttpServletResponse response){
        //获取subject操作主体
        Subject subject= SecurityUtils.getSubject();
        Map<String,Object> map=new HashMap<>();
        //获取登录输入的账号密码
        UsernamePasswordToken token=new UsernamePasswordToken(userQuery.getUsername(),userQuery.getPassword());
        try{
            //使用subject主体的login方法登录
            subject.login(token);
            map.put("msg","登录成功");
            map.put("session_id",request.getSession().getId());
            return JsonData.buildSucess(map);
        }catch (UnknownAccountException e){
            map.put("msg","用户名不存在");
            return JsonData.buildError(map);
        }catch (IncorrectCredentialsException e){
            map.put("msg","密码错误");
            return JsonData.buildError(map);
        }
    }
}

  AdminController.java

@RestController
@RequestMapping("/admin")
public class AdminController {

    @RequestMapping("/findorder")
    public JsonData findorder(){
        Map<String,Object> map=new HashMap<>();
        map.put("购买红米手机",1000);
        map.put("购买苹果手机",4000);
        return JsonData.buildSucess(map);
    }
}

  OrderController.java

@RestController
@RequestMapping("/authc")
public class OrderController {

    @RequestMapping("/video/playrecord")
    public JsonData playrecord(){
        Map<String,Object> map=new HashMap<>();
        map.put("火影忍者","第一集");
        map.put("海贼王","第四十八集");
        return JsonData.buildSucess(map);
    }
}

  VideoController.java

@RestController
@RequestMapping("/video")
public class VideoController {

    @RequestMapping("/update")
    public JsonData update(){
        return JsonData.buildSucess("更新成功");
    }

}
3.8、测试

  下面我们使用Postman对接口进行测试。
  用户直接登录,获取token值
在这里插入图片描述
  请求头不添加token值,直接访问需要登录的接口,访问失败
在这里插入图片描述
  请求头添加token值,直接访问需要登录的接口,访问成功
在这里插入图片描述
  当前登录的账号没有访问此接口的权限
在这里插入图片描述
  使用拥有权限的账号登录后,重新访问此接口
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值