Spring Security入门宝典(一)上篇

目录

一.简介

1.概括

2.历史

二.第一个 Spring Security 项目 

三.UserDetailsService 详解

四.PasswordEncoder 密码解析器详解

五.自定义登录逻辑 

六.自定义登录页面

七.认证过程其他常用配置

1.失败跳转

2.设置请求账户和密码的参数名

3.自定义登录成功处理器

4. 自定义登录失败处理器


一.简介

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 进行集成,使用时直接引入启动器即可。

去查找依赖,注意父依赖的外层标签是<parent></parent>

pom.xml里配置

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
            <version>2.7.0</version>
        </dependency>
    </dependencies>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
    </parent>

2.访问页面

导入 spring-boot-starter-security 启动器后,Spring Security 已经生效,默认拦截全部请求,如果用户没有登录,跳转到内置登录页面。
在项目中新建 login.html 页面
<!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="登录">
</form>
</body>
</html>

main.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
main.html
</body>
</html>

LoginController.java:

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

编写启动类:

@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class);
    }
}

在浏览器输入:http://localhost:8080/login.html 后会显示下面页面

但是咱们自己写的login.html没这么花哨啊,这是为啥呢?因为这是Spring Security帮我们设置的一个页面,默认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.代码演示 

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

    @Test
    public void test(){
        PasswordEncoder pe = new BCryptPasswordEncoder();
        //对123进行加密
        String encode = pe.encode("123");
        System.out.println(encode);
        //看加密后的密码和1234是否能匹配上,输出肯定是false,因为事和123匹配的
        boolean matches = pe.matches("1234", encode);
        System.out.println("============:"+matches);
    }
}

控制台输出如下:

$2a$10$I3Gh5TQGNN/k3M7KE043fuGWcFNOshmyDviJlTY7jxl0wfHKp6jka
============:false

五.自定义登录逻辑 

当 进 行 自 定 义 登 录 逻 辑 时 需 要 用 到 之 前 讲 解 的 UserDetailsService 和 PasswordEncoder 。但是 Spring Security 要求:当进行自定义登录逻辑时容器内必须有 PasswordEncoder 实例。所以不能直接 new 对象。
1. 编写配置类
新建类 com.bjsxt.config.SecurityConfig 编写下面内容
@Configuration
public class SecurityConfig {
    @Bean
    public PasswordEncoder getPwdEncoder(){
        return new BCryptPasswordEncoder();
    }
}

2.自定义逻辑

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

下的函数设置登录用户名为admin,密码为123

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private PasswordEncoder encoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //登陆时就会用到这个类,就会输出下面这句话
        System.out.println("执行了UserDetailsServiceImpl");

        //1. 查询数据库判断用户名是否存在,如果不存在抛出UsernameNotFoundException

        if(!username.equals("admin")){
            throw new UsernameNotFoundException("用户名不存在");
        }
        //把查询出来的密码进行解析,或直接把password放到构造方法中。
        //理解:password就是数据库中查询出来的密码,查询出来的内容不是123
        String password = encoder.encode("123");

        return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}
3. 查看效果
重启项目后,在浏览器中输入账号: admin ,密码: 123 。后可以正确进入到 login.html 页面。

启动项目时,注意运行的是哪个类,应该是启动类 

访问localhost:8080/login.html

六.自定义登录页面

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

1.编写登录页面

编写登录页面,登录页面中 <form> action 不编写对应控制器也可以。
用前面写的那个login.html即可
2. 修改配置类
修改配置类中主要是设置哪个页面是登录页面。配置类需要继承WebSecurityConfigurerAdapte,并重写 configure 方法。
  • successForwardUrl()登录成功后跳转地址
  • loginPage() 登录页面
  • loginProcessingUrl 登录页面表单提交地址,此地址可以不真实存在。
  • antMatchers():匹配内容
  • permitAll():允许

只有配置了loginProcessingUrl和之后关闭csrf防护之后,登录时才会成功调用UserDetailsService接口的实现类,故而控制台才会输出UserDetailsServiceImpl类的控制台输出语句的值。

但是在登录界面输入用户名和密码却出现了下面的错误,所以还需要在进行successForwardUrl

配置

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder getPwdEncoder(){
        return new BCryptPasswordEncoder();
    }

    protected void configure(HttpSecurity http) throws Exception{
        //表单认证
        http.formLogin()
                /*设置登陆界面,这样访问localhost:8080/login.html时会直接跳转,
                而不会先出现Spring Security的默认页面,但是你会发现http://localhost:8080/main.html也可以访问了,
                所以这样就把所有拦截都取消了
                 */
                .loginPage("/login.html")
                //当发现/login 时认为是登录,需要执行 UserDetailsServiceImpl
                .loginProcessingUrl("/login")
                //设置登录成功之后的跳转页面,为什么不直接写main.html,因为这个是post请求,而main.html是静态资源
                .successForwardUrl("/toMain");
        //url拦截
        http.authorizeRequests()
                //login.html 不需要被认证
                .antMatchers("/login.html").permitAll()
                //所有的请求都必须被认证。必须登录 后才能访问。
                .anyRequest().authenticated();
        //关闭 csrf 防护
        http.csrf().disable();

    }
}

3.编写控制器

