SpringSecurity框架详解

Posted 不易撞的网名

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSecurity框架详解相关的知识,希望对你有一定的参考价值。

SpringSecurity

来源视频

文章目录

1、概述

​ Spring Security是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富;

​ Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的实际标准;

​ Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring Security的真正强大之处在于可以轻松扩展以满足自定义要求。

​ 在 Java 生态中,目前有 Spring SecurityApache Shiro 两个安全框架,可以完成认证和授权的功能。

我们先来学习下 Spring Security 。其官方对自己介绍如下:

Spring Security is a powerful and highly customizable authentication and access-control framework. It is the de-facto standard for securing Spring-based applications.

​ Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实标准。

Spring Security is a framework that focuses on providing both authentication and authorization to Java applications. Like all Spring projects, the real power of Spring Security is found in how easily it can be extended to meet custom requirementsSpring

​ Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正威力在于它可以多么容易地扩展以满足定制需求

一般Web应用的需要进行认证授权

​ 认证(Authentication):验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户

​ 授权(Authorization):经过认证后判断当前用户是否有权限进行某个操作

​ 而认证和授权就是SpringSecurity作为安全框架的核心功能。

2、Spring Security、Apache Shiro 选择问题

2.1、Shiro

​ 首先Shiro较之 Spring Security,Shiro在保持强大功能的同时,还在简单性和灵活性方面拥有巨大优势。

Shiro是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。如下是它所具有的特点:

  1. 易于理解的 Java Security API;

  2. 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory 等);

  3. 对角色的简单的签权(访问控制),支持细粒度的签权;

  4. 支持一级缓存,以提升应用程序的性能;

  5. 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;

  6. 异构客户端会话访问;

  7. 非常简单的加密 API;

  8. 不跟任何的框架或者容器捆绑,可以独立运行。

    Shiro四大核心功能:Authentication,Authorization,Cryptography,Session Management

四大核心功能介绍:

  1. Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
  2. Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  3. Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
  4. Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Shiro架构

Shiro三个核心组件:Subject, SecurityManager 和 Realms.

  1. Subject:主体,可以看到主体可以是任何可以与应用交互的 用户;
  2. SecurityManager:相当于 SpringMVC 中的 DispatcherServlet 或者 Struts2 中的 FilterDispatcher;是 Shiro 的心脏;所有具体的交互都通过 SecurityManager 进行控制;它管理着所有 Subject、且负责进行认证和授权、及会话、缓存的管理。
  3. Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

2.1.1、shiro的优点

  • shiro的代码更易于阅读,且使用更加简单;
  • shiro可以用于非web环境,不跟任何框架或容器绑定,独立运行;

2.1.2、shiro的缺点

  • 授权第三方登录需要手动实现;

2.2、Spring Security

​ 除了不能脱离Spring,shiro的功能它都有。而且Spring Security对Oauth、OpenID也有支持,Shiro则需要自己手动实现。Spring Security的权限细粒度更高,毕竟Spring Security是Spring家族的。

Spring Security一般流程为:

  1. 当用户登录时,前端将用户输入的用户名、密码信息传输到后台,后台用一个类对象将其封装起来,通常使用的是UsernamePasswordAuthenticationToken这个类。
  2. 程序负责验证这个类对象。验证方法是调用Service根据username从数据库中取用户信息到实体类的实例中,比较两者的密码,如果密码正确就成功登陆,同时把包含着用户的用户名、密码、所具有的权限等信息的类对象放到SecurityContextHolder(安全上下文容器,类似Session)中去。
  3. 用户访问一个资源的时候,首先判断是否是受限资源。如果是的话还要判断当前是否未登录,没有的话就跳到登录页面。
  4. 如果用户已经登录,访问一个受限资源的时候,程序要根据url去数据库中取出该资源所对应的所有可以访问的角色,然后拿着当前用户的所有角色一一对比,判断用户是否可以访问(这里就是和权限相关)。

