SpringSecurity 安全框架
是基于Spring的安全管理框架 ,为系统提供声明式安全访问框架功能
安全管理框架两个重要概念 分别是Authentication(认证) 和Authorization(授权)
核心功能:
用户认证
用户授权
攻击防护
创建第一个SpringSecurity项目:
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
导入依赖后0配置已经有安全框架作用
Using generated security password: 2145465d-8915-4275-835b-1b5f56378bbf
控制台会有密码 账号为user 登录后才能访问资源
2.退出
/loginout
3.自定义密码和账户
spring:
security:
user:
name: ry
password: 123456
自定义用户认证:
1.基于数据库编写数据访问层代码
2.配置密码解析器组件(passwordEncoder)
基础PasswordEncoder
/**
* @author 冉毓
*/
@Configuration
public class MyPasswordEncoder implements PasswordEncoder {
/**
* 对密码进行加密
* @param rawPassword
* @return 返回加密的密码
*/
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
/**
* 对密码进行校验
* @param rawPassword 原始密码
* @param encodedPassword 数据库已加密的密码
* @return 校验结果
*/
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return encode(rawPassword).equals(encodedPassword);
}
}
3.配置用户登录服务组件(UserDetil)
package com.example.springsecurityleanpart1.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.springsecurityleanpart1.entity.User;
import com.example.springsecurityleanpart1.mapper.UserMapper;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* @author 冉毓
*/
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名查询用户信息
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getUsername,username);
User user = userMapper.selectOne(wrapper);
System.out.println("查询-------------------------");
//如果用户为null 抛出异常
if (user == null){
throw new UsernameNotFoundException("用户不存在");
}
//如果用户不为null 返回用户信息
//返回的是子类 User
org.springframework.security.core.userdetails.User userDetails =
new org.springframework.security.core.userdetails.User
(user.getUsername(),
user.getPassword(),
//不需要验证 工具类
AuthorityUtils.NO_AUTHORITIES);
return userDetails;
}
}
4.自定义用户认证测试
认证安全强化:
1.用户表密码加密
2.密码加密器:
BCryptPasswordEncoder 是实现了PasswordEncoder接口
采用了哈希算法SHA-256 + 随机盐 +密钥
package com.example.springsecurityleanpart1.config;
import com.example.springsecurityleanpart1.service.impl.MyUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* @author 冉毓
*/
@Configuration
public class SecurityConfig {
/**
*使用子类加密方式
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public MyUserDetailsService myUserDetailsService(){
return new MyUserDetailsService();
}
}
自定义登录页面:
1.配置SecurityFilterChain 过滤链 放入容器中
2.具体配置
1.登录验证
//用户认证
http.formLogin(config->{
//自定义登录页面
config.loginPage("/user/login")
//登录处理器的映射地址
.loginProcessingUrl("/user/login")
//登录成功后跳转
.defaultSuccessUrl("/main")
.failureUrl("/loginFail");
//自定义用户名密码参数
// .usernameParameter("uname")
// .passwordParameter("pwd");
});
2.退出验证
//退出 登录配置
http.logout(config->{
config.logoutUrl("/user/logout");
// .logoutSuccessUrl("/user/login");
});
3.配置CSRF防御(不使用token)
http.csrf(configuration->{
configuration.disable();
});
完整代码:
@Configuration
public class SecurityConfig {
/**
*使用子类加密方式
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService myUserDetailsService(){
return new MyUserDetailsService();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
//用户认证
http.formLogin(config->{
//自定义登录页面
config.loginPage("/user/login")
//登录处理器的映射地址
.loginProcessingUrl("/user/login")
//登录成功后跳转
.defaultSuccessUrl("/main")
.failureUrl("/loginFail");
//自定义用户名密码参数
// .usernameParameter("uname")
// .passwordParameter("pwd");
});
//退出 登录配置
http.logout(config->{
config.logoutUrl("/user/logout");
// .logoutSuccessUrl("/user/login");
});
//参数配置用户认证和授权
http.authorizeHttpRequests(registry->{
//配置放行路径
registry
.requestMatchers("/login","/user/login","/css/**","/img/**","/loginFail").permitAll()
//对于系统中其他资源 需要认证
.anyRequest().authenticated();
});
http.csrf(configuration->{
configuration.disable();
});
return http.build();
}
}
注意!!!!
//登录成功后跳转 但默认跳转之前访问的地址?contine
// .defaultSuccessUrl("/main")
//强制定向所登录页面
.successForwardUrl("/main")
资源权限认证
基于配置类
1.配置类中对资源加权限
http.authorizeHttpRequests(registry->{
//配置放行路径
registry
.requestMatchers("/login","/user/login","/css/**","/img/**","/loginFail").permitAll()
//对于系统中其他资源 需要认证
.anyRequest().authenticated()
.requestMatchers("/order/manage").hasAuthority("订单管理")
.requestMatchers("/user/manage").hasAuthority("用户管理")
.requestMatchers("/shop/manage").hasAuthority("商品管理")
.requestMatchers("").hasAnyAuthority("订单管理","用户管理","商品管理");
});
hasAnyAuthority可以指定多个权限
2.给用户赋权
UserDetailsService 组件中加权
//模拟加权
List<String> authorities = new ArrayList<>();
authorities.add("订单管理");
authorities.add("商品管理");
//如果用户不为null 返回用户信息
//返回的是子类 User
org.springframework.security.core.userdetails.User userDetails =
new org.springframework.security.core.userdetails.User
(user.getUsername(),
user.getPassword(),
//加权
AuthorityUtils.createAuthorityList(authorities));
3.基于角色
http.authorizeHttpRequests(registry->{
//配置放行路径
registry
.requestMatchers("/login","/user/login","/css/**","/img/**","/loginFail").permitAll()
//对于系统中其他资源 需要认证
.requestMatchers("/order/manage").hasRole("manage")
.anyRequest().authenticated()
;
});
hasRole 加角色
//模拟加权
// List<String> authorities = new ArrayList<>();
// authorities.add("订单管理");
// authorities.add("商品管理");
//从数据库查询权限
List<String> authorities = permissionMapper.selectListByUid(user.getUid());
// 从数据库查角色
List<String> strings = roleMapper.selectListByUid(user.getUid());
for(String roles : strings){
authorities.add("ROLE_"+roles);
}
//如果用户不为null 返回用户信息
//返回的是子类 User
org.springframework.security.core.userdetails.User userDetails =
new org.springframework.security.core.userdetails.User
(user.getUsername(),
user.getPassword(),
//加权
AuthorityUtils.createAuthorityList(authorities));
return userDetails;
注:!!!这里 需要加 authorities.add(“ROLE_”+roles);才是加角色
否则只是加权限
基于注解:
@EnableMethodSecurity 注解 开启基于注解方式
@RequestMapping("/order/manage")
@ResponseBody
@PreAuthorize("hasAnyRole('vip')")
public String orderManage(){
return "商城订单管理功能";
}
在 @PreAuthorize注解中使用同配置类加权 在接口上
扩展:
无权限跳转页面:
配置类中
//权限认证失败 跳转页面
http.exceptionHandling(config->{
config.accessDeniedPage("/user/noAuth");
});
网页记录功能:
1.开启springsecurity中 记住我功能
//开启记住我功能
http.rememberMe(config->{
// 有效时间
config.tokenValiditySeconds(360000)
.rememberMeParameter("remember");
});
2.在复选框中的name设置为remember
<div class="checkbox mb-3">
<label>
<input type="checkbox" name="remember"> 记住我
</label>
</div>
Security整合Thymeleaf
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity6</artifactId>
</dependency>
命名空间:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>好货商城</title>
</head>
<body>
<h1 align="center">欢迎进入好货商城管理后台<span style="padding-left: 50px;font-size: 20px">
<label sec:authentication="principal.username"></label> <a href="/user/logout">退出登录</a>
</span></h1>
<hr>
<div sec:authorize="hasRole('common')">
<h3>普通用户</h3>
<ul>
<li><a href="/order/manage">订单管理</a></li>
</ul>
</div>
<div sec:authorize="hasRole('vip')">
<h3>VIP用户</h3>
<ul>
<li><a href="/order/manage">订单管理</a></li>
<li><a href="/shop/manage">店铺管理</a></li>
</ul>
</div>
<div sec:authorize="hasRole('manager')">
<h3>管理员</h3>
<ul>
<li><a href="/order/manage">订单管理</a></li>
<li><a href="/shop/manage">店铺管理</a></li>
<li><a href="/user/manage">系统用户管理</a></li>
</ul>
</div>
</body>
</html>
sec:authentication可以的到用户的信息和权限
sec:authorize可以根据用户权限判断
获取认证用户信息:
1.通过控制器HttpSession
@RequestMapping(value = "/user")
public String userManage(HttpSession session)
{
//获取security上下文
SecurityContext springSecurityContext = (SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT");
UserDetails userDetails = (UserDetails) springSecurityContext.getAuthentication().getPrincipal();
userDetails.getUsername();
return "userManage";
}
2.通过SecurityContextHolder
@RequestMapping(value = "/user")
public String userManage(Authentication authentication)
{
SecurityContext context = SecurityContextHolder.getContext();
Authentication contextAuthentication = context.getAuthentication();
UserDetails userDetails = (UserDetails) contextAuthentication.getPrincipal();
System.out.println(userDetails.getUsername());
return "userManage";
}
3.Authentication 对象
@RequestMapping(value = "/user")
public String userManage(Authentication authentication)
{
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
System.out.println(userDetails.getUsername());
return "userManage";
}
CSRF
Cross-site request forgery:跨域请求伪造
1.HTTP Referer记录了该HTTP请求的来源地址。
2.验证码
3.CSRF Token令牌(Spring Security)
(1)用户登录后生成Token,放在用户的Session中
(2)在页面表单中附上Token参数
(3)用户发送请求都需要携带这个Token参数
Security中CSRF防护:
默认开启CSRF项目中所有POST PUT DELETE都会被拦截 需要携带Token参数
//注意修改之前的代码 注释掉以下
// http.csrf(configuration->{
// configuration.disable();
// });
方法一
表单加隐藏域
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
表单中使用th:action
<form class="form-signin" method="post" th:action="@{/user/login}">
但是!!!!! 退出登录需要改为Post请求
:跨域请求伪造
1.HTTP Referer记录了该HTTP请求的来源地址。
2.验证码
3.CSRF Token令牌(Spring Security)
(1)用户登录后生成Token,放在用户的Session中
(2)在页面表单中附上Token参数
(3)用户发送请求都需要携带这个Token参数
Security中CSRF防护:
默认开启CSRF项目中所有POST PUT DELETE都会被拦截 需要携带Token参数
//注意修改之前的代码 注释掉以下
// http.csrf(configuration->{
// configuration.disable();
// });
方法一
表单加隐藏域
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
表单中使用th:action
<form class="form-signin" method="post" th:action="@{/user/login}">
但是!!!!! 退出登录需要改为Post请求