在我们的业务处理中,对于安全的管理是必不可少的,也就是认证以及权限,在Spring横行的今天,学习如何使用SpringSecurity是必不可少的,所以最近想深入学习一下SpringSecurity并分享一下自己的学习历程,此次的学习主要以实践+部分源码为主,完成认证+权限+Oauth2+JWT+oos等,最后会做一个demo,来完成此次的SpringSecurity之旅。
简介
Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的 Bean,充分利用了 Spring IoC ,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。主要实现了认证(用户登录)和授权(资源调用)。
具体实现与部分源码分析
不进行任何处理
1、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2、写一个登录的controller
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(){
System.out.println("进行登录中");
return "redirect:main.html";
}
}
3、创建登录页和主页
<!--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">
密码:<input type="password" name="password">
<input type="submit" value="提交">
</form>
</body>
</html>
<!--main.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
登录成功
</body>
</html>
4、启动项目测试,访问http://localhost:8080/login.html
启动后访问一个官方提供的页面,用户名默认为user,密码为启动时控制台打印的Using generated security password。
Using generated security password: 90e4c13a-3e17-435a-9835-9a5c6088117f
输入可以正常访问。
认证的进阶(一)
上面这样的处理,很显然是不满足我们在业务中的处理的,我们需要自己的登录页面,也需要有自己的验证逻辑。接下来先从验证逻辑方面说起。
首先我们需要认识一个接口UserDetailsService。
public interface UserDetailsService {
//通过username加载一个User,此处的var1即为前端传过来的username.
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
可以看出该方法返回了一个UserDetails,让我们继续看一下这个东东是啥
public interface UserDetails extends Serializable {
//获取该用户的所有权限
Collection<? extends GrantedAuthority> getAuthorities();
//获取密码
String getPassword();
//获取用户名
String getUsername();
//判断账户是否过期
boolean isAccountNonExpired();
//判断账户是否锁定
boolean isAccountNonLocked();
//判断凭证(密码)是否过期
boolean isCredentialsNonExpired();
//判断账户是否可用
boolean isEnabled();
}
可以看出它是一个用来存储,账户信息的一个类(它存储的是用户的正确的信息,比如从数据库里查出的),我们继续看一下它的实现类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;
public User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this(username, password, true, true, true, true, authorities);
}
//此处为构造方法,进行一个参数的填充,在业务中可以将从数据库查询到的数据放入User对象中。
public User(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
//如果username和password为空,则抛出Cannot pass null or empty values to constructor
Assert.isTrue(username != null && !"".equals(username) && password != null, "Cannot pass null or empty values to constructor");
this.username = username;
this.password = password;
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.credentialsNonExpired = credentialsNonExpired;
this.accountNonLocked = accountNonLocked;
this.authorities = Collections.unmodifiableSet(sortAuthorities(authorities));
}
.....
}
这里还需要说一个PasswordEncoder接口,它是用于对密码进行一个加密,对比。
public interface PasswordEncoder {
//对var1进行加密,使用的是
String encode(CharSequence var1);
//对密码进行比对,var1为原生密码,var2为加密后的密码
boolean matches(CharSequence var1, String var2);
//是否进行了二次加密
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
官方推荐使用的实现类为BCryptPasswordEncoder,对其加密方式感兴趣的话,可以看看其源码。
如果对以上的俩种类不清楚的话,看一下接下来的实现逻辑就知道他的作用了。
承接上面的代码。
1、进行配置
@Configurat