SpringSecurity入门应用
前言:
本文不着重讲述SpringSecurity相关概念及其原理,而是致力于总结一些实战应用场景
基于数据库的登录认证
1.创建数据库表
-- 创建数据库
CREATE DATABASE `security-demo`;
USE `security-demo`;
-- 创建用户表
CREATE TABLE `user`(
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(50) DEFAULT NULL ,
`password` VARCHAR(500) DEFAULT NULL,
`enabled` BOOLEAN NOT NULL
);
-- 唯一索引
CREATE UNIQUE INDEX `user_username_uindex` ON `user`(`username`);
-- 插入用户数据(密码是 "abc" )
INSERT INTO `user` (`username`, `password`, `enabled`) VALUES
('admin', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Helen', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE),
('Tom', '{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW', TRUE);
2.创建实体类
@Data
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private Boolean enabled;
}
3.定义DBUserDetailsManager
DBUserDetailsManager要实现UserDetailsManager和UserDetailsPasswordService并重写其抽象方法。
@Component
public class DBUserDetailsManager implements UserDetailsManager, UserDetailsPasswordService {
@Autowired
private UserService userService;
@Override
public UserDetails updatePassword(UserDetails user, String newPassword) {
return null;
}
/**
* 根据用户名加载用户详情
* @param username 用户名
* @return 用户详情
* @throws UsernameNotFoundException 如果用户不存在
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.lambdaQuery().eq(User::getUsername, username).one();
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
// 返回自定义的安全用户对象,将数据库实体包装在内
return new SecurityUser(user);
}
@Override
public void createUser(UserDetails user) {
}
@Override
public void updateUser(UserDetails user) {
}
@Override
public void deleteUser(String username) {
}
@Override
public void changePassword(String oldPassword, String newPassword) {
}
@Override
public boolean userExists(String username) {
return false;
}
}
4.编辑配置
配置要根据项目实际情况进行选择,如果不主动实现filterChain,框架也会给出一个默认实现
@Configuration
@EnableMethodSecurity // 开启方法级权限控制
public class WebSecurityConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 1. 配置请求授权
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/user/add", "/login", "/error").permitAll() // 明确放行注册、登录和错误页
.requestMatchers("/static/**", "/css/**", "/js/**").permitAll() // 放行静态资源
.anyRequest().authenticated() // 剩下的所有请求都需要登录
)
// 2. 自定义表单登录逻辑
.formLogin(form -> form
.defaultSuccessUrl("/", true) // 登录成功后强制跳转到首页
.permitAll()
)
// 3. 配置注销功能
.logout(logout -> logout
.logoutUrl("/logout") // 注销接口地址
.logoutSuccessUrl("/login?logout") // 注销成功后跳转回登录页并带上提示
.invalidateHttpSession(true) // 销毁 Session .deleteCookies("JSESSIONID") // 删除 Cookie .permitAll()
)
// 4. 防护配置
.csrf(csrf -> csrf.disable()); // Demo 阶段保持禁用以便 API 测试
return http.build();
}
}
密码加密
1.配置passwordEncoder
config中添加PasswordEncoder,指明BCrypt加密算法
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
2.加密密码
passwordEncoder.encode(user.getPassword())
添加用户
1.自定义SecurityUser 类
自定义一个 SecurityUser 类作为包装类既能保持实体类 User 的纯粹性(不依赖 Spring Security 框架),也能在安全上下文中携带完整的业务数据。
public class SecurityUser implements UserDetails {
@Getter
private final User user; // 持有原始的数据库实体对象
public SecurityUser(User user) {
this.user = user;
}
@Override
public String getUsername() {
return user.getUsername();
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
// 后续可以在此根据 user 角色添加权限
return new ArrayList<>();
}
@Override
public boolean isAccountNonExpired() { return true; }
@Override
public boolean isAccountNonLocked() { return true; }
@Override
public boolean isCredentialsNonExpired() { return true; }
@Override
public boolean isEnabled() {
return user.getEnabled() != null && user.getEnabled();
}
}
2.重写DBUserDetailsManager中方法
@Override
public void createUser(UserDetails userDetails) {
if (userDetails instanceof SecurityUser) {
// 如果是自定义类型,直接获取内部持有实体保存
userService.save(((SecurityUser) userDetails).getUser());
} else {
// 兜底逻辑:处理标准的 UserDetails 对象
User user = new User();
user.setUsername(userDetails.getUsername());
user.setPassword(userDetails.getPassword());
user.setEnabled(userDetails.isEnabled());
userService.save(user);
}
}
@Override
public void updateUser(UserDetails user) {
// 根据业务需求实现,例如:
// userService.updateById(convertToEntity(user));
}
@Override
public void deleteUser(String username) {
userService.lambdaUpdate().eq(User::getUsername, username).remove();
}
@Override
public void changePassword(String oldPassword, String newPassword) {
}
@Override
public boolean userExists(String username) {
return userService.lambdaQuery().eq(User::getUsername, username).exists();
}
3.UserController中实现接口并调用已重写的方法
@RequiredArgsConstructor
@RestController
@RequestMapping("/user")
public class UserController {
private final UserService userService;
private final UserDetailsManager userDetailsManager;
private final PasswordEncoder passwordEncoder;
@PostMapping("/add")
public String add(@RequestBody User user) {
// 1. 校验用户是否存在
if (userDetailsManager.userExists(user.getUsername())) {
return "添加失败,用户名已存在";
}
// 2. 加密密码
user.setPassword(passwordEncoder.encode(user.getPassword()));
// 3. 使用自定义的 SecurityUser 包装实体类
SecurityUser securityUser = new SecurityUser(user);
// 4. 通过标准接口保存用户
try {
userDetailsManager.createUser(securityUser);
return "添加成功";
} catch (Exception e) {
log.error("添加用户失败", e);
return "添加失败:" + e.getMessage();
}
}
}
前后端响应
在用户登录成功,失败或是注销时返回给前端对应的JSON数据(此处为了方便演示,把统一响应结果封装在Map集合中,实际开发还是以自定义结果对象为好)
登录成功
编写实现了AuthenticationSuccessHandler的类,用户登录成功时会调用该类中的onAuthenticationSuccess方法
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//获取用户身份信息
Object principal = authentication.getPrincipal();
//创建结果对象
HashMap result = new HashMap();
result.put("code", 0);
result.put("message", "登录成功");
result.put("data", principal);
//转换成json字符串
String json = JSON.toJSONString(result);
//返回响应
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
}
}
登陆失败
编写实现了AuthenticationFailureHandler的类,用户登录失败时会调用该类中的onAuthenticationFailure方法
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
//获取错误信息
String localizedMessage = exception.getLocalizedMessage();
//创建结果对象
HashMap result = new HashMap();
result.put("code", -1);
result.put("message", localizedMessage);
//转换成json字符串
String json = JSON.toJSONString(result);
//返回响应
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
}
}
注销
编写实现了LogoutSuccessHandler的类,用户注销时会调用该类中的onLogoutSuccess方法
@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
//创建结果对象
HashMap result = new HashMap();
result.put("code", 0);
result.put("message", "注销成功");
//转换成json字符串
String json = JSON.toJSONString(result);
//返回响应
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(json);
}
}
挂载
目前位置以上三个handler只是进行了实现,但是为被挂载使用,挂载方法:
@Configuration
@EnableMethodSecurity // 开启方法级权限控制
public class WebSecurityConfig {
private final MyAuthenticationSuccessHandler successHandler;
private final MyLogoutSuccessHandler logoutSuccessHandler;
private final MyAuthenticationFailureHandler failureHandler;
// 构造器注入
public WebSecurityConfig(MyAuthenticationSuccessHandler successHandler, MyLogoutSuccessHandler logoutSuccessHandler, MyAuthenticationFailureHandler failureHandler) {
this.successHandler = successHandler;
this.logoutSuccessHandler = logoutSuccessHandler;
this.failureHandler = failureHandler;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.formLogin(form -> form
.successHandler(successHandler) // 启用成功处理器
.failureHandler(failureHandler) // 启用失败处理器(
);
http.logout(logout -> logout
.logoutSuccessHandler(logoutSuccessHandler) // 启用注销处理器
.invalidateHttpSession(true) // 销毁 Session
);
return http.build();
}
}
先通过构造器注入handler,再在http构造时对应的lambda表达式处挂载。
跨域
跨域全称是跨域资源共享(Cross-Origin Resources Sharing,CORS),它是浏览器的保护机制,只允许网页请求统一域名下的服务,同一域名指=>协议、域名、端口号都要保持一致,如果有一项不同,那么就是跨域请求。在前后端分离的项目中,需要解决跨域的问题。
在SpringSecurity中解决跨域很简单,在配置文件中添加如下配置即可
//跨域
http.cors(withDefaults());
身份认证
基本概念

在Spring Security框架中,SecurityContextHolder、SecurityContext、Authentication、Principal和Credential是一些与身份验证和授权相关的重要概念。它们之间的关系如下:
- SecurityContextHolder:SecurityContextHolder 是 Spring Security 存储已认证用户详细信息的地方。
- SecurityContext:SecurityContext 是从 SecurityContextHolder 获取的内容,包含当前已认证用户的 Authentication 信息。
- Authentication:Authentication 表示用户的身份认证信息。它包含了用户的Principal、Credential和Authority信息。
- Principal:表示用户的身份标识。它通常是一个表示用户的实体对象,例如用户名。Principal可以通过Authentication对象的getPrincipal()方法获取。
- Credentials:表示用户的凭证信息,例如密码、证书或其他认证凭据。Credential可以通过Authentication对象的getCredentials()方法获取。
- GrantedAuthority:表示用户被授予的权限
总结起来,SecurityContextHolder用于管理当前线程的安全上下文,存储已认证用户的详细信息,其中包含了SecurityContext对象,该对象包含了Authentication对象,后者表示用户的身份验证信息,包括Principal(用户的身份标识)和Credential(用户的凭证信息)。
在Controller中获取用户信息
IndexController:
@RestController
public class IndexController {
/**
* 获取当前登录用户的详细信息
* 用于学习 SecurityContextHolder 相关 API
*/ @GetMapping("/user/info")
public String getUserInfo() {
// 1. 从 SecurityContextHolder 中获取认证对象
// SecurityContextHolder 是一个工具类,内部通过 ThreadLocal 存储了当前线程的认证信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 2. 获取用户身份主体 (在你的项目中,你自定义的 SecurityUser )
Object principal = authentication.getPrincipal();
// 3. 获取用户权限信息 (如角色 ROLE_ADMIN 等,均为自定义)
Object authorities = authentication.getAuthorities();
// 4. 获取认证状态 (是否已登录)
boolean isAuthenticated = authentication.isAuthenticated();
// 为了方便查看,我们将这些信息封装到 Map 中返回
Map<String, Object> info = new java.util.HashMap<>();
info.put("principal", principal);
info.put("authorities", authorities);
info.put("isAuthenticated", isAuthenticated);
info.put("name", authentication.getName()); // 获取用户名
// 使用 Fastjson2 手动转换为 JSON 字符串
return JSON.toJSONString(info);
}
}
会话并发处理
简单来说就是,后登录的账号会使先登录的账号失效。
实现处理器接口SessionInformationExpiredStrategy
@Component
public class MySessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
// 1. 获取响应对象
HttpServletResponse response = event.getResponse();
// 2. 创建你要返回的 JSON 结果
Map<String, Object> result = new HashMap<>();
result.put("code", 401);
result.put("message", "您的账号已在其他地方登录,当前会话已失效");
// 3. 将结果转换为 JSON 并写入响应
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println(JSON.toJSONString(result));
}
}
config中挂载配置
http.sessionManagement(session -> session
.maximumSessions(1) // 限制同一个账号只能 1 个登录
.expiredSessionStrategy(sessionInformationExpiredStrategy) // 挂载自定义策略
);
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)