2.2.1、spring-security的优点

  • spring-security对spring整合较好,使用起来更加方便;
  • 有更强大的spring社区进行支持;
  • 支持第三方的 oauth 授权,官方网站:spring-security-oauth

3、快速入门

3.1、装备工作

​ 我们先创建一个空项目

在项目中创建一个普通的maven项目

将该普通的maven改为SpringBoot工程

1、添加依赖

  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
    </parent>

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    </dependencies>

2、创建启动类

@SpringBootApplication
public class IntroductionSpringSecurity 


    public static void main(String[] args) 
        SpringApplication.run(IntroductionSpringSecurity.class,args);
    


3、创建Controller


@RestController
public class HelloController 


    @RequestMapping("/hello")
    public String hello()
        return "World Hello";
    


测试访问:

4、导入SpringSecurity依赖

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

重新启动测试:

这时我们可以看到当我们访问我们的接口的时候,就会自动跳转到一个SpringSecurity的默认登陆页面

这时候需要我们登录才可以进行访问,我们可以看到控制台有一串字符串,其实那就是SpringSecurity初始化生成给我的密码

默认用户名:user

输入用户名和密码,再次登录

成功。

4、认证

4.1、登录流程校验

4.2、入门案例的原理

前后端认证流程:

  1. UsernamePasswordAuthenticationFilter:是我们最常用的用户名和密码认证方式的主要处理类,构造了一个UsernamePasswordAuthenticationToken对象实现类,将用请求信息封装为Authentication

  2. Authentication接口: 封装了用户相关信息。

  3. AuthenticationManager接口:定义了认证Authentication的方法,是认证相关的核心接口,也是发起认证的出发点,因为在实际需求中,我们可能会允许用户使用用户名+密码登录,同时允许用户使用邮箱+密码,手机号码+密码登录,甚至,可能允许用户使用指纹登录(还有这样的操作?没想到吧),所以说AuthenticationManager一般不直接认证AuthenticationManager接口的常用实现类ProviderManager 内部会维护一个List列表,存放多种认证方式,实际上这是委托者模式的应用(Delegate)。也就是说,核心的认证入口始终只有一个:AuthenticationManager

    AuthenticationManager,ProviderManager ,AuthenticationProvider…

    用户名+密码(UsernamePasswordAuthenticationToken),邮箱+密码,手机号码+密码登录则对应了三个AuthenticationProvider

  4. DaoAuthenticationProvider:用于解析并认证 UsernamePasswordAuthenticationToken 的这样一个认证服务提供者,对应以上的几种登录方式。

  5. UserDetailsService接口:Spring Security 会将前端填写的username 传给 UserDetailService.loadByUserName方法。我们只需要从数据库中根据用户名查找到用户信息然后封装为UserDetails的实现类返回给SpringSecurity 即可,自己不需要进行密码的比对工作,密码比对交由SpringSecurity处理。

  6. UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。

UsernamePasswordAuthenticationFilter:是我们最常用的用户名和密码认证方式的主要处理类,构造了一个UsernamePasswordAuthenticationToken对象实现类,将用请求信息封装为Authentication

BasicAuthenticationFilter...:将UsernamePasswordAuthenticationFilter的实现类UsernamePasswordAuthenticationToken封装成的 Authentication进行登录逻辑处理

AuthenticationManager

AuthenticationProvider

UsernamePasswordAuthenticationToken(Authentication的一个实现)对象,其实就是一个Authentication的实现,他封装了我们需要的认证信息。之后会调用AuthenticationManager。这个类其实并不会去验证我们的信息,信息验证的逻辑都是在AuthenticationProvider里面,而Manager的作用则是去管理Provider,管理的方式是通过for循环去遍历(因为不同的登录逻辑是不一样的,比如表单登录、第三方登录(qq登录,邮箱登录…)。换句话说 不同的Provider支持的是不同的Authentication)。在AuthenticationManager调用DaoAuthenticationProvider。而DaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider ,从而也就获得了其中的authenticate方法去进行验证。

