SpringSecurity入门实战应用场景

 更新时间:2026年04月09日 09:19:38   作者:漫霂  
本文总结了基于SpringSecurity的数据库登录认证实战,包括创建数据库表、实体类、DBUserDetailsManager、配置、密码加密、添加用户、自定义SecurityUser、前后端响应处理、配置handler、跨域解决、身份认证概念解析及会话并发处理等内容,感兴趣的朋友一起看看吧

本文不着重讲述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是一些与身份验证和授权相关的重要概念。它们之间的关系如下:

  1. SecurityContextHolder:SecurityContextHolder 是 Spring Security 存储已认证用户详细信息的地方。
  2. SecurityContext:SecurityContext 是从 SecurityContextHolder 获取的内容,包含当前已认证用户的 Authentication 信息。
  3. Authentication:Authentication 表示用户的身份认证信息。它包含了用户的Principal、Credential和Authority信息。
  4. Principal:表示用户的身份标识。它通常是一个表示用户的实体对象,例如用户名。Principal可以通过Authentication对象的getPrincipal()方法获取。
  5. Credentials:表示用户的凭证信息,例如密码、证书或其他认证凭据。Credential可以通过Authentication对象的getCredentials()方法获取。
  6. 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) // 挂载自定义策略  
        );

到此这篇关于SpringSecurity入门实战应用场景的文章就介绍到这了,更多相关SpringSecurity登录认证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Spring Boot Mybatis 搞反向工程的步骤

    使用Spring Boot Mybatis 搞反向工程的步骤

    这篇文章主要介绍了使用Spring Boot Mybatis 搞反向工程的步骤,帮助大家更好的理解和使用spring boot框架,感兴趣的朋友可以了解下
    2021-01-01
  • java实现登录注册界面

    java实现登录注册界面

    这篇文章主要为大家详细介绍了java实现登录注册界面,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-06-06
  • Java wait和notifyAll实现简单的阻塞队列

    Java wait和notifyAll实现简单的阻塞队列

    这篇文章主要介绍了Java wait和notifyAll实现简单的阻塞队列,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • SSM框架通过mybatis-generator自动生成代码(推荐)

    SSM框架通过mybatis-generator自动生成代码(推荐)

    这篇文章主要介绍了SSM框架通过mybatis-generator自动生成代码,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2017-11-11
  • RocketMQ消息队列实现随机消息发送当做七夕礼物

    RocketMQ消息队列实现随机消息发送当做七夕礼物

    这篇文章主要为大家介绍了RocketMQ消息队列实现随机消息发送当做七夕礼物,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Java都有哪些创建线程的方法

    Java都有哪些创建线程的方法

    这篇文章主要介绍了Java都有哪些创建线程的方法,文章分享Java创建线程得几种方法及推荐使用哪种方法,下面详细内容需要的小伙伴可以参考一下
    2022-05-05
  • Java中将bean放入Spring容器中的几种方式详解

    Java中将bean放入Spring容器中的几种方式详解

    这篇文章主要介绍了Java中将bean放入Spring容器中的几种方式详解,在Spring框架中,有多种方式可以将Bean(即对象)放入Spring容器中,今天我们就来详细说一下这几种方式,需要的朋友可以参考下
    2023-07-07
  • springBoot 插件工具热部署 Devtools的步骤详解

    springBoot 插件工具热部署 Devtools的步骤详解

    这篇文章主要介绍了springBoot 插件工具 热部署 Devtools,本文分步骤给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • Java实现Executors类创建常见线程池

    Java实现Executors类创建常见线程池

    本文主要介绍了Java实现Executors类创建常见线程池,在Java中,可以通过Executors工厂类提供四种常见类型的线程池,下面就来介绍一下这四种的方法实现,感兴趣的可以了解一下
    2023-11-11
  • java如何替换word/doc文件中的内容

    java如何替换word/doc文件中的内容

    docx格式的文件本质上是一个XML文件,只要用占位符在指定的地方标记,然后替换掉标记出的内容,这篇文章主要介绍了java替换word/doc文件中的内容,需要的朋友可以参考下
    2023-06-06

最新评论