Spring Security 的介绍和简单使用

Spring Security 的介绍

简介

平时我们写 Web 项目,都需要用户登录时验证,以及权限管理之类的操作,以前使用过滤器,拦截器等进行管理,原生代码较多。

所以出现了安全框架以供我们使用,安全框架在 Web 应用的主要功能是:

  • 认证
  • 授权

使用的较多的安全框架有两个:shiro、Spring Security

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

两个框架的主要功能相差不大,核心功能依旧是:认证、授权

Spring Security 的几个重要类:

  • WebSecurityConfigurerAdapter:自定义 Sercurity 策略
  • AuthenticationManagerBuilfer:自定义认证策略
  • @EnableWebSecurity:开启 WebSecurity 模式

SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。

快速使用

使用 Spring Boot 集成 Spring Security 结合 Web 应用。

Web 应用准备:

image-20221114162032521

三个等级,不同等级访问不同的区域。

在 maven 项目中导入 Spring Security 的依赖:

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

查看 maven 项目的各个依赖版本:Spring Boot:2.2.1,Spring Security:5.2.1

image-20221114162256579

开始使用 Spring Security 实现授权和认证效果:

// 自定义 Spring Security 的策略
// 需要继承 WebSecurityConfigurerAdapter,作用是 --> 启用 Web 安全
@EnableWebSecurity // 该注解包含了 @Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // 采用的是链式编程
    
    // 授权
    // 请求授权的规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()    // 授权请求,那些请求需要授权
                .antMatchers("/").permitAll() // 规定首页所有人都能请求
                .antMatchers("/level1/**").hasRole("vip1") // /level1/** 请求,只能 vip1 角色可以访问
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
        // 没有权限会跳到登录页面:源码默认是 /login
        http.formLogin();
    }

    // 认证
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 添加用户至内存中,内存中身份验证

        auth.inMemoryAuthentication()
                // 创建用户名为:zhangSan,密码为 123,角色是 vip1 和 vip2
                .withUser("zhangSan").password("123").roles("vip1","vip2")
                .and() // and() 来衔接不同的用户。
                // 创建用户名为:root,密码为 root,角色是 vip1、vip2、vip3
                .withUser("root").password("root").roles("vip1","vip2","vip3")
                // 创建用户名为:user,密码为 123,角色是 vip1
                .and()
                .withUser("user").password("123").roles("vip1");
        // 使用 数据库中的数据进行身份验证
        // auth.jdbcAuthentication();
    }
}

授权方法中 http.formLogin(); 源码的注释。

image-20221114164451219

用户登录界面:Spring Security 默认的登录界面,如有需要可以手动修改路径,http.loginPage("url")

image-20221114165504915

登录成功,但是服务器报错:

image-20221114165840898

此时需要对认证部分的代码每个用户的密码进行加密处理。

// 认证
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    // auth.passwordEncoder() 指定加密方式。Security 5.0+ 提供了很多加密方式
    auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
        .withUser("zhangSan").password(new BCryptPasswordEncoder().encode("123")).roles("vip1","vip2")
        .and()
        .withUser("root").password(new BCryptPasswordEncoder().encode("root")).roles("vip1","vip2","vip3")
        .and()
        .withUser("user").password(new BCryptPasswordEncoder().encode("123")).roles("vip1");
}

登陆过后:就会按照上诉的定义的规则,Spring Security 帮我们进行授权和认证操作。

  • zhangSan:vip1、vip2
  • root:vip1、vip2、vip3
  • user:vip1

每个用户只能根据自己的角色请求授权的资源(请求)。

注销

加入用户注销效果:

既然是注销,肯定是授权的一些操作了。

// 开启注销功能
http.logout().logoutSuccessUrl("/"); // 规定注销成功后,重定向到 / 

image-20221114172650480

上图标识了一些注意事项和例子,在前端只要发起/logout 请求即可将用户注销,在代码中还能清除 Cookie 或 Session 等后续操作。

<a href="/logout">注销</a>

权限控制

通过上面的代码,一个简单的 Web 应用授权和认证就完成了,只是没有跟网页进行联动,可能不同的角色或用户,在同一个页面看到的菜单栏显示不一样,根据角色的权限不同看到的内容也不同。

所以接下来进行对角色进行权限控制。

如果需要网页根据登录用户的角色进行动态显示内容,则需要 Security 和 thymeleaf 的整合包。

整合包与 Spring Boot 的版本有关:

Spring Boot 2.1.0+ 版本,

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

html 页面引入

xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"
<!-- 如果上面的出现不了提示,尝试使用下面这个引入 -->
xmlns:sec="http://www.thymeleaf.org/extras/spring-security"

Spring Boot 2.0.9- 版本,

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

html 页面引入

xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"

此处 Spring Boot 的版本是 2.2.1,导入 thymeleaf-extras-springsecurity5 依赖,在前端页面进行标签设置。

<html lang="en" xmlns:th="http://www.thymeleaf.org" 
      xmlns:sec="http://www.thymeleaf.org/extras/spring-security">

已有用户登录才显示注销,反之不显示

<!-- sec:authorize="isAuthenticated()" 用的是整合包中的标签,isAuthenticated 判断是否经过了身份验证,返回 boolean 值。 -->
<div sec:authorize="isAuthenticated()">
    <!-- 这里的 /logout 请求的是 security 的注销功能 -->
    <a href="/logout">注销</a> 
</div>
 	<!-- 这里的 /login 请求的是 security 的登录功能 -->
    <a href="/login">登录</a>

前端具体代码

<body>
<!-- 如果未登录才显示 -->
<div sec:authorize="!isAuthenticated()">
    <a href="/login">登录</a>
