SpringBoot与Shiro整合

Java框架

浏览数:154

2019-8-23

AD:资源代下载服务

SpringBoot与Shiro整合

Shiro基本概念

  • 先从官网盗一点概念下来:
  • Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
  • Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码学和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

Apache Shiro 体系结构

Apache Shiro 体系结构

  • 1、Authentication 认证 —- 用户登录
  • 2、Authorization 授权 — 用户具有哪些权限
  • 3、Cryptography 安全数据加密
  • 4、Session Management 会话管理
  • 5、Web Integration web系统集成
  • 6、Interations 集成其它应用,spring、缓存框架

关于Shiro的Quickstart

官方Quickstart:
http://shiro.apache.org/10-minute-tutorial.html

Springboot 整合Shiro

  • 本次实验基于Springboot的一个基本登录案例来测试Shiro的安全认证、授权功能
    • 要使用Shiro我们需要ShiroFilterFactoryBean
    • 而ShiroFilterFactoryBean需要设置DefaultWebSecurityManager管理器
    • 而DefaultWebSecurityManager需要设置UserRealm
    • 于是整个shiro的配置就变成这样:
 package zstu.edu.springboot.shiro;

import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

/**
 * shiro的配置类
 * Created by CHEN on 2019/7/20.
 */
@Configuration
public class ShiroConfig {


    /**
     * 创建ShiroFilterFactoryBean
     *
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);


        //添加shiro的内置过滤器
        /**
         * Shiro内置过滤器,可以实现权限相关的拦截器
         *      常用过滤器:
         *          anon:无需认证(登录)可以访问
         *          authc:必须认证才可以访问
         *          user:如果使用rememberMe的功能可以直接访问
         *          perms:该资源必须得到资源权限才可以访问
         *          role:该资源必须得到角色权限才可以访问
         */
        Map<String,String> filterMap = new LinkedHashMap<>();
//        filterMap.put("/add","authc");
//        filterMap.put("/update","authc");

        //可以这么写
        //这里应当注意顺序,上下换一下就算设置了anon还是会被拦截,
        //而且目前的顺序会先调用/testThymeleaf,然后再调用login,login还是调用了,只是因为权限是anon所以才没有彖传
        //testThymeleaf..........
        //login................

        filterMap.put("/testThymeleaf","anon");
        filterMap.put("/login","anon");

        //授权过滤器
        //当前授权拦截后shiro会自动跳转到未授权页面
        filterMap.put("/add","perms[user:add]");


        filterMap.put("/*","authc");

        //修改默认登陆界面
        shiroFilterFactoryBean.setLoginUrl("/tologin");

        //设置未授权提示页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);


        return shiroFilterFactoryBean;

    }


    /**
     * @Qualifier拿到容器中的UserRealm对象
     * 创建DefaultWebSecurityManager
     */
    @Bean(name="securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联一个Realm
        securityManager.setRealm(userRealm);

        return securityManager;
    }


    /**
     * 创建Realm
     *
     * @return
     */
    @Bean(name = "userRealm")
    public UserRealm getRealm() {
        return new UserRealm();
    }
}

  • 这里需要注意的是我们需要把定义的组件加到容器中,这样我们才能找到这些组件,@Bean中的name属性也可以不用设置,直接用他的默认名即可
  • 而UserRealm也是一个自定义类,他需要继承AuthorizingRealm类
package zstu.edu.springboot.shiro;

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;

/**
 * 自定义Realm
 * Created by CHEN on 2019/7/20.
 */
public class UserRealm  extends AuthorizingRealm {

    /**
     * 执行授权逻辑
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权逻辑");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //应当对接数据库,这里为了方便,直接写死
        info.addStringPermission("user:add");

        return info;
    }


    /**
     * 执行认证的逻辑
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("执行认证逻辑");

        //后续应当从数据库中获得,先写死
        String name = "123";
        String password = "123";

        //判断用户名

        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        if (!token.getUsername().equals(name)){
            //用户名不存在
            return null;
            //shiro底层会抛出UnKnownAccountException
        }

        return new SimpleAuthenticationInfo("",password,"");
    }
}

分析

  • 首先,是认证Authentication,认证发生在用户登录,我们需要在controller中写出他的逻辑:
  • 步骤如下:
    • 1.获取Subject Subject subject = SecurityUtils.getSubject();
    • 2.封装用户数据 UsernamePasswordToken
    • 3.执行登录方法 subject.login(token);
@RequestMapping("/login")
    public String login(String name ,String password,Model model) {
        System.out.println("name="+name);
        /**
         * 使用Shiro编写认证操作
         */
        //1.获取Subject
        Subject subject = SecurityUtils.getSubject();

        //封装用户数据
        UsernamePasswordToken token = new UsernamePasswordToken(name,password);

        //执行登录方法
        try {
            subject.login(token);
            //登录成功

            //跳转到test.html
            return "redirect:/testThymeleaf";
        }catch (UnknownAccountException e ){
            model.addAttribute("msg","用户名不存在");
            System.out.println("UnknownAccountException....................");
            return "login";
        }catch (IncorrectCredentialsException e ){
            model.addAttribute("msg","密码错误");
            System.out.println("IncorrectCredentialsException.......................");
            return "login";
        }
    }

认证流程:

认证流程

授权流程:

授权流程

最后写一点认证拦截

    //这里应当注意顺序,上下换一下就算设置了anon还是会被拦截,
    //而且目前的顺序会先调用/testThymeleaf,然后再调用login,login还是调用了,只是因为权限是anon所以才没有彖传
    //testThymeleaf..........
    //login................

    filterMap.put("/testThymeleaf","anon");
    filterMap.put("/login","anon");

    filterMap.put("/*","authc");

    //所以个人认为避免全拦截的好,拦截的方统一目录下,在认证拦截的时候只需要拦截这个文件下所有文件的请求

作者:椰子奶糖