编写控制器,当用户登录成功后跳转 toMain 控制器。编写完成控制器后编写 main.html 。页面中随意写上一句话表示 main.html 页面内容即可(main.html在前面已经写过)。而之前的/login 控制器方法是不执行的,所以可以删除了。
@Controller
public class LoginController {
    /*有没有这个都无所谓,因为这个路径实际上是不走的,login.html的action属性是/login只是为了
    配置配置类里的loginProcessingUrl属性,使之对应起来而已
     */
//    @RequestMapping("/login")
//    public String login(){
//        System.out.println("执行了login方法");
//        return "redirect:main.html";
//    }

    @PostMapping("/toMain")
    public String toMain(){
         return "redirect:/main.html";
    }
}

localhost:8080/login.html

 输入用户名admin,密码123

七.认证过程其他常用配置

1.失败跳转

表单处理中成功会跳转到一个地址,失败也可以跳转到一个地址中。
1.1 编写页面
src/main/resources/static 下新建 fail.html 并编写如下内容
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
登陆失败,请重新登录。<a href="/login.html">跳转</a>
</body>
</html>

1.2修改表单配置

在配置方法中表单认证部分添加 failureForwardUrl() 方法,表示登录失败跳转的 url 。此处依然是 POST 请求,所以跳转到可以接收 POST 请求的控制器/fail 中。
//登录失败跳转地址,因为这里仍然是post请求,所以还要对此路径写一个控制器
.failureForwardUrl("/fail")

1.3添加控制器方法

在控制器类中添加控制器方法,方法映射路径 /fail 。此处要注意:由于是 POST 请求访问 /fail 。所以如果返回值直接转发到 fail.html 中, 及时有效果,控制台也会报警告,提示 fail.html 不支持 POST 访问方式。
@PostMapping("/fail")
public String fail(){
    return "redirect:/fail.html";
}

1.4设置 fail.html 不需要认证

认证失败跳转到 fail.html 页面中,所以必须配置 fail.html 不需要被认证。需要修改配置类中内容

运行项目,访问登录页面,故意写错用户名或密码,会跳转到fail.html,

点一下跳转就又回到了登录界面。

2.设置请求账户和密码的参数名

2.1 源码简介
当进行登录时会执行 UsernamePasswordAuthenticationFilter 过滤器(可以看一下UsernamePasswordAuthenticationFilter的源码)。
  • usernamePasrameter:账户参数名(源码中规定了账户参数名必须是username)
  • passwordParameter:密码参数名(源码中规定了密码参数名必须是password)
  • postOnly=true:默认情况下只允许 POST 请求。

注意登录页面的前端代码中的用户名和密码标签的name也得和 usernamePasrameter以及passwordParameter规定的值一样才行

2.2修改配置 

我需要在配置类里对usernamePasramete和passwordParameter进行修改,

2.3 修改页面
修改 login.html

例如我现在修改前端代码为:前面都加了个my

3.自定义登录成功处理器

3.1 源码分析
使用 successForwardUrl() 时表示成功后转发请求到地址。内部是通过 successHandler ()方法进
行控制成功后交给哪个类进行处理

ForwardAuthenticationSuccessHandler 内部就是最简单的请求转发。由于是请求转发(只能是是站内跳转),当遇到需要跳转到站外或在前后端分离的项目中就无法使用了。  

当需要控制登录成功后去做一些事情时,可以进行自定义认证成功控制器。

我们现在以登陆成功之后跳转到百度为例演示以下,请看下面的代码实现。

3.2 代码实现
3.2.1 自定义类
新建类 com.first.handler.MyAuthenticationSuccessHandler 编写如下
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    private String url;

    public MyAuthenticationSuccessHandler(String url) {
        this.url = url;
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //Principal 主体,存放了登录用户的信息
        User user = (User)authentication.getPrincipal();
        System.out.println(user.getUsername());
        //密码输出为 null
        System.out.println(user.getPassword());
        //System.out.println(user.getAuthorities());

        //上面的代码是为了看一下getPrincipal能获得什么,而下面的代码是本小节的功能代码
        response.sendRedirect(url);
    }
}
3.2.2 修改配置项
使用 successHandler() 方法设置成功后交给哪个对象进行处理
//站外跳转,这个和successForwardUrl只能使用其一
.successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"));

启动项目,访问localhost:8080/login.html

输入正确的用户名和密码之后,就会跳转到百度主页。

4. 自定义登录失败处理器

4.1 源码分析
failureForwardUrl() 内部调用的是 failureHandler() 方法
ForwardAuthenticationFailureHandler 中也是一个请求转发,并在 request 作用域中设置 SPRING_SECURITY_LAST_EXCEPTION key,内容为异常对象。
4.2 代码实现
4.2.1 新建控制器
新建 com.bjsxt.handler.MyForwardAuthenticationFailureHandler 实现AuthenticationFailureHandler 。在方法中添加重定向语句
 
public class MyForwardAuthenticationFailureHandler implements AuthenticationFailureHandler {
    private String url;

    public MyForwardAuthenticationFailureHandler(String url) {
        this.url = url;
    }

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.sendRedirect("/fail.html");
    }
}
4.2.2 修改配置类
修改配置类中表单登录部分。设置失败时交给失败处理器进行操作。failureForwardUrl failureHandler 不可共存。

启动项目自己故意输错用户名或密码测试一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海鱼肝油ya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值