Bootstrap

shiro框架的简单介绍以及使用(一)

写给我大聂哥

其他的内容在这里
shiro框架的密码校验(二)
shiro框架的权限设定(三)
shiro框架的密码校验进阶(四)多登陆方式实现思路

1.什么是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,您可以快速、轻松地获得任何应用程序——从最小的移动应用程序到最大的网络和企业应用程序。

简单来说就是:Shiro是apache的一个开源安全框架,作用就是实现用户身份认证、权限授权、加密、会话管理等功能。

2.shiro有那些模块

在这里插入图片描述

2.1主要模块

  • Authentication:认证/登录
    认证简单来说就是登录,判断用户的账号密码是否正确,有没有这个用户。
  • Authorization:授权与权限验证。
    授权就是给与用户某个权限,权限验证就是判断这个用户有没有权限干这个事 。
    例如:你打LOL选了瞎子,你能看到所有皮肤,但是你没买的你用不了,为什么呢?
    因为你没这个权限呗,你买了我就给你加上这个权限,你就能用了。
  • SessionManagement:会话管理
    会话就是说用户访问应用时保持的连接关系,能够识别出当前访问的用户是谁。
  • Cryptography:加密
    加密就是加密,字面意思,使用密码算法保持数据安全,同时仍然易于使用。

2.2次要模块

官方说法

  • Web支持:Shiro的Web支持API可帮助轻松保护Web应用程序。
  • 缓存:缓存是Apache Shiro API的第一层公民,可确保安全操作保持快速有效。
  • 并发性:Apache Shiro的并发功能支持多线程应用程序。
  • 测试:测试支持可帮助您编写单元测试和集成测试,并确保您的代码将按预期进行保护。
  • “运行方式”:一种功能,允许用户采用其他用户的身份(如果允许),有时在管理方案中很有用。
  • “记住我”:在整个会话中记住用户的身份,因此他们只需要在必要时登录。

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

3.shiro高层概念

在这里插入图片描述

3.1三个主要概念

  • Subject
    代表当前用户,用户的抽象概念,它会被绑定到SecurityManager,所有的交互都由SecurityManager来完成
    简单来说相当于一个实体类,它本身没有功能性,只是起到一个存储数据的作用。
  • SecurityManager
    SecurityManager是shiro的核心,所有的安全交互都在这个里面完成。
  • Realm
    Realm是一个域,SecurityManager在Realm中获取安全数据,如用户是否合法,是否有权限访问
    也就是说上面描述的两个模块Authentication和Authorization都是在这个概念里面。

3.2从上面这个概念描述我们可以看出运行的顺序

用户登录->Subject->SecurityManager->Realm进行验证。
而Realm我们上面也说了Authentication和Authorization都是在这个概念里面,那么这两个模块先运行谁呢?
那肯定是Authentication登录认证啊,你不登录去查Authorization权限干嘛咧?看有没有权限登录?你搁这想屁吃呢。

4.shiro实现登录

4.1 maven配置

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

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.10.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

        <!-- MySQL 连接驱动依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.10</version>
        </dependency>

        <!-- spring boot 热部署 stat-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <!-- spring boot 热部署 end -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                <fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
            </configuration>
            </plugin>
        </plugins>
    </build>

4.2 JacksonUtil工具类

package com.lzl.practice.Util;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.util.List;

