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 项目
创建一个SpringBoot项目,添加web和security启动器,见下面的依赖
1 导入依赖
Spring Security 已经被 Spring boot 进行集成,使用时直接引入启动器即可。
<?xml version="1.0" encoding="UTF-8"?>
2创建main.html和login.html
Main:
<!DOCTYPE html>
Login.html:
<!DOCTYPE html>
3测试
- 启动类本身创建SpringBoot项目就会有
- 启动项目
- 访问http://localhost:8080/login.html
导入 spring-boot-starter-security 启动器后,Spring Security 已经生效,默认拦截全部请求,如果用户没有登录,跳转到内置登录页面,就是上面这个默认的 username 为 user,password 打印在控制台中。
在浏览器中输入账号和密码后会显示 login.html 页面内容。
三、 UserDetailsService 详解
当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。
如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。接口定义如下:
1 返回值
返回值 UserDetails 是一个接口,定义如下
要想返回 UserDetails 的实例就只能返回接口的实现类。SpringSecurity 中提供了如下的实例。对于我们只需要使用里面的 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 代码演示
在上面的项目中(只要是引入了Spring Security启动器的项目)进行测试
package
运行:
五、 自定义登录逻辑
当 进 行 自 定 义 登 录 逻 辑 时 需 要 用 到 之 前 讲 解 的UserDetailsService 和PasswordEncoder。但是 Spring Security 要求:当进行自定义登录逻辑时容器内必须有 PasswordEncoder 实例。所以不能直接 new 对象。
1 编写配置类
在上面的项目中:新建类 com.bjsxt.config.SecurityConfig 编写下面内容
package
2 自定义逻辑
在 Spring Security 中实现 UserDetailService 就表示为用户详情服务。在这个类中编写用户认证逻辑
package
3 查看效果
重启项目后,访问http://localhost:8080/login.html在浏览器中输入账号:admin,密码:123。后可以正确进入到 login.html 页面。
六、 自定义登录页面
虽然 Spring Security 给我们提供了登录页面,但是对于实际项目中,大多喜欢使用自己的登录页面。所以 Spring Security 中不仅仅提供了登录页面,还支持用户自定义登录页面。实现过程也比较简单,只需要修改配置类即可。
1 编写登录页面
编写登录页面,登录页面中<form>的 action 不编写对应控制器也可以。其实就是上面写的login.html
<!DOCTYPE html>
2 修改配置类
修改配置类中主要是设置哪个页面是登录页面。配置类需要继承WebSecurityConfigurerAdapte,并重写 configure 方法。
successForwardUrl()登录成功后跳转地址
loginPage() 登录页面
loginProcessingUrl 登录页面表单提交地址,此地址可以不真实存在。
antMatchers():匹配内容
permitAll():允许
package
3 编写控制器
编写控制器,当用户登录成功后跳转 toMain 控制器。编写完成控制器后编写 main.html。页面中随意写上一句话表示 main.html 页面内容即可。而之前的/login 控制器方法是不执行的,所以可以删除了。
package
4测试
- 启动项目
- 访问http://localhost:8080/main,然后就会跳转到自定义登陆页面login.html
- 输入admin用户名和123的密码,然后就会跳转到main.html
七、 认证过程其他常用配置
1 失败跳转
表单处理中成功会跳转到一个地址,失败也可以跳转到一个地址中。
1.1编写页面
在 src/main/resources/static 下新建 fail.html 并编写如下内容
<!DOCTYPE html>
1.2修改表单配置
在配置方法中表单认证部分添加 failureForwardUrl()方法,表示登录失败跳转的 url。此处依然是 POST 请求,所以跳转到可以接收 POST请求的控制器/fail 中。
1.3添加控制器方法
在控制器类中添加控制器方法,方法映射路径/fail。此处要注意:
由于是 POST 请求访问/fail。所以如果返回值直接转发到 fail.html 中,及时有效果,控制台也会报警告,提示 fail.html 不支持 POST 访问方式
package
1.4设置 fail.html 不需要认证
认证失败跳转到 fail.html 页面中,所以必须配置 fail.html 不需要被认证。需要修改配置类中内容
最后现在为止,配置类SecurityConfig变成了:
package
1.5测试
- 启动项目
- 访问http://localhost:8080/login.html,随便输入用户名和密码使其登陆失败
- 点击跳转,进入登陆页面
输入admin,123得以进入
2 设置请求账户和密码的参数名
2.1源码简介
当进行登录时会执行 UsernamePasswordAuthenticationFilter 过滤
usernamePasrameter:账户参数名
passwordParameter:密码参数名
postOnly=true:默认情况下只允许 POST 请求。
2.2修改配置
package com.bjsxt.springsecuritydemo.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()
.usernameParameter("username123")
.passwordParameter("password123")
//loginProcessingUrl 登录页面表单提交地址,此地址可以不真实存在
.loginProcessingUrl("/login") //当发现是/login时认为是登陆,需要执行UserDetailsServiceImpl中的那个方法
.loginPage("/login.html") //配置登陆页面
.successForwardUrl("/toMain") //此处是post请求
.failureForwardUrl("/fail"); //登陆失败跳转地址
//url 拦截
http.authorizeRequests()
.antMatchers("/login.html").permitAll() //login.html不需要被认证
.antMatchers("/fail.html").permitAll()
.anyRequest().authenticated(); //所有的请求都必须被认证。必须登陆后才能访问
//关闭csrf防护
http.csrf().disable();
}
@Bean
public PasswordEncoder setPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
2.3修改页面
修改 login.html
<!DOCTYPE html>
这样就可通过测试
3 自定义登录成功处理器
3.1源码分析
使用 successForwardUrl()时表示成功后转发请求到地址。内部是通过 successHandler()方法进行控制成功后交给哪个类进行处理
ForwardAuthenticationSuccessHandler 内部就是最简单的请求转发。由于是请求转发,当遇到需要跳转到站外或在前后端分离的项目中就无法使用了。
当需要控制登录成功后去做一些事情时,可以进行自定义认证成功控制器。
3.2代码实现
3.2.1 自定义类
新建类 com.bjsxt.handler.MyAuthenticationSuccessHandler 编写如下:
package
3.2.2 修改配置项
使用 successHandler()方法设置成功后交给哪个对象进行处理
package com.bjsxt.springsecuritydemo.config;
import com.bjsxt.springsecuritydemo.handler.MyAuthenticationSuccessHandler;
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()
.usernameParameter("username123")
.passwordParameter("password123")
//loginProcessingUrl 登录页面表单提交地址,此地址可以不真实存在
.loginProcessingUrl("/login") //当发现是/login时认为是登陆,需要执行UserDetailsServiceImpl中的那个方法
.loginPage("/login.html") //配置登陆页面
// .successForwardUrl("/toMain") //此处是post请求
重定向到百度。这只是一个示例,具体需要看项目业务需求
.successHandler(new MyAuthenticationSuccessHandler("http://www.baidu.com"))
.failureForwardUrl("/fail"); //登陆失败跳转地址
//url 拦截
http.authorizeRequests()
.antMatchers("/login.html").permitAll() //login.html不需要被认证
.antMatchers("/fail.html").permitAll()
.anyRequest().authenticated(); //所有的请求都必须被认证。必须登陆后才能访问
//关闭csrf防护
http.csrf().disable();
}
@Bean
public PasswordEncoder setPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
3.2.3测试
- 启动项目
- 访问http://localhost:8080/login.html,登陆admin,123
- 进入百度首页,查看控制台