Spring Security学习笔记

1.权限控制

1.1认证和授权概念

以此项目为例:https://gitee.com/fan-shuaiqiang/health-project

该项目的功能有:检查项管理、检查组管理、套餐管理、预约管理等。

我们来思考以下两个问题:

  • 问题1:在生产环境下,如果我们不登录后台系统可以操作这些功能吗?
    • 答案当然是不能的,我们不能让任何人都能操作我们的系统,要进行相关操作必须先登录到系统上。
  • 问题2:是不是所有用户,只要登录成功就可以操作所有的功能呢?
    • 答案当然也是不能,不同的用户可能有不同的权限,这就需要对不同用户进行授权

由此我们可以得出,要想实现权限控制,必须要进行认证授权

  1. 认证:系统提供的用于识别用户身份的功能,通常我们提供用户名和密码进行登录就是认证,认证的目的是让系统知道你是谁。
  2. 授权:用户认证成功后,需要为用户授权,其实就是指定当前用户可以操作哪些功能。对于一个系统,普通用户和管理员所看到的页面和能够进行的操作是不同的。

1.2权限模块数据模型

前面已经分析了认证和授权的概念,而要想实现最终的权限控制,需要有一套表结构支撑。

通常,我们用7张表来构成权限模块数据模型,分别为:

  1. 用户表(t_user):存放用户的基本信息,如:用户名、密码。
  2. 角色表(t_role):存放为系统分配的不同角色。如:普通用户、管理员。
  3. 用户角色关联表(t_user_role):用户登录到系统之后,我们要为其分配角色。同时,因为一个用户可能对应多个角色,一个角色也可能对应多个用户,是多对多的关系,所以需要在用户表和角色表之间创建一张关联表。
  4. 权限表(t_permission):存放对系统可执行的操作,如:增加检查项、删除检查项。
  5. 角色权限关联表(t_role_permission):我们要为不同角色授予不同的操作权限,角色和权限之间也是多对多的关系,故需要在角色表和权限表之间创建一张关联表。
  6. 菜单表(t_menu):存放不同的网页链接地址,如:会员管理页面、套餐设置页面、用户预约页面等。
  7. 角色菜单关联表(t_role_menu):为不同角色分配不同的页面,角色和页面之间也是多对多的关系,也需要一张关联表。

表之间关系如下图:

由上图可看出,在这7张表中,角色表起到了及其重要的作用,其位于核心位置,与用户表、权限表和菜单表都是多对多的关系。

我们可以分析一下在认证和授权过程中分别会使用到哪些表:

认证过程:只需要用户表即可,在用户登录时可以根据用户输入的用户名和密码来查询用户表,从而实现校验。

授权过程:用户必须完成认证后才能进行授权

  1. 首先可以根据用户查询其对应的角色。
  2. 再根据其角色查询对应的菜单,如此就确定了用户可以看到哪些菜单。
  3. 然后再根据其角色查询对应的权限,如此就确定了用户可执行的操作。

授权过程会用到上面的7张表。

2.Spring Security介绍

Spring Security是Spring提供的安全认证服务的框架。使用Spring Security可以帮助我们来简化认证和授权的过程。

官网:https://spring.io/projects/spring-security

对应的maven坐标:

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-web</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-config</artifactId>
  <version>5.0.5.RELEASE</version>
</dependency>

常用的权限控制框架除了Spring Security,还有Apache的shiro框架。

3.入门案例

3.1项目搭建

创建maven web工程spring-security-project,打包方式为war,然后在pom.xml中加入依赖。

