SpringBoot学习笔记(二)—— Spring Security

Spring Security

此部分内容参考【狂神说 Java】SpringBoot 最新教程 IDEA 版通俗易懂[1]以及学习笔记[2],仅供学习使用!

1、Spring Security 简介

1、在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。因此,从应用开发的第一天就应该把安全相关的因素考虑进来,并在整个应用的开发过程中。

2、市面上存在比较有名的:Shiro,Spring Security !

3、首先我们看下它的官网介绍:Spring Security 官网地址[3]

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 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 requirements

4、从官网的介绍中可以知道这是一个权限框架。想我们之前做项目是没有使用框架是怎么控制权限的?对于权限 一般会细分为功能权限,访问权限,和菜单权限。代码会写的非常的繁琐,冗余。

5、Spring 是一个非常流行和成功的 Java 应用开发框架。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。

  • 用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
  • 用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

6、对于上面提到的两种应用情景,Spring Security 框架都有很好的支持。

  • 在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。
  • 在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

2、实战测试-环境搭建

接下来,我们通过一个简单的 demo 来 一下如何使用 Spring Security 做认证和授权。

2.1、创建项目

新建 springboot 项目springboot-07-security,选择 web、thymeleaf、security 模块

2.2、导入静态资源

SpringBoot学习笔记(二)—— Spring Security

这样测试环境就准备好了。

3、编写 controller

RouteController.java

package com.thomas.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class RouteController {
    @RequestMapping({"/""/index""/index.html"})
    public String index() {
        return "index";
    }

    //跳转到login页面
    @RequestMapping("/toLogin")
    public String toLogin() {
        return "views/login";
    }

    //level1相关页面:对应/level1/1 /level1/2 /level1/3
    @RequestMapping("/level1/{id}")
    public String level1(@PathVariable("id") int id) {
        return "views/level1/" + id;
    }

    //level2相关页面:对应/level2/1 /level2/2 /level2/3
    @RequestMapping("/level2/{id}")
    public String level2(@PathVariable("id") int id) {
        return "views/level2/" + id;
    }

    //level3相关页面:对应/level3/1 /level3/2 /level3/3
    @RequestMapping("/level3/{id}")
    public String level3(@PathVariable("id") int id) {
        return "views/level3/" + id;
    }
 }

因为没有添加授权验证,所以用户可以访问 vip 页面。

SpringBoot学习笔记(二)—— Spring Security

4、认证和授权

4.1、 WebSecurityConfigurerAdapter

继承WebSecurityConfigurerAdapter,重写configure方法

spring security5.7之前,可以通过继承该类来实现认证和授权。做出如下设置:

  • 首页允许所有人访问
  • level1 的所有页面只能 vip1 访问
  • level2 的所有页面只能 vip2 访问
  • level3 的所有页面只能 vip3 访问
package com.thomas.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 首页所有人都可以访问,功能也只有对应有权限的人才能访问到
        // 请求授权的规则
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");
    }
}

测试发现,除了首页,其他页面都进不去,因为我们还没有授权,这里我们先暂时优化一下。

SpringBoot学习笔记(二)—— Spring Security

优化 1:重定向到登录页面。在上面的configure方法中添加以下代码,开启spring security自动配置的登录功能。

// 开启自动配置的登录功能
// /login 请求来到登录页
// /login?error 重定向到这里表示登录失败
http.formLogin();

再次测试,重定向到登录页面。

SpringBoot学习笔记(二)—— Spring Security

在启动项目的时候,Spring security 会提供一个默认用户,用户名为user,而密码会在控制台中打印出来。

SpringBoot学习笔记(二)—— Spring Security

我们接着在配置类里设置认证,对不同的角色给予不同的权限:

  • 用户 thomas 只有 vip1 的权限,因此也只能访问 level1 下的所有页面
  • 用户 root 有 vip1、vip2、vip3 的权限,因此能够访问所有的页面
  • 用户 guest 只有 vip3 的权限,因此只能访问 level3 下的所有页面
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //从内存中读取,也可以使用jdbc从数据库中读取
    auth.inMemoryAuthentication().withUser("thomas").password("123456").roles("vip1")
        .and()
        .withUser("root").password("123456").roles("vip1""vip2""vip3")
        .and()
        .withUser("guest").password("123456").roles("vip3");
}

报错!There is no PasswordEncoder mapped for the id "null"

