1.关于Spring Security
一般Spring Security是搭配Spring boot使用的,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。
2.SpringSecurity入门
添加依赖
<dependencies>
<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>
</dependencies>
依赖添加完,Spring Security就默认提供了许多功能将整个应用给保护了起来:
1.要求未经过身份验证的用户必须先经过身份验证才能访问其它接口
2.创建好了默认登录表单,HTTP由基础认证变成了表单认证
3.添加了 CSRF 攻击防护,Session Fixation 攻击防护
3.Spring Security的基础认证和表单认证:
认证方式 | 有无状态 | 简介 | 应用场景 |
---|---|---|---|
基础认证 | 无状态 | 不使用cookie | 对外API |
表单认证 | 有状态 | 使用session会话 | 网站应用 |
1、HTTP基础认证
通过HTTP请求头携带用户名和密码进行登录认证
2、HTTP表单认证
Spring Security的默认认证方式是表单认证,使用session会话
Spring Security 用户对象与认证对象
Spring Security的登录认证其实主要就是 找到用户对象,给其认证
1、用户对象
怎么去找用户对象呢?Spring Security默认提供了一些接口
接口名 | 说明 |
---|---|
UserDetails | 用户对象 |
GrantedAuthority | 用户权限 |
UserDetailsService | 用户对象查询操作 |
UserDetailsManager | 创建用户、修改用户密码 |
因为只涉及到认证,所以只需要实现UserDetails接口,和UserDetailsManager接口就可以了。
其中UserDetails接口的源码中封装了一些对用户的基本操作,getUsername() 和 getPassword()等,我们会用到这两个方法。
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
UserDetailsService接口中只有一个方法loadUserByUsername(String username)方法,作用是根据username去查找用户
我们一般会实现这个接口,重写这个方法来查找用户。
2、认证对象
怎么去给对象认证呢?Spring Security也默认提供了一些接口
接口名 | 说明 |
---|---|
Authentication | 认证请求详细信息 |
AuthenticationProvider | 认证的业务执行者 |
Authentication 中是什么信息?
1、Principal
:用户信息,没有认证时一般是用户名,认证后一般是用户对象
2、Credentials
:用户凭证,一般是密码
3、Authorities
:用户权限
基于MySQL自定义认证过程:
我们是从数据库中查询用户的,所以这里我们用MySQL数据库来存储用户。
建表语句:
根据Bcrypt算法加密密码后的数据
密码和用户名一致
-- 建表
CREATE TABLE `tb_user` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
`username` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
`password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
`nickname` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '昵称',
`enabled` tinyint NOT NULL DEFAULT '1' COMMENT '账号可用标识',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表';
-- 数据
INSERT INTO `tb_user` VALUES (1, 'zhangsan', '$2a$10$/1XHgJYXtF4g/AiR41si8uvVC6Zc.Z9xVmXX4hO2z.b4.DX.H2j5W', '张三', 1);
INSERT INTO `tb_user` VALUES (2, 'lisi', '$2a$10$PEcF03ina7x9mmt2VbB0ueVkLZWQo/yoKOfvfQpoL09/faBlNuuZ.', '李四', 1);
INSERT INTO `tb_user` VALUES (3, 'wangwu', '$2a$10$PMumxkwwrELTbNDXCj0N4.jD/e/Hv.JiiZTFkdFqlDNLU2TahdYNq', '王五', 1);
代码实现:
创建springboot项目,引入lombok , spirng security , spring boot web , mysql
引入mabatis plus
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
实体类User
实体类User继承了UserDetails
@Data
@TableName("tb_user")
public class User implements UserDetails {
/**
* 主键id
*/
@TableId
private Long id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 昵称
*/
private String nickname;
/**
* 账号可用标识
*/
private Integer enabled;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
UserMapper
UserMapper继承了mybatisplus的baseMapper<T>,封装了对数据库增删改查的方法
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
UserService
UserService继承了mybatis的 IService<T>类
@Service
public interface UserService extends IService<User> {
}
UserServiceImpl类:
作用:只查询用户是否在数据库中是否存在
继承了mtbatis的ServiceImpl<M extends BaseMapper<T> , T> 类
同时实现了UserService(自己的接口) , UserDetailsService(用户对象查询操作的接口)
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService, UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return null;
}
}
controller类:
随便写了一个接口
@RestController
public class IndexController {
@GetMapping("/hello")
public String hello() {
return "hello";
}
}
UsernamePasswordAuthenticationToken使用
UsernamePasswordAuthenticationToken
是用于封装用户名密码信息的一个类,它实现了Authentication
接口,它的构造方法如下:
public UsernamePasswordAuthenticationToken(Object principal, Object credentials);
以下代码演示了如何使用 UsernamePasswordAuthenticationToken
对象进行认证:
// 构造用户名密码认证信息
Authentication authentication = new UsernamePasswordAuthenticationToken(username, password);
// 对认证信息进行认证
authentication = authenticationManager.authenticate(authentication);
// 将认证信息存储在 SecurityContextHolder 中
SecurityContextHolder.getContext().setAuthentication(authentication);
authenticationManager 是一个 AuthenticationManager 对象,用于对认证请求进行认证。如果认证成功,authenticate() 方法会返回一个 Authentication 对象,其中包含了认证请求的详细信息,例如认证主体、认证凭证、授权信息等。最后,将认证信息存储在 SecurityContextHolder 中,用于在应用程序中获取当前的认证信息。
自定义认证类UserAuthenticationProvider:
如果用户在数据库中存在,那么就去认证,检测用户名和密码是否正确,如果都正确,那么返回一个已经认证的Authentication
@Component
@Slf4j
public class UserAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
// 密码加密
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
// 自己实现认证过程
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 从Authentication 对象中获取用户名和密码
String username = authentication.getName();
String password = authentication.getCredentials().toString();
// 比对用户名,看是否存在,不存在会抛出异常
UserDetails user = userDetailsService.loadUserByUsername(username);
// 用户名存在的话,则比对密码
if (this.passwordEncoder().matches(password,user.getPassword())) {
// 密码匹配成功
log.info("Access Success: " + user);
return new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities());
} else {
// 密码匹配失败
log.error("Access Denied: The username or password is wrong!");
throw new BadCredentialsException("The username or password is wrong!");
}
}
@Override
public boolean supports(Class<?> authentication) {
return true;
}
}
基于表单实现认证配置:
@Configuration
public class SecurityFormConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// 启用session会话
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
// 认证方式:Form
http.authorizeRequests()
// 所有请求都需要认证
.anyRequest().authenticated()
.and()
// 启动表单认证模式
.formLogin()
// 登录页面
.loginPage("/login.html")
// 请求提交地址
.loginProcessingUrl("/login")
// 放行上面的两个地址
.permitAll()
// 设置提交的参数名
.usernameParameter("username")
.passwordParameter("password")
.and()
// 开始设置注销功能
.logout()
// 注销的url
.logoutUrl("/logout")
// session直接过期
.invalidateHttpSession(true)
// 清除认证信息
.clearAuthentication(true)
// 注销成功后跳转地址
.logoutSuccessUrl("/login.html")
.and()
// 禁用csrf安全防护
.csrf().disable();
return http.build();
}
}
static/login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<h2>Login</h2>
<form action="/login" method="post">
<div><label>username:<input type="text" name="username"></label></div>
<div><label>password:<input type="password" name="password"></label></div>
<div><input type="submit"></div>
</form>
</body>
</html>
测试
测试http://localhost:8080/hello,看是否跳转到 login登录页面。输入用户名和密码,看是否能够访问/hello接口,页面显示hello