</div>
    
<!-- 如果已登录才显示注销 -->
<div sec:authorize="isAuthenticated()">
    <a href="/logout">注销</a>

    <!-- 显示登录用户和角色 -->
    <span sec:authentication="name"></span>
    <span>,你好你的角色有:</span>
    <span sec:authentication="principal.authorities"></span>

    <div class="div1">

        <!-- 如果当前用户角色是 vip1,才显示 -->
        <div class="div2" sec:authorize="hasRole('vip1')">
            <h3>level 1</h3>
            <ul>
                <li><a th:href="@{level1/1}">level1-1</a></li>
                <li><a th:href="@{level1/2}">level1-2</a></li>
                <li><a th:href="@{level1/3}">level1-3</a></li>
            </ul>
        </div>

        <!-- 如果当前用户角色是 vip2,才显示 -->
        <div class="div2" sec:authorize="hasRole('vip2')">
            <h3>level 2</h3>
            <ul>
                <li><a th:href="@{level2/1}">level2-1</a></li>
                <li><a th:href="@{level2/2}">level2-2</a></li>
                <li><a th:href="@{level2/3}">level2-3</a></li>
            </ul>
        </div>

        <!-- 如果当前用户角色是 vip3,才显示 -->
        <div class="div2" sec:authorize="hasRole('vip3')">
            <h3>level 3</h3>
            <ul>
                <li><a th:href="@{level3/1}">level3-1</a></li>
                <li><a th:href="@{level3/2}">level3-2</a></li>
                <li><a th:href="@{level3/3}">level3-3</a></li>
            </ul>
        </div>
    </div>
</div>

看到具体的整合包部分标签:

  • sec:authorize="isAuthenticated()"

    isAuthenticated 判断是否经过了身份验证,返回 boolean 值。

  • sec:authentication="name"

    获取当前用户的用户名。

  • sec:authentication="principal.authorities"

    获取当前用户的所有角色。

  • sec:authorize="hasRole('role')"

    只有用户角色包括 role 才会显示。

效果截图:

image-20221115104640120

登录 zhangSang 用户:

image-20221115104758298

登录 root 用户:

image-20221115104853541

登录 user 用户:

image-20221115104906647

到此,根据用户的角色动态显示内容功能简单实现。

自定义登录界面和开启 RememberMe 功能

开启 RememberMe 功能。

http.rememberMe();

点击登录就有:

image-20221115110719770

这样就有了 remember me 功能,默认是 14 天时间。

自定义登录界面

login.html

<form th:action="@{/toLogin}" method="post">
    <input type="text" name="username" placeholder="用户名:">
    <input type="password" name="password" placeholder="密码:">
    <input type="checkbox"  name="remember"> 记住我
    <input type="submit" value="登录">
</form>

<!-- 登录连接跳转 -->
<a th:href="@{/toLogin}">登录</a>

修改登录页面的 url:

http.formLogin().loginPage("/toLogin");

image-20221115112001034

image-20221115104758298

登录成功。

这个项目中并没有实现对 toLogin 的登录验证实现,从头到尾都是 Spring Security 框架帮我们进行登录验证的处理,用户就是在内存中写的用户。所以我们修改了两处地方:

  1. 准备一个登录界面(login.html)和对于的 Controller (/toLogin),如果是 /login,则会被 Security 拦截直接走默认的 login 页面
  2. 修改登录页面的 url:http.formLogin().loginPage("/toLogin");
  3. 修改登录界面的表单提交的 action:/toLogin

按照这个步骤结合我们以前写的用户验证过程,大概原理应该是 Security 根据 loginPage 指定的登录界面进行拦截处理,并且帮我们提取表单参数从而进行一系列操作。

关键是 loginPage 的 url 与 表单提交的 action 保持一致,这样 Security 才能够拦截成功,经过多次尝试,只要 Controller 中有映射,loginPage 就可以指定该映射,效果一样。

那么帮我们进行表单验证的到底是谁呢?如果没有其他设置的话,默认帮我们处理的还是之前的那个页面表单提交的 url

这里就引出一个问题:自定义的表单提交的参数是不是需要固定?

默认情况下 Security 的表单验证的部分固定参数可以在源码中看到:

  • 用户名:username
  • 密码:password
  • 记住我选项:remember

如果表单的 name 参数不一致,需要在 Security 配置中进行指定:

// 没有权限会跳到登录页面:源码默认是 /login
http.formLogin()
    .loginPage("/to")
    .loginProcessingUrl("/login")
    .usernameParameter("user")
    .passwordParameter("pwd");

// 开启 rememberMe 功能
http.rememberMe()
    .rememberMeParameter("rem");

那么:loginProcessingUrl() 这一配置作用是什么?顾名思义 login 请求发送到指定 URL。

如果 loginPage 与表单提交action 指定的 URL 不一致,则loginProcessingUrl()的 URL 与 action URL

保持一致,这样就可以将表单进行拦截验证。

小结

学习了 Spring Security 的基本使用,以及和 Thymeleaf 进行整合的使用。

  • 实现 WebSecurityConfigurerAdapter 类,自定义 Security 的一些安全策略
  • 重写两个方法,configure() ,一个授权,一个认证
  • 整合 Thymeleaf 进行前端的设计,根据配置进行权限控制。
  • 以及一些整合时的注意事项和细节

其主要通过 AOP 进行授权和认证功能的切入。

而后学习的 Shiro 也跟 Security 有相似之处。

posted @ 2022-11-15 16:09  CN_DADA  阅读(212)  评论(0编辑  收藏  举报