public class JacksonUtil {
	public static String parseString(String body, String field) {
		ObjectMapper mapper = new ObjectMapper();
		JsonNode node = null;
		try {
			node = mapper.readTree(body);
			JsonNode leaf = node.get(field);
			if (leaf != null)
				return leaf.asText();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static List<String> parseStringList(String body, String field) {
		ObjectMapper mapper = new ObjectMapper();
		JsonNode node = null;
		try {
			node = mapper.readTree(body);
			JsonNode leaf = node.get(field);

			if (leaf != null)
				return mapper.convertValue(leaf, new TypeReference<List<String>>() {
				});
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static Integer parseInteger(String body, String field) {
		ObjectMapper mapper = new ObjectMapper();
		JsonNode node = null;
		try {
			node = mapper.readTree(body);
			JsonNode leaf = node.get(field);
			if (leaf != null)
				return leaf.asInt();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static List<Integer> parseIntegerList(String body, String field) {
		ObjectMapper mapper = new ObjectMapper();
		JsonNode node = null;
		try {
			node = mapper.readTree(body);
			JsonNode leaf = node.get(field);

			if (leaf != null)
				return mapper.convertValue(leaf, new TypeReference<List<Integer>>() {
				});
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static Boolean parseBoolean(String body, String field) {
		ObjectMapper mapper = new ObjectMapper();
		JsonNode node = null;
		try {
			node = mapper.readTree(body);
			JsonNode leaf = node.get(field);
			if (leaf != null)
				return leaf.asBoolean();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static Short parseShort(String body, String field) {
		ObjectMapper mapper = new ObjectMapper();
		JsonNode node = null;
		try {
			node = mapper.readTree(body);
			JsonNode leaf = node.get(field);
			if (leaf != null) {
				Integer value = leaf.asInt();
				return value.shortValue();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static Byte parseByte(String body, String field) {
		ObjectMapper mapper = new ObjectMapper();
		JsonNode node = null;
		try {
			node = mapper.readTree(body);
			JsonNode leaf = node.get(field);
			if (leaf != null) {
				Integer value = leaf.asInt();
				return value.byteValue();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static <T> T parseObject(String body, String field, Class<T> clazz) {
		ObjectMapper mapper = new ObjectMapper();
		JsonNode node = null;
		try {
			node = mapper.readTree(body);
			node = node.get(field);
			return mapper.treeToValue(node, clazz);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static Object toNode(String json) {
		if (json == null) {
			return null;
		}
		ObjectMapper mapper = new ObjectMapper();
		try {
			JsonNode jsonNode = mapper.readTree(json);
			return jsonNode;
		} catch (IOException e) {
			e.printStackTrace();
		}

		return null;
	}

}

4.3 配置shiroConfig

@Configuration
public class ShiroConfig {

    //将自己的登录验证方式加入容器
    //直接写return new AuthRealm()也是一样的,但是不要在下面的securityManager()方法中直接new AuthRealm()
    //直接new的话我只能说好家伙,自己实例化对象之后该类就不会在springboot容器中了,
    //这样就不能在AuthRealm类中autowired了
    @Bean
    public Realm AuthRealm() {
        AuthRealm authRealm = new AuthRealm();
        return authRealm;
    }



   //将自己配置的AuthRealm配置到securityManager中
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(AuthRealm());
        return securityManager;
    }

    //Filter工厂,设置对应的过滤条件和跳转条件
    @Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String,String> map = new HashMap<String, String>();
        //登出
        map.put("/logout","logout");
        //对所有用户认证
        map.put("/**","authc");

        //首页
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //错误页面,认证不通过跳转
        shiroFilterFactoryBean.setUnauthorizedUrl("/auth/error");
        shiroFilterFactoryBean.setLoginUrl("/auth/login");//登录
        shiroFilterFactoryBean.setSuccessUrl("/auth/index");

        shiroFilterFactoryBean.setUnauthorizedUrl("/auth/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    //开启代码支持
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }
}

shirFilter工厂里面的的logout、authc、anon都是拦截器。
拦截器链名定义

名字名称说明
anon匿名拦截器不需要登录就能访问,一般用于静态资源,或者移动端接口
authc登录拦截器需要登录认证才能访问的资源
user用户拦截器用户拦截器,用户已经身份验证 / 记住我登录的都可;
logout登出拦截器用户登出拦截器

4.4 配置AuthRealm

public class AuthRealm extends AuthorizingRealm {

    /***
     * @Description: 这块是授权配置
     * @Param: [principalCollection]
     * @return: org.apache.shiro.authz.AuthorizationInfo
     * @Author: lizelin
     * @Date: 2021/5/13 0013 1:25
    */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        return info;
    }
    
    /***
     * @Description: 这块是登录验证配置
     * @Param: [token]
     * @return: org.apache.shiro.authc.AuthenticationInfo
     * @Author: lizelin
     * @Date: 2021/5/13 0013 1:25
    */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //AuthenticationToken转成UsernamePasswordToken这样可以直接拿到用户名和密码
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();
        //判断用户名是否为aaa否则抛出异常完事密码错误
        if(!username.equals("aaa")){
            throw new UnknownAccountException("用户错误");
        }
        return new SimpleAuthenticationInfo(upToken.getUsername(), upToken.getPassword(), "ShiroReaml");
    }

这里我只写了登录验证配置,判断用户名是否正确,简单测试一下,加入授权和密码验证明天再说今天困了。

4.5 配置controller测试


@RestController
@RequestMapping("/auth")
public class AuthController {

    /**
    * @Description: 登录验证
    * @Param: [body]
    * @return: java.lang.Object
    * @Author: Mr.Wang
    * @Date: 2021/5/13 0013 1:50
    */
    @PostMapping("/login")
    public Object login(@RequestBody String body) {
        String username = JacksonUtil.parseString(body, "username");
        String password = JacksonUtil.parseString(body, "password");
        //获取Subject
        Subject subject = SecurityUtils.getSubject();
        try {
            //登录测试,就这一行代码
            subject.login(new UsernamePasswordToken(username, password));
        } catch (UnknownAccountException uae) {
            return "用户帐号或密码不正确";
        } catch (LockedAccountException lae) {
            return "用户帐号已锁定不可用";
        } catch (AuthenticationException ae) {
            return "用户帐号已锁定不可用";
        }
        return "登录成功,账号名:"+username;
    }

    @PostMapping("/testAuthc")
    public Object testAuthc() {
        return "testAuthc";
    }
    
    @GetMapping("/401")
    public Object page401() {
        return "401";
    }

    @GetMapping("/index")
    public Object pageIndex() {
        return "index";
    }

    @GetMapping("/403")
    public Object page403() {
        return "index";
    }

    @GetMapping("/error")
    public Object error() {
        return "error";
    }
}

简单的测试环境就搭好了,完事我们去测试。

5.spring boot+shiro测试

5.1账号正确测试

在这里插入图片描述
ok登录成功,测试没毛病。

5.2账号错误测试

在这里插入图片描述
ok返回密码错误,测试没毛病。

5.3拦截器authc测试

在这里插入图片描述

在这里插入图片描述
ok返回地址是/auth/login,测试拦截没毛病。

简单的登陆用户名验证就完事了,然后写密码验证,权限,三方登录

;