[]: https://zhuanlan.zhihu.com/p/201029977

ExceptionTranslationFilter:主要用于处理AuthenticationException(认证)和AccessDeniedException(授权)的异常

FilterSecurityInterceptor:获取当前 request 对应的权限配置**,**调用访问控制器进行鉴权操作

4.3、正式开始

登录:

​ 1.自定义登录接口

​ 调用ProviderManager的方法进行认证 如果认证通过生成jwt

​ 把用户信息存入redis中

​ 2.自定义UserDetailsService

​ 在这个实现类中去查询数据库

校验:

​ 1.定义Jwt认证过滤器

​ 获取token

​ 解析token获取其中的userid

​ 从redis中获取用户信息

​ 存入SecurityContextHolder

4.3.1 准备工作

重新建立一个新的普通maven项目

1.添加依赖

  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.4</version>
    </parent>

    <dependencies>


<!--        redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

<!--        json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>

<!--        jwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

<!--        mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>


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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

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

<!--       mybatis-plus-boot-starter-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>



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

    </dependencies>

2.在主启动类同级目录下建立utils包

添加Redis相关配置

FastJsonRedisSerializer

package com.qx.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import com.alibaba.fastjson.parser.ParserConfig;
import org.springframework.util.Assert;
import java.nio.charset.Charset;

/**
 * Redis使用FastJson序列化
 */
public class FastJsonRedisSerializer<T> implements RedisSerializer<T>


    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static
    
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    

    public FastJsonRedisSerializer(Class<T> clazz)
    
        super();
        this.clazz = clazz;
    

    @Override
    public byte[] serialize(T t) throws SerializationException
    
        if (t == null)
        
            return new byte[0];
        
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    
        if (bytes == null || bytes.length <= 0)
        
            return null;
        
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    


    protected JavaType getJavaType(Class<?> clazz)
    
        return TypeFactory.defaultInstance().constructType(clazz);
    

3.在主启动类同级目录下建立config包

RedisConfig

package com.qx.config;


import com.qx.utils.FastJsonRedisSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig 

    @Bean
    @SuppressWarnings(value =  "unchecked", "rawtypes" )
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);

        // Hash的key也采用StringRedisSerializer的序列化方式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        template.afterPropertiesSet();
        return template;
    

4.在主启动类同级目录下建立controller包

响应类ResponseResult

package com.qx.domain;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_NULL)
public class ResponseResult<T> 
    /**
     * 状态码
     */
    private Integer code;
    /**
     * 提示信息,如果有错误时,前端可以获取该字段进行提示
     */
    private String msg;
    /**
     * 查询到的结果数据,
     */
    private T data;

    public ResponseResult(Integer code, String msg) 
        this.code = code;
        this.msg = msg;
    

    public ResponseResult(Integer code, T data) 
        this.code = code;
        this.data = data;
    

    public Integer getCode() 
        return code;
    

    public void setCode(Integer code) 
        this.code = code;
    

    public String getMsg() 
        return msg;
    

    public void setMsg(String msg) 
        this.msg = msg;
    

    public T getData() 
        return data;
    

    public void setData(T data) 
        this.data = data;
    

    public ResponseResult(Integer code, String msg, T data) 
        this.code = code;
        this.msg = msg;
        this.data = data;
    

5.将工具类置于utils包

JwtUtil

package com.qx.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import

Shiro 安全框架详解一(概念+登录案例实现)

总结内容

一、RBAC 的概念

RBAC 全称 Role Based Access Control,意思是基于角色的访问控制,说白了就是每个用户拥有不同的角色,每个角色又拥有不同的权限,用户通过所拥有的的权限对系统进行操作。
RBAC 是一种目前使用最普片的权限管理模型,该模型中有3个基础的组成部分,分别是:用户、角色、权限。我们接下来也是围绕这3个组成部分来进行的。


