Shiro

目录

1 Shiro简介

1.1 什么是shiro

1.2 Shiro架构

2 SpringBoot整合Shiro

2.1 用户认证

 2.2 用户授权


1 Shiro简介

1.1 什么是shiro

  • Apache Shiro是一个Java安全的框架
  • Shiro能够非常容易开发出很好的应用,其不仅可以在JavaSE环境,也可以用在JavaEE环境
  • Shiro可以完成认证、授权、加密、会话管理、web集成、缓存等
  • 下载地址:Apache Shiro | Simple. Java. Security.

Authentication:身份认证/登录,验证用户是不是拥有相应的身份

Authorization:授权,即权限验证,验证某个已经认证的用户是否拥有某个权限

Session Manager:会话管理,即用户登录以后就是一次会话,在没有推出之前,它的所有信息都会存储在会话中;会话可以是普通的JavaSE环境,也可以是如web环境

Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储

Web Support:Web支持。可以非常容易集成到web环境

Caching:缓存,用户登录后,其用户信息、拥有的角色\权限不必每次去查、这样可以提高效率

Concurrency:shiro 支持多线程的并发验证,即在一个线程中开启另外骗一个线程,能将权限自动传播过去

Testing:提供测试支持

Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问

Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。

注意:shiro不会去维护用户、维护权限;这些需要我们自己去设计提供;然后通过相应的接口注入给shiro即可。

1.2 Shiro架构

Subject:主体,代表了当前用户,这个用户不一定是一个具体的人,与当前用户交互的任何东西都要Subject,如网络爬虫、机器人等;相当于一个抽象概念;所有的subject都要绑定到SecurityManager,与Subject所有交互都会委托给SecurityManager;可以将Subject认为是一个门面,而SecurityManager才是实际的执行者。

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且他管理者所有的Subject;可以看出他是Shiro的核心,它负责与后边介绍的其他组件交互,就相当于SpringMVC中DispatcherServlet 前端控制器。

Relam:域,shiro从Relam获取安全数据(用户、角色、权限),就是SecurityManager 需要验证身份,那么久需要从Relam获取相应的用户进行比较以确定用户身份是否合法;也需要从Relam得到相应的角色\权限进行验证用户是否能进行操作;就相当于DataSource这样的一个数据源。

注意:

1)应用代码通过Subject进行认证和授权,而subject又委托给SecurityManager;

2)我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager得到合法的用户及其权限进行判断。

2 SpringBoot整合Shiro

  项目依赖:

<?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.6.7</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.liubujun</groupId>
	<artifactId>springboot-shiro</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>springboot-shiro</name>
	<description>springboot-shiro</description>
	<properties>
		<java.version>1.8</java.version>
	</properties>
	<dependencies>

		<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
        <!--shiro整合spring的包-->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.9.0</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.thymeleaf</groupId>
			<artifactId>thymeleaf-spring5</artifactId>
			<version>3.0.11.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-java8time</artifactId>
			<version>3.0.4.RELEASE</version>
		</dependency>
		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>2.6</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-core</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.2.9</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

实现逻辑:

定义几个html,让其登录能够进行访问,从而验证我们的授权和验证的逻辑。

 如:首页有add和update两个按钮,点击其中一个按钮就进入到其对应的页面。

2.1 用户认证

步骤一:shiro的配置类,其分为3步

配置UserRealmDefaultWebSecurityManagerShiroFilterFactoryBean
@Configuration
public class ShiroConfig {


    /**
     * 步骤三:ShiroFilterFactoryBean
     * @param defaultWebSecurityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        //添加shiro的内置过滤器
        /**
         * anon: 无需认证就可以访问
         * authc: 必须认证了才能访问
         * user: 必须记住我才能访问
         * perms: 拥有对某个资源的权限才能访问
         * role: 拥有某个角色权限才能访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();
//        filterMap.put("/user/add","authc");
//        filterMap.put("/user/update","authc");
        filterMap.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filterMap);

        //设置登录的请求
        bean.setLoginUrl("/toLogin");
        return bean;
    }


    /**
     * 步骤二:DefaultWebSecurityManager
     * @param userRealm
     * @return
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联UserRelam
        securityManager.setRealm(userRealm());
        return securityManager;
    }


    /**
     * 步骤一
     * 创建realm对象,需要自定义类
     * @return
     */
    @Bean
    public UserRealm userRealm(){

        return new UserRealm();
    }

}

步骤二:定义UserRealm类

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;

/**
 * @author liubujun
 * @date 2022/5/16 13:40
 */
public class UserRealm extends AuthorizingRealm {

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

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了》》》认证doGetAuthenticationInfo");
        String name = "root";
        String password = "123456";

        UsernamePasswordToken userToken = (UsernamePasswordToken)token;
        if (!userToken.getUsername().equals(name)){
            return null;
        }
        //shiro对密码进行了加密处理 不能直接验证
        return new SimpleAuthenticationInfo("",password,"");
    }
}

注意:只要是执行了登录操作,就会进入到这个认证逻辑,其中shiro对密码进行了加密处理,不能直接进行验证。

