SpringSecurity

Spring Security

一、 Spring Security 简介

1 概括

Spring Security 是一个高度自定义的 安全框架。利用 Spring IoC/DI和 AOP 功能,为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。
使用 Spring Secruity 的原因有很多,但大部分都是发现了 javaEE的 Servlet 规范或 EJB 规范中的安全功能缺乏典型企业应用场景。同时认识到他们在 WAR 或 EAR 级别无法移植。因此如果你更换服务器环境,还有大量工作去重新配置你的应用程序。使用 Spring Security解决了这些问题,也为你提供许多其他有用的、可定制的安全功能。
正如你可能知道的两个应用程序的两个主要区域是“ 认证”和“ 授权”(或者访问控制)。这两点也是 Spring Security 重要核心功能。“认证”,是建立一个他声明的主体的过程(一个“主体”一般是指用户,设备或一些可以在你的应用程序中执行动作的其他系统),通俗点说就是系统认为用户是否能登录。“授权”指确定一个主体是否允许在你的应用程序执行一个动作的过程。通俗点讲就是系统判断用户是否有权限去做某些事情。

2 历史

Spring Security 以“The Acegi Secutity System for Spring” 的名字始于 2003 年年底。其前身为 acegi 项目。起因是 Spring 开发者邮件列表中一个问题,有人提问是否考虑提供一个基于 Spring 的安全实现。限制于时间问题,开发出了一个简单的安全实现,但是并没有深入研究。几周后,Spring 社区中其他成员同样询问了安全问题,代码提供给了这些人。2004 年 1 月份已经有 20 人左右使用这个项目。随着更多人的加入,在 2004 年 3 月左右在 sourceforge 中建立了一个项目。在最开始并没有认证模块,所有的认证功能都是依赖容器完成的,而 acegi 则注重授权。但是随着更多人的使用,基于容器的认证就显现出了不足。acegi 中也加入了认证功能。大约 1 年后 acegi 成为 Spring子项目。
在 2006 年 5 月发布了 acegi 1.0.0 版本。2007 年底 acegi 更名为Spring Security。

二、第一个 Spring Security 项目

在这里插入图片描述

在这里插入图片描述

1 导入依赖

Spring Security 已经被 Spring boot 进行集成,使用时直接引入启动器即可。

<?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.bjsxt</groupId>
    <artifactId>SpringSecurityDemo</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

    </dependencies>
</project>
package com.bjsxt.controller;

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

@Controller
public class LoginController {
   