二、两种常用的权限管理框架

1. Apache Shiro

Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,我们可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

2. Spring Security

Spring Security是为基于Spring的应用程序提供声明式安全保护的安全性框架,它提供了完整的安全性解决方案,能够在web请求级别和方法调用级别处理身份证验证和授权。因为基于Spring框架,所以Spring Security充分利用了依赖注入和面向切面的技术。

3. Shiro 和 Spring Security 比较

Shiro 比 Spring Security更容易上手使用和理解,Shiro 可以不跟任何的框架或者容器绑定,可独立运行,而Spring Security 则必须要有Spring环境, Shiro 可能没有 Spring Security 做的功能强大,但是在实际工作时可能并不需要那么复杂的东西,所以使用小而简单的 Shiro 就足够了。对于它俩到底哪个好,这个不必纠结,能更简单的解决项目问题就好了。


三、Shiro 的概述

1. shiro 作用

1)认证
2)授权
3)加密
4)会话管理
5)与 Web 集成
6)缓存
7)…

2. shiro 架构

Shiro 主要组件包括:Subject,SecurityManager,Authenticator,Authorizer,SessionManager,CacheManager,Cryptography,Realms。
在这里插入图片描述

  1. Subject(用户)
    Subject即主体,外部应用与subject进行交互,subject记录了当前的操作用户,将用户的概念理解为当前操作的主体。Subject 可以是一个人,但也可以是第三方服务、守护进程帐户、时钟守护任务或者其它–当前和软件交互的任何事件。
    在程序任意位置可使用:Subject currentUser = SecurityUtils.getSubject() 获取到subject主体对象,类似 Employee user = UserContext.getUser() ;

  2. SecurityManager(安全管理器)
    它是 shiro 功能实现的核心,负责与后边介绍的其他组件(认证器/授权器/缓存控制器)进行交互,实现 subject 委托的各种功能。有点类似于SpringMVC 中的 DispatcherServlet 前端控制器,负责进行分发调度。

  3. Realms(数据源)
    Realm 充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”。;可以把Realm 看成 DataSource,即安全数据源。执行认证(登录)和授权(访问控制)时,Shiro 会从应用配置的 Realm 中查找相关的比对数据。以确认用户是否合法,操作是否合理。

  4. Authenticator(认证器)
    用于认证,从 Realm 数据源取得数据之后进行执行认证流程处理。

  5. Authorizer(授权器)
    用户访问控制授权,决定用户是否拥有执行指定操作的权限。

  6. SessionManager (会话管理器)
    Shiro 与生俱来就支持会话管理,这在安全类框架中都是独一无二的功能。即便不存在 web 容器环境,shiro 都可以使用自己的会话管理机制,提供相同的会话 API。

  7. CacheManager (缓存管理器)
    用于缓存认证授权信息等。

  8. Cryptography(加密组件)
    Shiro 提供了一个用于加密解密的工具包。


四、基于 ini 的认证案例实现

1. 实现原理图

在这里插入图片描述

2. 实现代码

2.1 添加 maven jar包依赖

<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.3</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.5.2</version>
</dependency>

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.22</version>
    <scope>provided</scope>
</dependency>

2.2 编写 ini 配置文件:shiro-authc.ini

shiro默认支持的是ini配置的方式,这里只是为了方便,项目中还是会选择xml

#用户的身份、凭据
[users]
admin=123
xfy=123

2.3 使用 Shiro 相关的 API 完成身份认证