步骤三:controller层访问

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author liubujun
 * @date 2022/5/12 16:54
 */
@Controller
public class MyController {


    @RequestMapping({"/","/index"})
    public String toIndex(Model model){
        model.addAttribute("msg","欢迎学习Shiro");
        return "index";

    }

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


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

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }

    @RequestMapping("/login")
    public String login(String username,String password,Model model){
        //获取当前的用户
        //获取当前的用户
        Subject subject = SecurityUtils.getSubject();
        //封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            //执行登录方法,如果没有异常就说明OK了
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) { //用户名不存在异常
           model.addAttribute("msg","用户名错误");
           return "login";
        }catch (IncorrectCredentialsException e) { //密码不存在
            model.addAttribute("msg","密码错误");
            return "login";
        }


    }
}

步骤四:测试

1)访问

2)点击add或者update跳转登录

 3)输入账号和密码(正确的是root/123456)我输的是root/root

 

 可见,密码错误时会自动拦截,并且控制台执行了认证逻辑

 4)输入正确的账号和密码(root/123456)

 可见进入了页面,并且控制台也执行了认证逻辑

 2.2 用户授权

用户授权需要用到

* perms: 拥有对某个资源的权限才能访问

步骤一:在shiroConfig中的ShiroFilterFactoryBean里面对perms进行配置;例如:

filterMap.put("/user/update","perms[user:update]");

那么登录用户必须拥有update权限才能够进行访问 ;

演示:登录进来之后

1)点击add ,进入页面

 

2)点击update,无法访问 

 注意:因为此时登录进来的页面是什么权限也没有的(如果不加上面那条授权),就add和update两者都可以进来,但加了之后就只有update权限的用户才能进来了(但登录用户此时什么权限都没,所以能够进入add,因为add不需要授权,不能够进入update,因为被要求授权了才可以进)

步骤二:对所有接口进行授权

//授权
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");


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;

/**
 * @author liubujun
 * @date 2022/5/16 13:35
 */
@Configuration
public class ShiroConfig {


    /**
     * 步骤三:ShiroFilterFactoryBean
     * @param defaultWebSecurityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        //设置安全管理器
        bean.setSecurityManager(defaultWebSecurityManager);
        //添加shiro的内置过滤器
        /**
         * anon: 无需认证就可以访问
         * authc: 必须认证了才能访问
         * user: 必须记住我才能访问
         * perms: 拥有对某个资源的权限才能访问
         * role: 拥有某个角色权限才能访问
         */
        Map<String, String> filterMap = new LinkedHashMap<>();

        //授权
        filterMap.put("/user/add","perms[user:add]");
        filterMap.put("/user/update","perms[user:update]");

        filterMap.put("/user/*","authc");
        bean.setFilterChainDefinitionMap(filterMap);

        //设置登录的请求
        bean.setLoginUrl("/toLogin");
        //设置未授权页面
        bean.setUnauthorizedUrl("/noauth");
        return bean;
    }


    /**
     * 步骤二:DefaultWebSecurityManager
     * @param userRealm
     * @return
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){

        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //关联UserRelam
        securityManager.setRealm(userRealm());
        return securityManager;
    }


    /**
     * 步骤一
     * 创建realm对象,需要自定义类
     * @return
     */
    @Bean
    public UserRealm userRealm(){

        return new UserRealm();
    }

}

 步骤三:设置UserRealm,对用户进行授权

 

package com.liubujun.config;

import com.liubujun.entity.User;
import org.apache.shiro.SecurityUtils;
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.apache.shiro.subject.Subject;

/**
 * @author liubujun
 * @date 2022/5/16 13:40
 */
public class UserRealm extends AuthorizingRealm {

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


         SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
         info.addStringPermission("user:add");

         //拿到当前登录的这个对象
         Subject subject = SecurityUtils.getSubject();
         User user = (User) subject.getPrincipal();

         info.addStringPermission(user.getPerms());
        return info;
    }

    /**
     * 认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行了》》》认证doGetAuthenticationInfo");
        String name = "root";
        String password = "123456";

        //为了在授权那边能够拿到用户信息,定义了个用户对象并且传到了下面的SimpleAuthenticationInfo中的principal参数,这样在
        //授权那边可以直接获取到
        User user = new User();
        user.setUsername(name);
        user.setPassword(password);

        String perms = "user:add";
        user.setPerms(perms);


        UsernamePasswordToken userToken = (UsernamePasswordToken)token;
        if (!userToken.getUsername().equals(name)){
            return null;
        }
        //shiro对密码进行了加密处理 不能直接验证
        return new SimpleAuthenticationInfo(user,password,"");
    }
}

步骤三:controller加个未授权的接口

    @RequestMapping("/noauth")
    @ResponseBody
    public String Unauthorized(){
        return "未经授权无法发访问本页面";
    }

步骤四:访问

因为add和update都被要求了需要授权,而在UserRelam中用户只被赋予了update权限。所以update可以访问,而add不能。

首页:

 访问:add

 

访问update

 

 

posted @ 2022-05-18 11:16  小猪不会叫  阅读(75)  评论(0)    收藏  举报  来源