    @RequestMapping("/login")
    public String login() {
   
        System.out.println("执行login方法");
        return "redirect:main.html";
    }

}
package com.bjsxt;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MyApp {
   
    public static void main(String[] args) {
   
        SpringApplication.run(MyApp.class, args);
    }
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password"/><br/>
    <input type="submit" value="OK">
</form>
</body>
</html>

2 访问页面

导入 spring-boot-starter-security 启动器后,Spring Security 已经生效,默认拦截全部请求,如果用户没有登录,跳转到内置登录页面。
在项目中新建 login.html 页面后在浏览器输入: http://localhost:8080/login.html 后会显示下面页面

在这里插入图片描述

默认的 username 为 user,password 打印在控制台中。当然了,同学们显示的肯定和我的不一样。
在浏览器中输入账号和密码后会显示 login.html 页面内容。

在这里插入图片描述

三、 UserDetailsService 详解

当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。
如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。接口定义如下:

1 返回值

返回值 UserDetails 是一个接口,定义如下要想返回 UserDetails 的实例就只能返回接口的实现类。Spring
Security 中提供了如下的实例。对于我们只需要使用里面的 User 类即可。注意 User 的全限定路径是:
org.springframework.security.core.userdetails.User
此处经常和系统中自己开发的 User 类弄混。
在 User 类中提供了很多方法和属性。

在这里插入图片描述

其中构造方法有两个,调用其中任何一个都可以实例化
UserDetails 实现类 User 类的实例。而三个参数的构造方法实际上也是调用 7 个参数的构造方法。
username:用户名
password:密码
authorities:用户具有的权限。此处不允许为 null
此处的用户名应该是客户端传递过来的用户名。而密码应该是从数据库中查询出来的密码。Spring Security 会根据 User 中的 password和客户端传递过来的 password 进行比较。如果相同则表示认证通过,如果不相同表示认证失败。
authorities 里面的权限对于后面学习授权是很有必要的,包含的所有内容为此用户具有的权限,如有里面没有包含某个权限,而在做某个事情时必须包含某个权限则会出现 403。通常都是通过
AuthorityUtils.commaSeparatedStringToAuthorityList(“”) 来 创 建authorities 集合对象的。参数是一个字符串,多个权限使用逗号分隔。

2 方法参数

方法参数表示用户名。此值是客户端表单传递过来的数据。默认情况下必须叫 username,否则无法接收。

3 异常

UsernameNotFoundException 用 户 名 没 有 发 现 异 常 。 在loadUserByUsername 中是需要通过自己的逻辑从数据库中取值的。如果 通 过 用 户 名 没 有 查 询 到 对 应 的 数 据 , 应 该 抛 出
UsernameNotFoundException,系统就知道用户名没有查询到。

四、 PasswordEncoder 密码解析器详解

Spring Security 要求容器中必须有 PasswordEncoder 实例。所以当自定义登录逻辑时要求必须给容器注入 PaswordEncoder 的 bean 对象

在这里插入图片描述

1 接口介绍

encode():把参数按照特定的解析规则进行解析。
matches()验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。
第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
upgradeEncoding():如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false。

2 内置解析器介绍

在 Spring Security 中内置了很多解析器。

3 BCryptPasswordEncoder 简介

BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器。
BCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单向加密(只能加密不能解密)。可以通过 strength 控制加密强度,默认 10.

在这里插入图片描述

4 代码演示

在 项 目 src/test/java 下 新 建 com.bjsxt.MyTest 测 试BCryptPasswordEncoder 用法。

在这里插入图片描述

package com.bjsxt;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest
@RunWith(SpringRunner.class)
public class MyTest {
   

    @Test
    public void test() {
   
        PasswordEncoder pe = new BCryptPasswordEncoder();
        String encode = pe.encode("123");
        System.out.println(encode);
    }
}

BUG:测试代码少了@Test注解

在这里插入图片描述

判断是否匹配

在这里插入图片描述

不匹配

在这里插入图片描述

BUG: 包名包结构还必须一样

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

五、 自定义登录逻辑

当 进 行 自 定 义 登 录 逻 辑 时 需 要 用 到 之 前 讲 解 的UserDetailsService 和 PasswordEncoder。

但是 Spring Security 要求:

当进行自定义登录逻辑时容器内必须有 PasswordEncoder 实例。所以不能直接 new 对象。

1 编写配置类

新建类 com.bjsxt.config.SecurityConfig 编写下面内容

package com.bjsxt.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig {
   

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

2 自定义逻辑

在 Spring Security 中实现 UserDetailService 就表示为用户详情服务。在这个类中编写用户认证逻辑。

package com.bjsxt.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
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.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
   

    @Autowired//注入密码解析器
    private PasswordEncoder encoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
   
        //查询数据库判断用户名是否存在,如果不存在抛出UsernameNotFoundException
        if (!username.equals("admin")) {
   
            throw new UsernameNotFoundException("用户名不存在!!!");
        }
        //将查询出来的密码进行解析,或直接将password放到构造方法中,数据库中存在的是密码加密后的结果
        String password = encoder.encode("123");
        return new User(username, password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

3 查看效果

重启项目后,在浏览器中输入账号:admin,密码:123。后可以正确进入到 login.html 页面。

在这里插入图片描述

在这里插入图片描述

六、 自定义登录页面

虽然 Spring Security 给我们提供了登录页面,但是对于实际项目中,大多喜欢使用自己的登录页面。所以 Spring Security 中不仅仅提供了登录页面,还支持用户自定义登录页面。实现过程也比较简单,只需要修改配置类即可。

package com.bjsxt.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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   

    @Override
    protected void configure(HttpSecurity http) throws Exception {
   
        http.formLogin()
            .loginPage("/login.html");
    }

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

重启项目 原因:所有的请求都需要拦截,必须登陆后才能访问,会跳到login.html,但是访问login.html也需要认证,如此陷入死循环

在这里插入图片描述

解决办法,对login.html进行放行

在这里插入图片描述

访问main.html回车 跳转到login.html

在这里插入图片描述

在业务层添加打印语句

登录打印语句并未打印

在这里插入图片描述

在这里插入图片描述

再配置两行代码

在这里插入图片描述

会执行代码,但是跳转到404

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

package com.bjsxt.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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * 自定义登录页面
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   

    @Override
    protected void configure(HttpSecurity http) throws Exception {
   
        //用户认证
        http.formLogin()
                .loginProcessingUrl("/login")//当发现请求是/login时认为是登录,需要执行UserDetailsServiceImpl
                .successForwardUrl("/toMain")//此处是一个post请求得用过控制器跳转,直接请求会报405错误
                .loginPage("/login.html");

        //URL拦截
        http.authorizeRequests()
                .antMatchers("/login.html").permitAll()//   /login.html 不需要拦截,放行
                .anyRequest().authenticated();//所有的请求都需要被认证,登录后才能访问

        //关闭CSRF防护
        http.csrf().disable();
    }

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

重启项目,成功登录

在这里插入图片描述

1 编写登录页面

别写登录页面,登录页面中的 action 不编写对应控制器也可以。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password"/><br/>
    <input type="submit" value="OK">
</form>
</body>
</html>

2 修改配置类

修改配置类中主要是设置哪个页面是登录页面。配置类需要继承WebSecurityConfigurerAdapte,并重写 configure 方法。

successForwardUrl()登录成功后跳转地址

loginPage() 登录页面

loginProcessingUrl 登录页面表单提交地址,此地址可以不真实存在。

antMatchers():匹配内容

permitAll():允许

package com.bjsxt.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.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   

    @Override
    protected void configure(HttpSecurity http) throws Exception {
   

        //表单认证
        http.formLogin()
                .loginProcessingUrl("/login")   //当发现是/login 时认为是登录需要执行UserDetailsServiceImpl
                .successForwardUrl("/toMain")   //此处是一个post请求   得通过控制器跳转,直接请求页面会报405错误
                .loginPage("/login.html"
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值