SpringBoot学习笔记(二)—— Spring Security

原因就是没有对密码进行加密,我们将密码进行加密

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("thomas").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1")
        .and()
        .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1""vip2""vip3")
        .and()
        .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip3");
}

测试,发现用户只能访问指定的页面,权限设置完成。

1. 注销及权限控制

注销

注销的话,实际上在configure方法中添加一行http.logout()就可以实现。看看源码的注释

SpringBoot学习笔记(二)—— Spring Security

其实就是把用户的 session 直接清除。此外前端也需要做相应的配置,增加一个退出按钮,并且绑定th:href="@{/logout}"

SpringBoot学习笔记(二)—— Spring Security

点击 logout,就可以成功退出。

SpringBoot学习笔记(二)—— Spring Security

但是现在还有点问题,我们注销一般需要跳到首页,我们可以指定http.logout().logoutSuccessUrl("/");,这样就可以在注销登录后跳转到首页。

权限控制

正常来说,用户只能看到自己当前能看到的页面,所以我们还需要做权限控制。首先需要显示当前用户名和角色。

这里需要导入security-thymeleaf的整合包

<!-- security-thymeleaf整合包 -->
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
    <version>3.0.4.RELEASE</version>
</dependency>

在 html 中,我们可以使用引入对应的命名空间,然后根据客户登录状态,渲染出不同的标签。

  • sec:authorize="isAuthenticated()"判断登录状态
  • sec:authentication="principal.username" 获取当前登录用户名
  • sec:authentication="principal.authorities"获取当前登录用户角色,这个角色在权限验证的时候赋予。
<html lang="en" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">


<!--登录注销-->
<div class="right menu">

    <!--如果未登录-->
    <div sec:authorize="!isAuthenticated()">
        <a class="item" th:href="@{/toLogin}">
            <i class="address card icon"></i> 登录
        </a>
    </div>

    <!--如果已登录-->
    <div sec:authorize="isAuthenticated()">
        <a class="item">
            <i class="address card icon"></i>
            用户名:<span sec:authentication="principal.username"></span>
            角色:<span sec:authentication="principal.authorities"></span>
        </a>
    </div>

    <div sec:authorize="isAuthenticated()">
        <a class="item" th:href="@{/logout}">
            <i class="sign-out  icon"></i> 注销
        </a>
    </div>
</div>

这样在首页就可以正确显示当前登录用户名和角色名。

SpringBoot学习笔记(二)—— Spring Security

接下来还需要设置菜单根据用户的角色动态实现

以 vip1 为例。

<div class="column"  sec:authorize="hasRole('vip1')">
    <div class="ui raised segment">
        <div class="ui">
            <div class="content">
                <h5 class="content">Level 1</h5>
                <hr>
                <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
            </div>
        </div>
    </div>
</div>

显示如下,只显示 vip1 能看到的页面。

SpringBoot学习笔记(二)—— Spring Security

2. 记住我及登录页面定制

记住我

在很多网站,都会有Remember me的选项,用户登录一次后,只要点击记住,下次就可以自动登录(服务没有重新启动,只是页面被关闭的情况下)。这里实现很简单,还是在configure认证方法里,加入http.rememberMe().rememberMeParameter("remember");,这个参数remember要与前端的单选框中的 name 属性保持一致

SpringBoot学习笔记(二)—— Spring SecuritySpringBoot学习笔记(二)—— Spring Security

登录后,我们 F12 查看元素,可以看到 cookie 中多增加了一个remember-me的键值对,而且其有效期为 14 天。

SpringBoot学习笔记(二)—— Spring Security

登录页面定制

之前我们登录的时候,默认会使用 security 自带的页面进行登录验证。这个页面当然也是可以定制的。还是在config方法中定义http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login").defaultSuccessUrl("/");。这样我们在登录的时候可以跳转到我们自己定义的页面。

  • loginPage是我们需要登录的页面
  • loginProcessingUrl是进行登录操作的页面
  • defaultSuccessUrl是完成登录操作后默认跳转的页面

这里需要注意:/login请求实际上是通过 form 表单提交的 post 请求,跟上面的 remember me 一样,这里的参数也是需要对应的。