@Test
public void testLogin(){
    //创建Shiro的安全管理器,是shiro的核心
    DefaultSecurityManager securityManager = new DefaultSecurityManager();
    //加载shiro.ini配置,得到配置中的用户信息(账号+密码)
    IniRealm iniRealm = new IniRealm("classpath:shiro-authc.ini");
    securityManager.setRealm(iniRealm);
    //把安全管理器注入到当前的环境中
    SecurityUtils.setSecurityManager(securityManager);
    //无论有无登录都可以获取到subject主体对象,但是判断登录状态需要利用里面的属性来判断
    Subject subject = SecurityUtils.getSubject();
    System.out.println("认证状态:"+subject.isAuthenticated());
    //创建令牌(携带登录用户的账号和密码)
    UsernamePasswordToken token = new UsernamePasswordToken("dafei","666");
    //执行登录操作(将用户的和 ini 配置中的账号密码做匹配)
    subject.login(token);
    System.out.println("认证状态:"+subject.isAuthenticated());
    //登出
    //subject.logout();
    //System.out.println("认证状态:"+subject.isAuthenticated());
}

如果输入的身份和凭证和 ini 文件中配置的能够匹配,那么登录成功,登录状态为true,反之登录状态为false。

  • 登录失败一般存在两种情况
    【1】账号错误 org.apache.shiro.authc.UnknownAccountException
    【2】密码错误 org.apache.shiro.authc.IncorrectCredentialsException

五、项目中集成 Shiro 认证案例实现

1. 项目准备

1.1 添加依赖

<properties>
	<shiro.version>1.5.2</shiro.version>
</properties>
<!--shiro 核心-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!--shiro 的 Web 模块-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-web</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!--shiro 和 Spring 集成-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!--shiro 底层使用的 ehcache 缓存-->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>${shiro.version}</version>
</dependency>
<!--shiro 依赖的日志包-->
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.2</version>
</dependency>
<!--Freemarker 的 shiro 标签库-->
<dependency>
    <groupId>net.mingsoft</groupId>
    <artifactId>shiro-freemarker-tags</artifactId>
    <version>1.0.1</version>
</dependency>

1.2 配置代理过滤器

在访问的时候,需要做一系列的预处理操作,Shiro是选择使用filter过滤器来进行拦截的,因为Shiro不依赖Spring容器,所以当没有springmvc时意味着不能用拦截器,但过滤器则不同,只要是web项目都可以使用。

  • web.xml 文件
<filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>
        org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

这里使用了一个代理过滤器DelegatingFilterProxy,因为真正的shiroFilter需要注入很多复杂的对象,而web.xml中只能配置字符串或数字的参数,是不能满足的,因此我们会把shiroFilter交给 Spring 进行管理,通过spring的xml文件来配置。 使用DelegatingFilterProxy代理过滤器后,但浏览器发送请求过来,被代理过滤器拦截到后,代理过滤器会自动从 spring 容器中找filter-name所配置相同名称的bean,来实现真正的业务。


2. 代码实现

2.1 登录的 LoginController 控制器

@RequestMapping("/userLogin")
@ResponseBody
public JsonResult login(String username, String password) {
    try {
        UsernamePasswordToken token = new UsernamePasswordToken(username, password); 			SecurityUtils.getSubject().login(token);
        // JsonResult 是一个自定义的工具类,里面就 2 个参数,boolean 类型的 success,String 类型的 message
        return new JsonResult();
    } catch (UnknownAccountException e) {
        return new JsonResult(false, "账号不存在");
    } catch (IncorrectCredentialsException e) {
        return new JsonResult(false, "密码错误");
    } catch (Exception e) { 
        e.printStackTrace();
        return new JsonResult(false, "登录异常,请联系管理员");
    }
}

2.2 配置自定义Realm:CarBusinessRealm

shiro默认支持的是ini配置的方式,这里只是为了方便,项目中还是会选择xml

/**
 * 自定义Realm查询mysql
 */
public class CarBusinessRealm extends AuthorizingRealm {
    @Autowired
    private IEmployeeService employeeService;
    //提供认证信息
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //通过token获取用户名(用户登录的时候填的)
        String username = (String)token.getPrincipal();
        //判断是否存在数据库(根据名字查询员工)
        Employee employee = employeeService.selectByUsername(username);
        if(employee!=null){
            //身份对象,真正的密码,当前realm的名字
            return new SimpleAuthenticationInfo(employee, employee.getPassword(), this.getName());
        }
        //返回值就是查询出来的数据,如果查到这个账号,就应该返回该账号正确的数据,如果查不到,就返回null
        return null;
    }
    //提供授权信息
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return null;
    }
}