pom.xml:

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.tsccg</groupId>
  <artifactId>springsecuritydemo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>war</packaging>

  <!-- 集中定义依赖版本号 -->
  <properties>
    <junit.version>4.12</junit.version>
    <spring.version>5.0.5.RELEASE</spring.version>
    <spring.security.version>5.0.5.RELEASE</spring.security.version>
      
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <!-- Spring -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-beans</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jms</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.47</version>
    </dependency>
    <!-- 安全框架 -->
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-web</artifactId>
      <version>${spring.security.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-config</artifactId>
      <version>${spring.security.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-taglibs</artifactId>
      <version>${spring.security.version}</version>
    </dependency>
    <dependency>
      <groupId>com.github.penggle</groupId>
      <artifactId>kaptcha</artifactId>
      <version>2.3.2</version>
      <exclusions>
        <exclusion>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>2.3.2</version>
    </dependency>
  </dependencies>
<!--  tomcat插件-->
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <configuration>
          <!-- 指定端口 -->
          <port>85</port>
          <!-- 请求路径 -->
          <path>/</path>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

3.2配置web.xml

在web.xml中主要配置SpringMVC的DispatcherServlet和用于整合第三方框架的DelegatingFilterProxy,用于整合Spring Security。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <display-name>Archetype Created Web Application</display-name>
    <!-- 1.注册Spring提供的DelegatingFilterProxy用于整合第三方框架 -->
    <filter>
        <!--
          DelegatingFilterProxy用于整合第三方框架
          整合Spring Security时过滤器的名称必须为springSecurityFilterChain,
          否则会抛出NoSuchBeanDefinitionException异常
        -->
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <!-- 2.注册SpringMVC的中央调度器   -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 指定加载的配置文件 ,通过参数contextConfigLocation加载 -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-security.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

3.3配置Spring-security.xml(重点理解)

在spring-security.xml中主要配置Spring Security的拦截规则和认证管理器。

<?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:context="http://www.springframework.org/schema/context"
       xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc.xsd
                        http://code.alibabatech.com/schema/dubbo
                        http://code.alibabatech.com/schema/dubbo/dubbo.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd
                          http://www.springframework.org/schema/security
                          http://www.springframework.org/schema/security/spring-security.xsd">

    <!--
        http:用于定义相关权限控制
        auto-config:是否自动配置
                        设置为true时框架会提供默认的一些配置,例如提供默认的登录页面、登出处理等
                        设置为false时需要显示提供登录表单配置,否则会报错
        use-expressions:用于指定intercept-url中的access属性是否使用表达式
    -->
    <security:http auto-config="true" use-expressions="true">
        <!--
            intercept-url:定义一个拦截规则
            pattern:对哪些url进行权限控制
            access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,
                  请求的用户只需拥有其中的一个角色就能成功访问对应的URL
        -->
        <security:intercept-url pattern="/**"  access="hasRole('ROLE_ADMIN')" />
    </security:http>

    <!--
        authentication-manager:认证管理器,用于处理认证操作
    -->
    <security:authentication-manager>
        <!--
            authentication-provider:认证提供者,执行具体的认证逻辑
        -->
        <security:authentication-provider>
            <!--
                user-service:用于获取用户信息,提供给authentication-provider进行认证
            -->
            <security:user-service>
                <!--
                    user:定义用户信息,可以指定用户名、密码、角色,后期可以改为从数据库查询用户信息
                  {noop}:表示当前使用的密码为明文
                -->
                <security:user name="admin" password="{noop}123456" authorities="ROLE_ADMIN"/>
            </security:user-service>
        </security:authentication-provider>
    </security:authentication-manager>
</beans>

3.4创建index.html

在webapp目录下创建一个index.html,只有当登录成功时,才能看到该页面。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    欢迎登录系统页面!
</body>
</html>

3.5入门案例测试

启动服务,在浏览器地址栏输入:http://localhost:85/index.html

点击回车后,浏览器并没有跳转到index.html页面,而是跳转到了一个login页面。这是Spring Security默认的登录页面。

输入错误的用户名和密码:

输入正确的用户名和密码:

4.对入门案例改进

分析:

在前面的入门案例中,我们可以看出,Spring Security将我们项目中所有的资源都保护了起来,要想访问这些资源必须要完成认证而且需要具有ROLE_ADMIN角色。

但是入门案例中使用的方法距离我们真实的生产环境还差的远,还存在如下一些问题:

  1. 不可匿名访问:项目中我们将所有的资源都保护了起来(拦截所有请求URL),但在实际环境下往往有一些资源不需要认证也能访问,也就是匿名访问。比如说我们自己写的登录页面。
  2. 没有使用指定的登录页面:登录页面是由框架生成的,而我们的项目往往会使用自己的登录页面。
  3. 没有动态地获取用户名密码:直接将用户名和密码配置在了配置文件中,而真实环境下的用户名和密码往往保存在数据库里。
  4. 密码为非加密的明文:在配置文件中配置的密码使用的是明文,非常不安全,在真实环境下需要对密码进行加密。

4.1配置可匿名访问的资源

第一步:在项目中创建pages目录,在pages目录中创建a.html和b.html

第二步:在spring-security.xml文件中配置,指定哪些资源可以匿名访问

<!--
    配置可匿名访问的资源
        http:用于定义相关权限控制
        指定哪些资源不需要进行权限校验,可以使用通配符
        配置可匿名访问的资源配置必须放在拦截所有请求的配置前面
    -->
<!--单个指定-->
<security:http security="none" pattern="/pages/a.html"/>
<security:http security="none" pattern="/pages/b.html"/>
<!--使用通配符-->
<security:http security="none" pattern="/pages/**"/>

第三步:测试

开启服务,在浏览器中访问pages目录下的a.html和b.html。以及被保护的index.html

4.2使用指定的登录页面

第一步:提供login.html作为项目的登录页面

在webapp目录下新建login.html页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
    <link rel="shortcut icon" href="#"/>
</head>
<body>
<center>
    <h2>自定义登录页面</h2>
    <form action="/login.do" method="post">
        用户名:<input type="text" name="username"><br>
        密码:<input type="password" name="password"><br>
        <input type="submit" value="登录">
    </form>
</center>
</body>
</html>

第二步:修改spring-security.xml文件,指定login.html页面可以被匿名访问

<!--设置自定义登录页面可匿名访问-->
<security:http security="none" pattern="/login.html"/>

第三步:修改spring-security.xml文件,配置自定义表单登录信息。位置:security:http中。

<security:form-login
                     login-page="/login.html"
                     username-parameter="username"
                     password-parameter="password"
                     login-processing-url="/login.do"
                     default-target-url="/index.html"
                     authentication-failure-url="/login.html"
                     />

form-login自定义表单登录信息:

  1. login-page:登录页面路径
  2. username-parameter:告诉Spring Security框架用户名输入框的name属性值
  3. password-parameter:告诉Spring Security框架密码输入框的name属性值
  4. login-processing-url:告诉框架要处理的登录请求url
  5. default-target-url:登录成功后,默认跳转的页面路径
  6. authentication-failure-url:登录失败后,跳转的页面路径

我们不需要自己写Controller来处理登录请求,Spring Security会处理。

第四步:修改spring-security.xml文件,关闭CsrfFilter过滤器。位置:security:http中。

<!--
      csrf:对应CsrfFilter过滤器
      disabled:是否启用CsrfFilter过滤器,
        如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)
    -->
<security:csrf disabled="true"/>

完整配置:

<!--设置自定义登录页面可匿名访问-->
<security:http security="none" pattern="/login.html"/>
<!--http:用于定义相关权限控制-->
<security:http auto-config="true" use-expressions="true">
    <security:intercept-url pattern="/**"  access="hasRole('ROLE_ADMIN')" />
    <!--
        指定使用自己写的登录页面:
        form-login自定义表单登录信息
    -->
    <security:form-login
            login-page="/login.html"
            username-parameter="username"
            password-parameter="password"
            login-processing-url="/login.do"
            default-target-url="/index.html"
            authentication-failure-url="/login.html"
    />
    <!--
      关闭CsrfFilter过滤器
    -->
    <security:csrf disabled="true"/>
</security:http>

第五步:测试

开启服务,通过浏览器访问index.html

输入错误用户名密码

输入正确用户名密码

4.3从数据库查询用户信息

4.3.1编写UserDetailsService接口的实现类

如果我们要从数据库动态查询用户信息,就必须按照spring security框架的要求提供一个实现UserDetailsService接口的实现类,并按照框架的要求进行配置即可。框架会自动调用实现类中的方法并自动进行密码校验。

实现类代码:

package com.tsccg.service;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: TSCCG
 * @Date: 2021/11/27 21:44
 */
public class UserService implements UserDetailsService {
    //模拟数据库用户信息
    private static Map<String, MyUser> userInDB = new HashMap<>();
    static {
        MyUser user1 = new MyUser("admin","123456");
        userInDB.put(user1.getUsername(),user1);
        MyUser user2 = new MyUser("lisi","123456");
        userInDB.put(user2.getUsername(),user2);
    }
    /**
     * 根据用户名查询用户信息
     * @param username 输入的用户名
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println(username);
        //1.根据用户名查询数据库获取用户信息,包括用户的密码信息
        MyUser myUser = userInDB.get(username);
        //如果根据用户名没有从数据库中查到信息,返回null,阻止登录
        if (myUser == null) {
            return null;
        }
        //框架要求传入加密后的密码,但此处没有对密码进行加密,故声明为明文
        String passwordInDB = "{noop}" + myUser.getPassword();
        //2.授权,此处为模拟授权,后期需要改为通过查询数据库动态获得用户拥有的权限和角色
        List<GrantedAuthority> list = new ArrayList<>();
        //授予当前用户添加权限
        list.add(new SimpleGrantedAuthority("PERMISSION_ADD"));
        //授予当前用户删除权限
        list.add(new SimpleGrantedAuthority("PERMISSION_DELETE"));
        //当用户名为”admin“时,授予该用户”ROLE_ADMIN“角色
        if ("admin".equals(username)) {
            list.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        }
        //3.将用户信息返回给框架,框架会将用户输入的用户名和密码与数据库中的数据进行比对
        User securityUser = new User(username,passwordInDB,list);//使用框架提供的User类
        return securityUser;
    }
}

/**
 * 实体类
 */
class MyUser {
    private String username;
    private String password;

    public MyUser() {
    }

    public MyUser(String username, String password) {
        this.username = username;
        this.password = password;
    }
    //get和set
}

4.3.2引用实现类

在spring配置文件中注册UserService,指定其作为认证过程中根据用户名查询用户信息的处理类。

spring-security.xml:

<!--注册UserService类-->
<bean id="userService" class="com.tsccg.service.UserService"/>
<!--authentication-manager:认证管理器,用于处理认证操作-->
<security:authentication-manager>
    <!--authentication-provider:认证提供者,执行具体的认证逻辑
	user-service-ref:引用实现UserDetailsService接口的实现类
	-->
    <security:authentication-provider user-service-ref="userService">
        <!--<security:user-service>
            <security:user name="admin" password="{noop}123456" authorities="ROLE_ADMIN"/>
        </security:user-service>-->
    </security:authentication-provider>
</security:authentication-manager>

4.3.3测试

前面我们提供了UserService实现类,并且按照框架的要求实现了UserDetailsService接口。

在spring配置文件中注册UserService,指定其作为认证过程中根据用户名查询用户信息的处理类。

当我们进行登录操作时,spring security框架会调用UserService的loadUserByUsername方法查询用户信息,并根据此方法中提供的密码和用户页面输入的密码进行比对来实现认证操作。

当用户名为"lisi"时,由于未授予其"ROLE_ADMIN"角色,故没有权限登录。

4.4对密码进行加密

前面我们使用的密码都是明文的,这是非常不安全的。一般情况下用户的密码需要进行加密后再保存到数据库中。

常见的密码加密方式有:

3DES、AES、DES:使用对称加密算法,可以通过解密来还原出原始密码

MD5、SHA1:使用单向HASH算法,无法通过计算还原出原始密码,但是可以建立彩虹表进行查表破解

bcrypt:将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题

加密后的格式一般为:

$2a$10$/bTVvqqlH9UiE0ZJZ7N2Me3RIgUCdgMheyTgV0B4cMCSokPa.6oCa

加密后字符串的长度为固定的60位。其中:$是分割符,无意义;2a是bcrypt加密版本号;10是cost的值;而后的前22位是salt值;再然后的字符串就是密码的密文了。

实现步骤:

第一步:在spring-security.xml文件中指定密码加密对象

<!--1.配置密码加密对象-->
<bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<!--注册UserService类-->
<bean id="userService" class="com.tsccg.service.UserService"/>
<!--authentication-manager:认证管理器,用于处理认证操作-->
<security:authentication-manager>
    <!--
        authentication-provider:认证提供者,执行具体的认证逻辑
        user-service-ref:引用实现UserDetailsService接口的实现类
        -->
    <security:authentication-provider user-service-ref="userService">
        <!--2.指定密码加密策略-->
        <security:password-encoder ref="bCryptPasswordEncoder"/>
    </security:authentication-provider>
</security:authentication-manager>
<!--3.开启Spring注解-->
<context:annotation-config/>

第二步:修改UserService实现类

package com.tsccg.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: TSCCG
 * @Date: 2021/11/27 21:44
 */
public class UserService implements UserDetailsService {
    //自动注入密码加密对象
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;
    //模拟数据库用户信息
    private Map<String, MyUser> userInDB = new HashMap<>();
    //初始化数据
    public void initUserData() {
        //对密码进行加密
        MyUser user1 = new MyUser("admin",passwordEncoder.encode("123456"));
        userInDB.put(user1.getUsername(),user1);
        MyUser user2 = new MyUser("lisi",passwordEncoder.encode("123456"));
        userInDB.put(user2.getUsername(),user2);
    }
    /**
     * 根据用户名查询用户
     * @param username 输入的用户名
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//        System.out.println(username);
        initUserData();//执行初始化数据方法
        //1.根据用户名查询数据库获取用户信息,包括用户的密码信息
        MyUser myUser = userInDB.get(username);
        //如果根据用户名没有从数据库中查到信息,返回null,阻止登录
        if (myUser == null) {
            return null;
        }
        //框架要求传入加密后的密码,这里的密码已经加密
        String passwordInDB = myUser.getPassword();
        //2.授权,此处为模拟授权,后期需要改为通过查询数据库动态获得用户拥有的权限和角色
        List<GrantedAuthority> list = new ArrayList<>();
        //授予用户添加权限
        list.add(new SimpleGrantedAuthority("PERMISSION_ADD"));
        //授予用户删除权限
        list.add(new SimpleGrantedAuthority("PERMISSION_DELETE"));
        //授予用户角色
        if ("admin".equals(username)) {
            list.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        }
        //3.将用户信息返回给框架,框架会将用户输入的用户名和密码与数据库中的数据进行比对
        User securityUser = new User(username,passwordInDB,list);//使用框架提供的User类
        return securityUser;
    }
}

/**
 * 实体类
 */
class MyUser {
    private String username;
    private String password;

    public MyUser() {
    }

    public MyUser(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

第三步:进行测试

4.5定义多种校验规则

为了测试方便,首先在项目中/webapp/pages目录下创建a.html、b.html、c.html、d.html几个页面

步骤一:修改spring-security.xml文件:

<security:http auto-config="true" use-expressions="true">
    <!--
        intercept-url:定义一个拦截规则
        pattern:对哪些url进行权限控制
        access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,
              请求的用户只需拥有其中的一个角色就能成功访问对应的URL
    -->
    <!--1.只要认证通过就可以访问-->
    <security:intercept-url pattern="/pages/a.html" access="isAuthenticated()"/>

    <!--2.拥有add权限就可以访问b.html页面-->
    <security:intercept-url pattern="/pages/b.html" access="hasAuthority('add')"/>

    <!--3.拥有ROLE_ADMIN角色就可以访问c.html页面-->
    <security:intercept-url pattern="/pages/c.html" access="hasRole('ROLE_ADMIN')"/>

    <!--4.拥有ROLE_ADMIN角色就可以访问d.html页面,
        注意:此处虽然写的是ADMIN角色,框架会自动加上前缀ROLE_-->
    <security:intercept-url pattern="/pages/d.html" access="hasRole('ADMIN')"/>

    <security:intercept-url pattern="/**"  access="hasRole('ROLE_ADMIN')" />

    <security:form-login
            login-page="/login.html"
            username-parameter="username"
            password-parameter="password"
            login-processing-url="/login.do"
            default-target-url="/index.html"
            authentication-failure-url="/login.html"
    />
    <security:csrf disabled="true"/>
</security:http>

步骤二:修改修改UserService实现类

当用户名为admin时,为其添加add权限

步骤3:测试

当用户名为lisi时:

lisi只能访问a.html。

当用户名为admin时:

admin可访问所有页面。

4.6注解方式权限控制(方法级别-常用)

Spring Security除了可以在配置文件中配置权限校验规则,还可以使用注解方式控制类中方法的调用。

例如Controller中的某个方法要求必须具有某个权限才可以访问,此时就可以使用Spring Security框架提供的注解方式进行控制。

实现步骤:

第一步:在spring-security.xml文件中添加校验规则,只要认证通过即可访问以/user/*结尾的请求

<!--只要认证通过就可以访问以/user/*结尾的请求-->
<security:intercept-url pattern="/user/*" access="isAuthenticated()"/>

第二步:在spring-security.xml文件中配置组件扫描,用于扫描Controller

<!--配置注解驱动-->
<mvc:annotation-driven/>
<!--配置组件扫描器,扫描Controller-->
<context:component-scan base-package="com.tsccg.controller"/>

第三步:在spring-security.xml文件中开启权限注解支持

<!--开启注解方式权限控制-->
<security:global-method-security pre-post-annotations="enabled"/>

第四步:创建Controller类并在处理器方法上加入注解进行权限控制

package com.tsccg.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Author: TSCCG
 * @Date: 2021/11/28 16:10
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/find")//无需权限即可访问
    public String find() {
        System.out.println("find...");
        return "find success...";
    }
    @RequestMapping("/add")
    @PreAuthorize("hasAuthority('add')")//表示用户必须拥有add权限才能访问该方法
    public String add() {
        System.out.println("add...");
        return "add success...";
    }
    @RequestMapping("/delete")
    @PreAuthorize("hasRole('ROLE_ADMIN')")//用户必须拥有ROLE_ADMIN角色才能访问该方法
    public String delete() {
        System.out.println("delete...");
        return "delete success...";
    }
}

第五步:测试

①用户名为:lisi

lisi没有add权限和ROLE_ADMIN角色,只能访问find方法。

②用户名为admin

admin有add权限以及ROLE_ADMIN角色,所有方法都可以访问

4.7退出登录

用户完成登录后,Spring Security框架会记录当前用户认证状态为已认证状态,即表示用户登录成功了。但用户如何退出登录呢?我们可以在spring-security.xml文件中进行如下配置:

<security:http 中

<!--
   退出登录
       logout-url:退出登录操作对应的请求路径
       logout-success-url:退出登录后跳转的页面
       invalidate-session:销毁session
-->
<security:logout logout-url="/logout.do"
                 logout-success-url="/login.html"
                 invalidate-session="true"
                 />

测试:

4.8spring-security.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:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/mvc
                        http://www.springframework.org/schema/mvc/spring-mvc.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context.xsd
                          http://www.springframework.org/schema/security
                          http://www.springframework.org/schema/security/spring-security.xsd">

    <!--
    配置可匿名访问的资源
        http:用于定义相关权限控制
        指定哪些资源不需要进行权限校验,可以使用通配符
        配置可匿名访问的资源配置必须放在拦截所有请求的配置前面
    -->
    <!--单个指定-->
<!--    <security:http security="none" pattern="/pages/a.html"/>-->
<!--    <security:http security="none" pattern="/pages/b.html"/>-->
    <!--使用通配符-->
<!--    <security:http security="none" pattern="/pages/**"/>-->
    <!--设置自定义登录页面可匿名访问-->
    <security:http security="none" pattern="/login.html"/>
    <!--
        http:用于定义相关权限控制
        auto-config:是否自动配置
                        设置为true时框架会提供默认的一些配置,例如提供默认的登录页面、登出处理等
                        设置为false时需要显示提供登录表单配置,否则会报错
        use-expressions:用于指定intercept-url中的access属性是否使用表达式
    -->
    <security:http auto-config="true" use-expressions="true">
        <!--
            intercept-url:定义一个拦截规则
            pattern:对哪些url进行权限控制
            access:在请求对应的URL时需要什么权限,默认配置时它应该是一个以逗号分隔的角色列表,
                  请求的用户只需拥有其中的一个角色就能成功访问对应的URL
        -->
        <!--1.只要认证通过就可以访问-->
        <security:intercept-url pattern="/pages/a.html" access="isAuthenticated()"/>

        <!--2.拥有add权限就可以访问b.html页面-->
        <security:intercept-url pattern="/pages/b.html" access="hasAuthority('add')"/>

        <!--3.拥有ROLE_ADMIN角色就可以访问c.html页面-->
        <security:intercept-url pattern="/pages/c.html" access="hasRole('ROLE_ADMIN')"/>

        <!--4.拥有ROLE_ADMIN角色就可以访问d.html页面,
            注意:此处虽然写的是ADMIN角色,框架会自动加上前缀ROLE_-->
        <security:intercept-url pattern="/pages/d.html" access="hasRole('ADMIN')"/>

        <!--只要认证通过就可以访问以/user/*结尾的请求-->
        <security:intercept-url pattern="/user/*" access="isAuthenticated()"/>

        <security:intercept-url pattern="/**"  access="hasRole('ROLE_ADMIN')" />
        <!--
            指定使用自己写的登录页面:
            form-login自定义表单登录信息
                login-page:登录页面路径
                username-parameter:告诉Spring Security框架用户名输入框的name属性值
                password-parameter:告诉Spring Security框架密码输入框的name属性值
                login-processing-url:告诉框架要处理的登录请求url
                default-target-url:登录成功后,默认跳转的页面路径
                authentication-failure-handler-ref:登录失败后,跳转的页面路径
            我们不需要自己写Controller来处理登录请求,Spring Security会自己处理
        -->
        <security:form-login
                login-page="/login.html"
                username-parameter="username"
                password-parameter="password"
                login-processing-url="/login.do"
                default-target-url="/index.html"
                authentication-failure-url="/login.html"
        />
        <!--
          csrf:对应CsrfFilter过滤器
          disabled:是否启用CsrfFilter过滤器,
            如果使用自定义登录页面需要关闭此项,否则登录操作会被禁用(403)
        -->
        <security:csrf disabled="true"/>
        <!--
        退出登录
            logout-url:退出登录操作对应的请求路径
             logout-success-url:退出登录后跳转的页面
             invalidate-session:销毁session
        -->
        <security:logout logout-url="/logout.do"
                         logout-success-url="/login.html"
                         invalidate-session="true"
        />
        <!--设置在页面可以通过iframe访问受保护的页面,默认为不允许访问-->
        <security:headers>
            <security:frame-options policy="SAMEORIGIN"/>
        </security:headers>
    </security:http>

    <!--注册UserService类-->
    <bean id="userService" class="com.tsccg.service.UserService"/>
    <!--1.配置密码加密对象-->
    <bean id="bCryptPasswordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
    <!--authentication-manager:认证管理器,用于处理认证操作-->
    <security:authentication-manager>
        <!--
        authentication-provider:认证提供者,执行具体的认证逻辑
        user-service-ref:引用实现UserDetailsService接口的实现类
        -->
        <security:authentication-provider user-service-ref="userService">
            <!--2.指定密码加密策略-->
            <security:password-encoder ref="bCryptPasswordEncoder"/>
            <!--
                user-service:用于获取用户信息,提供给authentication-provider进行认证
            -->
            <!--<security:user-service>
                &lt;!&ndash;
                    user:定义用户信息,可以指定用户名、密码、角色,后期可以改为从数据库查询用户信息
                  {noop}:表示当前使用的密码为明文
                &ndash;&gt;
                <security:user name="admin" password="{noop}123456" authorities="ROLE_ADMIN"/>
            </security:user-service>-->
        </security:authentication-provider>
    </security:authentication-manager>
    <!--3.开启Spring注解-->
    <context:annotation-config/>

    <!--配置注解驱动-->
    <mvc:annotation-driven/>
    <!--配置注解扫描器,扫描Controller-->
    <context:component-scan base-package="com.tsccg.controller"/>
    <!--开启注解方式权限控制-->
    <security:global-method-security pre-post-annotations="enabled"/>
</beans>
posted @ 2021-11-28 19:17  TSCCG  阅读(204)  评论(0编辑  收藏  举报