<form th:action="@{/login}" method="post">
    <div class="field">
        <label>Username</label>
        <div class="ui left icon input">
            <input type="text" placeholder="Username" name="username">
            <i class="user icon"></i>
        </div>
    </div>
    <div class="field">
        <label>Password</label>
        <div class="ui left icon input">
            <input type="password" name="password">
            <i class="lock icon"></i>
        </div>
    </div>
    <div class="field">
        <input type="checkbox" name="remember"> 记住我
    </div>
    <input type="submit" class="ui blue submit button"/>
</form>

==源码==

在源码中,对应到的字段其实就是usernamepassword,因此在表单中,两个提交的参数的 name 属性要与对应的usernameParameterpasswordParameter保持一致,或者我们可以通过指定具体的值(当然这个值也要与 form 表单中的 nam 属性保持一致)

SpringBoot学习笔记(二)—— Spring Security

一个小问题:在注销的时候,出现以下报错:

SpringBoot学习笔记(二)—— Spring Security

查看审查元素,可以看到这里应该是跨域请求的问题,我们可以先粗暴的直接关闭 csrf 功能。

http.csrf().disable();

以上就是关于使用 spring-security 进行认证、授权等一系列操作。

前面我们也说过,在 spring-security5.7 以上的版本中,“

4.2、==securityFilterChain==

实际上,当我们查看WebSecurityConfigurerAdapter的源码时,会提示我们这个类要被废弃。。。

SpringBoot学习笔记(二)—— Spring Security

我们查看提供的网址https://spring.io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter,官方推荐的写法如下,注册一个SecurityFilterChain Bean 完成认证。

SpringBoot学习笔记(二)—— Spring Security

那如何进行授权呢?我们继续往下看文档,这里就提示了关于授权的内容,我们还是用内存加载的方法。同样的,我们也需要注册一个对应的 bean

SpringBoot学习笔记(二)—— Spring Security

这里可以看到,InMemorylUserDetialsManager也是实现了UserDetailService这个接口,所以我们其实直接返回UserDetailService也可以的。

SpringBoot学习笔记(二)—— Spring Security

因为前面也提到了,我们的密码是需要进行加密的,这也是 spring security5.x 版本以来强制要求的。但是官方的写法中,withDefaultPasswordEncoder其实是不安全而且也不推荐的。

SpringBoot学习笔记(二)—— Spring Security

我们需要自己注册一个PasswordEncoder bean,然后针对具体的代码使用加密方法。

关于认证和授权部分的代码,如下所示:

package com.thomas.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;


@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/").permitAll()
                .antMatchers("/level1/**").hasRole("vip1")
                .antMatchers("/level2/**").hasRole("vip2")
                .antMatchers("/level3/**").hasRole("vip3");

       http.formLogin().loginPage("/toLogin").loginProcessingUrl("/login").defaultSuccessUrl("/");
        http.csrf().disable();
        http.logout().logoutSuccessUrl("/");
        http.rememberMe().rememberMeParameter("remember");
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        UserDetails user_thomas = User.builder()
                .username("thomas")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("vip3")
                .build();
        UserDetails user_root = User.builder()
                .username("root")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("vip1""vip2""vip3")
                .build();
        UserDetails user_guest = User.builder()
                .username("guest")
                .password(new BCryptPasswordEncoder().encode("123456"))
                .roles("vip1")
                .build();
        return new InMemoryUserDetailsManager(user_thomas, user_root, user_guest);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

最后我们再测试以下:

用户 thomas,只能访问 vip3 页面。

SpringBoot学习笔记(二)—— Spring Security
image-20230422105205501

用户 guest,只能访问 vip1 页面

SpringBoot学习笔记(二)—— Spring Security
image-20230422105241465

用户 root,三个页面均能访问

SpringBoot学习笔记(二)—— Spring Security

测试通过!

小结

以上就是关于 Spring Security 的全部内容。Spring Security 集成了认证和授权的功能,这使得我们可以不需要像之前那样通过拦截器就可以实现复杂的权限验证的功能,之后我将继续学习 Shiro,敬请期待~

参考资料

[1]

【狂神说 Java】SpringBoot 最新教程 IDEA 版通俗易懂: https://www.bilibili.com/video/BV1PE411i7CV/?vd_source=f5b96c50144e9d1797a8c9056fefba8a

[2]

学习笔记: https://github.com/lzh66666/SpringBoot

[3]

Spring Security 官网地址: https://docs.spring.io/spring-security/reference/index.html


原文始发于微信公众号(多肉罗罗):SpringBoot学习笔记(二)—— Spring Security

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/186230.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!