SpringSecurity(基本使用)
第一部分(基本使用)
第一、了解SpringSecurity
SpringSecurity是一个开源的、可用于分布式系统的权限控制框架,它的核心功能有两个:认证和授权
所谓的认证,其实就是常见的短信认证、账户密码认证、语音认证等。
所谓的授权,其实就是常见的认证之后需要对该用户权限进行查询,查看该用户的权限,根据权限实现对不同资源的控制。
第二、基于内存的认证策略
基于内存的认证逻辑是SpringSecurity的第一种认证方式,他的核心思想就是自定义认证对象,将其封装为UsersDetails对象,然后放在内存中,执行登录逻辑的时候,只需要进行比对即可。- 内存认证逻辑
@Configuration
public class SpringSecurityConfig {
/**
* 内存的认证逻辑
* @return
*/
@Bean
public UserDetailsService userDetailsService() {
// 1、创建一个内存认证对象
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
// 2、创建内存对象
UserDetails user1 = User.withUsername("huidou").password("huidou").authorities("admin").build();
UserDetails user2 = User.withUsername("liziying").password("liziying").authorities("admin").build();
// 3、将内存认证对象放在内存中
manager.createUser(user1);
manager.createUser(user2);
// 4、返回认证逻辑
return manager;
}
}
同时还要编写密码编码器逻辑(SpringSecurity框架使用了密码加密的方式避免了黑客的密码比对攻击) /**
* 创建一个不编码的密码编码器
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
测试结果:成功登录!
但是这样的方式,也只能用于测试认证功能,在真实项目中是不可以的,所以,就可以使用基于数据库的方式实现用户的认证逻辑。
第三、基于数据库的认证策略
基于数据库的认证逻辑的原理是:会发现,在这里进行登录逻辑的认证的时候,它并没有通过controller层,直接执行自己的登录逻辑(也就是service层),是因为它的认证逻辑实现了UsersDetailsService接口实现类loadUserByUsername方法。
项目的登录认证逻辑只能存在一个,也就是如何实现UsersDetailsService接口的话,就需要关闭内存逻辑。
- 登陆逻辑代码
/**
* @Description: 用户登录的认证逻辑
* @Author: huidou 惠豆
* @CreateTime: 2022/6/23 10:46
*/
@Service
public class UserServiceImpl implements UserDetailsService {
@Autowired
private UsersMapper usersMapper;
/**
* 根据前端传递的用户名进行查找,但是这样的话,就意味着数据库的用户名必须unique唯一
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 1、封装查找条件
QueryWrapper<Users> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username",username);
// 2、查询对象信息
Users users = usersMapper.selectOne(queryWrapper);
// 3、判断用户信息是否存在
if (users == null) {
return null;
}
// 4、封装为UsersDetails对象
UserDetails userDetails = User.withUsername(users.getUsername()).password(users.getPassword()).authorities("admin").build();
return userDetails;
}
}
第四、密码编码器与解析器
- PasswordEncoder密码编码器
@Test
public void PasswordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String salt = "salt";
String passwordEncoder = bCryptPasswordEncoder.encode("huidou" + salt);
System.out.println(passwordEncoder);
}
该方式也是可以使用加盐操作实现对密码复杂化,降低攻击成功机率。
huidou加密之后的密码密文就是:$2a$10$NBOHiODtPZE54MXvhUIjn.mJycO/Pd/encCHb8oyNuzK3m4719ODS
- PasswordDecoder密码解析器
// 第一个参数是:原密码+盐值 第二个参数是:加密之后的密文 该方***进行对密文进行比对,看是否一致。
boolean matches = bCryptPasswordEncoder.matches("huidou" + salt, passwordEncoder);
System.out.println(matches);
第五、自定义登录页面
实现自定义登录页面的原因就是因为在真实项目中,是不会使用SpringSecurity所提供的默认的登陆页面,所以这个时候就需要对SpringSecurity进行自定义配置。
自定义登陆页面,需要考虑的比较多,例如:
-
表单里的登录请求
-
表达里的登录请求参数
-
表单登录成功跳转的页面
-
表单登录失败的请求
-
表单登录成功的请求
-
对必要的请求进行放行
-
对静态资源进行放行
-
关闭csrf跨域防护
/**
* 对请求资源进行控制
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
}
/**
* 对web静态资源进行放行
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
}
/**
* 对请求资源进行控制
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 对表单请求进行控制
http.formLogin()
.usernameParameter("username") // 登录参数1
.passwordParameter("password") // 登录参数2
.loginPage("/login.html") // 登录页面
.successForwardUrl("/index") // 登陆成功跳转请求
.failureUrl("/fail") // 登录失败跳转请求
.loginProcessingUrl("/login"); // 表单提交路径
// 对请求路径进行控制
http.authorizeRequests()
.antMatchers("/login.html").permitAll() // 对登录请求进行放行
.antMatchers("/fail").permitAll() // 对登录失败请求进行放行
.anyRequest().authenticated(); // 其余的请求都进行控制
// 关闭csrf跨域防护
http.csrf().disable();
}
/**
* 对web静态资源进行放行
* @param web
* @throws Exception
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/css/**")
.antMatchers("/js/**")
.antMatchers("/img/**");
}
Controller页面跳转控制器/**
* @Description: 页面跳转控制器
* @Author: huidou 惠豆
* @CreateTime: 2022/6/23 15:22
*/
@Controller
public class CommonController {
@RequestMapping("/{page}")
public String page(@PathVariable String page) {
return page;
}
}
测试结果:第六、登录成功之后处理器
前面已经成功的配置了自定义登录逻辑,下面就可以对登陆成功之后做一个自定义的处理器,该处理器的作用就是用于实现登录之后做一些日志的记录等。
想要做一个登陆成功的处理器,需要自定义一个处理器类,并且实现一个接口AuthenticationSuccessHandler,重写其中的方法onAuthenticationSuccess
/**
* @Description: 认证成功的处理器
* @Author: huidou 惠豆
* @CreateTime: 2022/6/23 15:30
*/
@Slf4j
public class MyLoginSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
// 做一些操作
UserDetails principal = (UserDetails) authentication.getPrincipal();
log.info("登录成功之后做一些日志的打印,登陆人信息: " + principal.getUsername());
// 登录成功之后的重定向
response.sendRedirect("/index");
}
}
修改SpringSecurity的http请求配置
// .successForwardUrl("/index") // 登陆成功跳转请求(注释起来)
.successHandler(new MyLoginSuccessHandler()) // 登录成功之后的处理器
测试结果:
进行登录成功时候,在控制台查看输出日志信息:
登录成功之后做一些日志的打印,登陆人信息: baizhan第七、登录失败时候处理器
登陆失败处理器就是用于实现登陆失败之后,做一些分析处理、日志记录等
想要做一个登陆成功的处理器,需要自定义一个处理器类,并且实现一个接口AuthenticationSuccessHandler,重写其中的方法AuthenticationFailureHandler
/**
* @Description: 登陆失败处理器
* @Author: huidou 惠豆
* @CreateTime: 2022/6/23 15:54
*/
@Slf4j
public class MyLoginFailureHandler implements AuthenticationFailureHandler {
// 自定义登陆失败处理器
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("登录失败,记录日志,分析错误原因");
// 登录失败重定向
response.sendRedirect("/fail");
}
}
修改SpringSecurity的http请求配置 // .failureUrl("/fail") // 登录失败跳转请求
测试结果:
进行登录失败时候,在控制台查看输出日志信息:
第八、SpringSecurity会话管理
实现SpringSecurity会话,这个功能的主要作用就是:当我在登录完成之后,就可以在页面上显示我的个人登录信息。/**
* @Description: 会话管理
* @Author: huidou 惠豆
* @CreateTime: 2022/6/23 16:12
*/
@RestController
public class MyController {
/**
* 实现会话管理,实现获得用户信息
* @return
*/
@RequestMapping("/user")
public String getUsername() {
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return userDetails.getUsername();
}
}
测试结果:第九、退出登录实现
退出登录功能实现,用于实现在SpringSecurity安全框架中,实现安全的退出,也就是对于自己的登录会话状态进行安全的销毁删除,同时退出请求必须是 /logout- 修改index页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"> <head> <meta charset="UTF-8"> <title>首页页面</title> </head> <body> 首页页面 <a href="/logout">立即退出</a> </body> </html>
第十、退出登录成功之后的处理器
实现退出登录之后的处理器的主要目的是:可以在退出邓丽之前做一些自定义的资源释放等。/**
* @Description: 成功退出处理器
* @Author: huidou 惠豆
* @CreateTime: 2022/6/23 16:24
*/
@Slf4j
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("成功退出处理器");
log.info("在成功退出之前,做一些资源的释放操作");
response.sendRedirect("/login.html");
}
}
- 修改SpringSecurity配置
// 配置退出登录的逻辑 http.logout() .logoutSuccessHandler(new MyLogoutSuccessHandler());
测试结果:
点击立即退出之后,后台退出处理器就会进行逻辑执行。
第十一、SpringSecuirty实现Remember-Me功能
SpringSecurity的Remember-Me功能就是用于项目第一次登录之后,可以保存自己的session时间,以便下一次规定的时间以内能直接免密登录。
- Remember-Me的配置类
/**
* @Description: 记住我功能配置
* @Author: huidou 惠豆
* @CreateTime: 2022/6/23 16:51
*/
@Configuration
public class RememberMConfig {
@Autowired
private DataSource dataSource;
/**
* 实现记住我配置
* @return
*/
@Bean
public PersistentTokenRepository getPersistentTokenRepository() {
// 实例化一个JdbcTokenRepositoryImpl持久层对象
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
// 将数据源database放在持久层对象中
jdbcTokenRepository.setDataSource(dataSource);
// 项目第一次启动的时候,需要将其创建表,非第一次的话,就需要进行关闭,否则报错。
jdbcTokenRepository.setCreateTableOnStartup(true);
return jdbcTokenRepository;
}
}
- 修改SpringSecurity配置
@Autowired private PersistentTokenRepository repository; @Autowired private UserDetailsService userDetailsService; // 配置记住我功能 http.rememberMe() .tokenValiditySeconds(60) // session过期时间,单位是:秒 .tokenRepository(repository) // 持久层对象 .userDetailsService(userDetailsService); // 用户认证逻辑
- 修改前端页面login
<!doctype html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录</title>
<link href="../static/css/styles.css" rel="stylesheet" >
</head>
<body>
<div class="htmleaf-container">
<div class="wrapper">
<div class="container">
<h1>Welcome</h1>
<form class="form" action="/login" method="post">
<!-- 设置一个隐藏域 -->
<!-- <input type="hidden" name="_csrf" th:value="${_csrf.token}" th:if="${_csrf}">-->
<input type="text" placeholder="用户名" name="username">
<input type="password" placeholder="密码" name="password">
<input type="checkbox" name="remember-me" value="true">记住我
<button type="submit" id="login-button">登录</button>
</form>
</div>
</div>
</div>
</body>
</html>
需要设置一个复选框checkbox,name的属性值必须是:remember-me value是:true测试结果:
登录成功之后,关闭浏览器,一份钟以内免密登录,一分钟以外需要密码登录。