2.3 创建 shiro.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- <aop:config/> 会扫描配置文件中的所有 advisor,并为其创建代理 -->
    <!-- <aop:config/> -->

    <!-- Pointcut advisor 通知器,会匹配所有加了 shiro 权限注解的方法 -->
    <!-- <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean> -->

    <!-- 自定义 realm -->
    <bean id="carBusinessRealm" class="cn.wolfcode.realm.CarBusinessRealm"/>

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 设置 realm 值-->
        <property name="realm" ref="carBusinessRealm"/>
    </bean>


    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 引用指定的安全管理器 -->
        <property name="securityManager" ref="securityManager"/>
        <!-- shiro 默认的登录地址是 /login.jsp 现在要指定我们自己的登录页面地址 -->
        <property name="loginUrl" value="/login.html"/>
        <!-- 路径对应的规则:登录校验规则 -->
        <property name="filterChainDefinitions">
            <value>
                /userLogin=anon
                /css/**=anon
                /js/**=anon
                /img/**=anon
                /upload/**=anon
                /static/**=anon
                /userLogout=logout
                /**=authc
            </value>
        </property>
    </bean>
</beans>

2.4 在SpringMVC 中引入 shiro.xml

<import resource="classpath:shiro.xml"/>

2.5 安全管理器

在 JavaEE 环境中,我们需要使用的安全管理器是:DefaultWebSecurityManager

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 这里面有很多配置文件 -->
</bean>

有了上面的配置,当我们的访问到达具体资源之前,会先进过指定的过滤器做预处理,在允许通过之后才能继续访问。

  • Shiro 中定义了多个过滤器来完成不同的预处理操作
过滤器的名称Java
anonorg.apache.shiro.web. lter.authc.AnonymousFilter
authcorg.apache.shiro.web. lter.authc.FormAuthenticationFilter
authcBasicorg.apache.shiro.web. lter.authc.BasicHttpAuthenticationFilter
rolesorg.apache.shiro.web. lter.authz.RolesAuthorizationFilter
permsorg.apache.shiro.web. lter.authz.PermissionsAuthorizationFilter
userorg.apache.shiro.web. lter.authc.UserFilter
logoutorg.apache.shiro.web. lter.authc.LogoutFilter
portorg.apache.shiro.web. lter.authz.PortFilter
restorg.apache.shiro.web. lter.authz.HttpMethodPermissionFilter
sslorg.apache.shiro.web. lter.authz.SslFilter

anon: 匿名处理过滤器,即不需要登录即可访问;一般用于静态资源过滤;

示例 /static/**=anon

authc: 表示需要认证(登录)才能使用;

示例 /**=authc

roles: 角色授权过滤器,验证用户是否拥有资源角色;

示例 /admin/*=roles[admin]

perms: 权限授权过滤器,验证用户是否拥有资源权限;

示例 /employee/input=perms[“user:update”]

logout: 注销过滤器

示例 /logout=logout

3. 注销功能

shiro.xml 中的路径规则加入 /logout=logout 即可交给shiro来处理,我们以前写的LoginController中的logout方法可以删掉啦。


总结

以上就是对Shiro 安全框架入门的总结了,代码仅供参考,欢迎讨论交流。

以上是关于SpringSecurity框架详解的主要内容,如果未能解决你的问题,请参考以下文章

Springboot中使用springsecurity

Shiro 安全框架详解一(概念+登录案例实现)

shiro入门

Shiro Review——Shiro介绍

Spring Security:简单的保护一个SpringBoot应用程序(总结)

springsecurity-shiro获取登录用户详解