SpringBoot-Shiro初步

149 阅读2分钟

Shiro 简介

  • shiro中文文档
  • Shiro的架构主要有三个顶级概念:Subject, SecurityManagerRealms
名称概念
Subject”用户“,也可以指操作应用的人,第三方的应用, 当前正在与软件交互的事物
SecurityManager是 Shiro 体系结构的核心,并充当一种“伞”对象,该对象协调其内部安全组件,一起形成对象图。就是管理shiro中的各种组件,如,subject,realm等等
Realms充当 Shiro 与您的应用程序安全数据之间的“bridge 梁”或“连接器”。就是整用户名和密码的

关键点

  • 经典的图

image.png

image.png

  • 属于shiro的东西都归Security Manager管
  • 所以写代码时,将shiro的东西与Security Manager绑定是核心
  • 最主要的权限管理的一定要有:Subject, SecurityManagerRealms

样例

  • 导入依赖(要用Mybatis和Druid数据源)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringBootShiroMybatis</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.5</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.14.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
  • 配置文件(用Druid数据源和配置Mybtis)
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=Asia/Shanghai
    driver-class-name: com.mysql.cj.jdbc.Driver
    #SpringBoot默认是不注入这些的,需要自己绑定
    #druid数据源专有配置
    type: com.alibaba.druid.pool.DruidDataSource
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许报错,java.lang.ClassNotFoundException: org.apache.Log4j.Properity
    #则导入log4j 依赖就行
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
  type-aliases-package: com.boot.pojo
  mapper-locations: classpath:mapper/*.xml
  • 实体类pojo(User [id,name, password,perm]) ,略

image.png

  • 各种Mapper(就用一个方法)
User getUserByName(String name);
  • 配置类config,有两个
@Configuration
public class ShiroConfig {

    //shiroFactoryBean
    //在基于Spring的Web应用程序中使用FactoryBean来定义主Shiro过滤器
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("webSecurityManager") DefaultWebSecurityManager webSecurityManager){
           ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
           //设置安全管理器
           bean.setSecurityManager(webSecurityManager);//设置SecurityManager,管理过滤器
           //添加shiro内置过滤器
           /*
            anon:无需认证就可以访问
            authc:必须认证了才能让问
            user:必颈拥有记往我功能才能用
            perms :拥有对某个资源的权限才能访问;
            role:拥有某个角色权限才能访问
            */
           Map<String, String> filterMap = new LinkedHashMap<>();
           //filterMap.put("/user/add","authc");
           //需要认证才能访问这个链接/user/add
           //filterMap.put("/user/update","authc");
        //拦截
        //设置不同权限名允许访问的地方
           filterMap.put("/user/add","perms[user:add]");//[括号]里面就是某种权限的名字,可以随意
           filterMap.put("/user/update","perms[user:update]"); //这个权限可以访问/user/update
           //filterMap.put("/user/*","authc");
        //要注意filterMap.put的顺序,上面的权限大,下面权限小的不执行
           bean.setFilterChainDefinitionMap(filterMap);
           bean.setLoginUrl("/toLogin");//设置登录请求
           bean.setUnauthorizedUrl("/unAuth");//设置未授权页面
           return bean;
    }
    //SecurityManager需要关联realm

    @Bean
    public DefaultWebSecurityManager webSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(userRealm);//将userRealm和securityManager关联
        return securityManager;
    }
    //创建realm
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
}
public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {// principal就是账户主体,本例就是user
        System.out.println("执行了授权");
        User user = (User)principals.getPrimaryPrincipal();
        System.out.println(user.getPerm());
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //simpleAuthorizationInfo.addStringPermission("user:add");
//        Subject subject = SecurityUtils.getSubject();
//        User currentUser= (User) subject.getPrincipal();//拿到user
        //simpleAuthorizationInfo.addStringPermission((String) principals.getPrimaryPrincipal());//从数据库中获取权限
        simpleAuthorizationInfo.addStringPermission(user.getPerm());
        return simpleAuthorizationInfo;
    }
    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行的认证");
//        String username="root";
//        String password="123456";
//        UsernamePasswordToken userToken= (UsernamePasswordToken) token;
//        if (!userToken.getUsername().equals(username))
//        {
//            return null;//抛出异常到subject所在地
//        }
//        //密码认证,shiro干活
//        return new SimpleAuthenticationInfo("",password,"");
        UsernamePasswordToken userToken= (UsernamePasswordToken) token;
        User user=userService.getUserByName(userToken.getUsername());//查数据库,realm的职责就是这个
        if (user==null){//认证不通过
            return null;//返回null,让subject.login(token),抛出异常
        }
        //将user,丢到SimpleAuthenticationInfo,以便通过subject获取
        return new SimpleAuthenticationInfo(user,userToken.getPassword(),"");
        //将查到的pojo变成账户主体(principal),以及匹配密码,将密码进行哈希盐加密
        //principals,存在session中
    }
}
  • controller
@Controller
public class ShiroController {
    @GetMapping({"/","index"})
    public String index(Model model){
        model.addAttribute("msg","hello Shiro");
        return "index";
    }
    @GetMapping("/user/add")
    public String add(){
         return "user/add";
     }
    @GetMapping( "/user/update")
    public String update(){
    return "user/update";
    }
    @GetMapping("/toLogin")
    public String toLogin(){
        return "login";
    }
    @PostMapping("/login")
    public String login(String username,String password,Model model){
        Subject subject = SecurityUtils.getSubject();//从线程获得subject,
        /*
        运行中,SubjectThreadState对象,这个对象很重要,
        主要负责将subject和securityManager通过ThreadContext对象绑定到当前线程中
         */
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
        //将username和password,打包成令牌
        try {
            subject.login(usernamePasswordToken);//通过login携带令牌进入认证(authenticate)
            return "index";
        }
        catch (UnknownAccountException | IncorrectCredentialsException e){//用户名不存在,或密码错误
            model.addAttribute("msg","用户名或密码错误");
           return "login";
        }
    }
     @GetMapping("/unAuth")
     @ResponseBody
    public String unAuthorized(){
         return "未授权,请充值一百万";
    }

}
  • 前端目录

image.png

注释很关键

大致流程

image.png

配置

image.png

你可能有点好奇心