Spirng Security知识点整理
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spirng Security知识点整理相关的知识,希望对你有一定的参考价值。
Spirng Security
案例
新建工程,引入依赖
<?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>org.example</groupId>
<artifactId>SpringSecurity</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<!-- 父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
</dependencies>
</project>
创建启动项和controller层
@SpringBootApplication
public class main
{
public static void main(String[] args) {
SpringApplication.run(main.class,args);
}
}
@RestController
public class HelloController
{
@GetMapping("/hello")
public String hello()
{
return "hello spring security";
}
}
启动项目
启动日志会打印一个通过UUID随机生成的密码
访问controller,首先请求会被安全框架的aop机制拦截,要求使用用户名和密码验证登录
默认的用户名和密码为:
用户名: user
密码: 日志打印生成的uuid
自定义用户名和密码
配置文件中设置用户名和密码
spring:
security:
user:
name: 大忽悠
password: 123456
对应的绑定配置文件的类,如下:
@ConfigurationProperties(
prefix = "spring.security"
)
public class SecurityProperties {
public static final int BASIC_AUTH_ORDER = 2147483642;
public static final int IGNORED_ORDER = -2147483648;
public static final int DEFAULT_FILTER_ORDER = -100;
private final SecurityProperties.Filter filter = new SecurityProperties.Filter();
private final SecurityProperties.User user = new SecurityProperties.User();
public SecurityProperties() {
}
public SecurityProperties.User getUser() {
return this.user;
}
public SecurityProperties.Filter getFilter() {
return this.filter;
}
public static class User {
private String name = "user";
private String password = UUID.randomUUID().toString();
private List<String> roles = new ArrayList();
private boolean passwordGenerated = true;
public User() {
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
if (StringUtils.hasLength(password)) {
this.passwordGenerated = false;
this.password = password;
}
}
public List<String> getRoles() {
return this.roles;
}
public void setRoles(List<String> roles) {
this.roles = new ArrayList(roles);
}
public boolean isPasswordGenerated() {
return this.passwordGenerated;
}
}
public static class Filter {
private int order = -100;
private Set<DispatcherType> dispatcherTypes;
public Filter() {
this.dispatcherTypes = new HashSet(Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Set<DispatcherType> getDispatcherTypes() {
return this.dispatcherTypes;
}
public void setDispatcherTypes(Set<DispatcherType> dispatcherTypes) {
this.dispatcherTypes = dispatcherTypes;
}
}
}
关闭验证功能
主配置类中排除安全框架的配置
//排除security的配置,不启用
@SpringBootApplication(exclude = SecurityAutoConfiguration.class)
public class main
{
public static void main(String[] args) {
SpringApplication.run(main.class,args);
}
}
默认用户认证模块涉及到的三个类
UserDetailsService
当什么也没有配置的时候,账号和密码是由 Spring Security 定义生成的。而在实际项目中账号和密码都是从数据库中查询出来的。所以我们要通过自定义逻辑控制认证逻辑。如果需要自定义逻辑时,只需要实现 UserDetailsService 接口即可。接口定义如下:
public interface UserDetailsService {
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
返回值: UserDetails
返回值 UserDetails 是一个接口,定义如下
public interface UserDetails extends Serializable {
//获取用户权限
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
//账号是否未过期
boolean isAccountNonExpired();
//账号是否未被锁定
boolean isAccountNonLocked();
//凭证是否未过期,凭证就是密码
boolean isCredentialsNonExpired();
//用户是否启用状态
boolean isEnabled();
}
UserDetails的实现类之:User
要想返回 UserDetails
的实例就只能返回接口的实现类。SpringSecurity 中提供了如下的实例。对于我们只需要使用里面的 User
类即可。注意 User 的全限定路径是:
org.springframework.security.core.userdetails.User
此处经常和系统中自己开发的 User 类弄混。
在 User 类中提供了很多方法和属性。
public class User implements UserDetails, CredentialsContainer {
private static final long serialVersionUID = 550L;
private static final Log logger = LogFactory.getLog(User.class);
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
.....
}
其中构造方法有两个,调用其中任何一个都可以实例化
UserDetails
实现类 User
类的实例。而三个参数的构造方法实际上也是调用 7 个参数的构造方法。
-
username
:用户名 -
password
:密码 -
authorities
:用户具有的权限。此处不允许为 null
此处的用户名应该是客户端传递过来的用户名。而密码应该是从数据库中查询出来的密码。Spring Security 会根据 User 中的 password
和客户端传递过来的 password
进行比较。如果相同则表示认证通过,如果不相同表示认证失败。
authorities
里面的权限对于后面学习授权是很有必要的,包含的所有内容为此用户具有的权限,如有里面没有包含某个权限,而在做某个事情时必须包含某个权限则会出现 403。通常都是通过AuthorityUtils.commaSeparatedStringToAuthorityList(“”)
来创建authorities
集合对象的。参数是一个字符串,多个权限使用逗号分隔。
方法参数
方法参数表示用户名。此值是客户端表单传递过来的数据。默认情况下必须叫 username
,否则无法接收。
异常
UsernameNotFoundException
用户名没有发现异常。在loadUserByUsername
中是需要通过自己的逻辑从数据库中取值的。如果通过用户名没有查询到对应的数据,应该抛出UsernameNotFoundException
,系统就知道用户名没有查询到。
PasswordEncoder
Spring Security 要求容器中必须有PasswordEncoder
实例。所以当自定义登录逻辑时要求必须给容器注入PaswordEncoder
的bean对象。
接口介绍
-
encode()
:把参数按照特定的解析规则进行解析。 -
matches()
:验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。 -
upgradeEncoding()
:如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回 false。默认返回 false。
public interface PasswordEncoder {
//加密
String encode(CharSequence var1);
//匹配
boolean matches(CharSequence var1, String var2);
//二次加密
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
内置解析器介绍
BCryptPasswordEncoder 简介
BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器。
BCryptPasswordEncoder 是对 bcrypt
强散列方法的具体实现。是基于Hash算法实现的单向加密。可以通过strength控制加密强度,默认 10.
代码演示
新建测试方法BCryptPasswordEncoder 用法。
@SpringBootTest(classes = main.class)
public class Test
{
@org.junit.jupiter.api.Test
public void test()
{
//创建解析器
PasswordEncoder pw = new BCryptPasswordEncoder();
//对密码加密
String encode = pw.encode("123");
System.out.println(encode);
//判断原字符和加密后内容是否匹配
boolean matches = pw.matches("123", encode);
System.out.println("==================="+matches);
}
}
自定义登录逻辑
当 进 行 自 定 义 登 录 逻 辑 时 需 要 用 到 之 前 讲 解 的UserDetailsService
和 PasswordEncoder
。但是 Spring Security 要求:当进行自定义登录逻辑时容器内必须有 PasswordEncoder
实例。所以不能直接 new 对象。
编写配置类
@Configuration
public class SecurityConfig {
@Bean
public PasswordEncoder getPw(){
return new BCryptPasswordEncoder();
}
}
自定义逻辑
在 Spring Security 中实现 UserDetailService 就表示为用户详情服务。在这个类中编写用户认证逻辑。
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
private PasswordEncoder pw;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.查询数据库判断用户名是否存在,如果不存在抛出UsernameNotFoundException异常
if (!"admin".equals(username)){
throw new UsernameNotFoundException("用户名不存在");
}
//2.把查询出来的密码(注册时已经加密过)进行解析,或直接把密码放入构造方法中
String password = pw.encode("123");
return new User(username,password, AuthorityUtils.commaSeparatedStringToAuthorityList("admin,normal"));
}
}
其框架会把提交的密码使用我们定义的passwordEncode加密后调用**org.springframework.security.crypto.password.PasswordEncoder#matches
**方法,与 返回的User中的密码进行比对。配对正常就验证通过。
查看效果
重启项目后,在浏览器中输入账号:admin,密码:123。后可以正确进入到 login.html 页面。
自定义登录页面
虽然 Spring 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>
修改配置类
修改配置类中主要是设置哪个页面是登录页面。配置类需要继承WebSecurityConfigurerAdapter,并重写 configure 方法。
-
successForwardUrl()
:登录成功后跳转地址 -
loginPage()
:登录页面 -
loginProcessingUrl
:登录页面表单提交地址,此地址可以不真实存在。 -
antMatchers()
:匹配内容 -
permitAll()
:允许
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//表单提交
http.formLogin()
//自定义登录页面
.loginPage("/login.html")
//当发现/login时认为是登录,必须和表单提交的地址一样。去执行UserServiceImpl
.loginProcessingUrl("/login"以上是关于Spirng Security知识点整理的主要内容,如果未能解决你的问题,请参考以下文章