网站加在线qq,上海多语种建站,wordpress 关注微博,外贸网站建设公司咨询** 第 一 步 #xff1a; 搭 建 Spring Boot 项 目 #xff0c; 引 入 Spring Security **** 小 明 的 需 求 **“ 先 让 网 站 有 个 最 基 础 的 登 录 功 能 #xff0c; 能 拦 住 未 登 录 的 用 户 。 ”** 实 操 步 骤 **** 创 建 Spring Boot 项 目 ** #xff1a; …** 第 一 步 搭 建 Spring Boot 项 目 引 入 Spring Security **** 小 明 的 需 求 **“ 先 让 网 站 有 个 最 基 础 的 登 录 功 能 能 拦 住 未 登 录 的 用 户 。 ”** 实 操 步 骤 **** 创 建 Spring Boot 项 目 ** 用 https://start.spring.io/ 选 Spring Web 搭 Web 服 务 、 Spring Security 安 全 框 架 、 Spring Data JPA 操 作 数 据 库 、 MySQL Driver 数 据 库 驱 动 。** 添 加 依 赖 ** pom.xml 核 心 部 分 dependenciesdependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-security/artifactId !-- Spring Security 依 赖 --/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-jpa/artifactId/dependencydependencygroupIdcom.mysql/groupIdartifactIdmysql-connector-j/artifactId/dependency/dependencies** 运 行 项 目 ** 启 动 Spring Boot 访 问 http://localhost:8080 会 自 动 跳 转 到 Spring Security 的 默 认 登 录 页 。** 现 象 与 原 理 **** 默 认 用 户 ** 控 制 台 会 打 印 一 行 密 码 格 式 如 Using generated security password: abcdef12-3456-7890-abcd-ef1234567890 用 户 名 固 定 为 user 。** 原 理 ** Spring Security 自 动 配 置 了 默 认 的 InMemoryUserDetailsManager 内 存 用 户 存 储 生 成 随 机 密 码 。 所 有 接 口 默 认 需 要 USER 角 色 才 能 访 问 。 小 明 的 疑 问 “ 这 密 码 每 次 启 动 都 变 而 且 用 户 不 能 自 己 注 册 得 改 ”** 第 二 步 自 定 义 用 户 存 储 —— 从 内 存 到 数 据 库 **** 小 明 的 需 求 **“ 用 户 信 息 存 在 自 己 的 数 据 库 里 支 持 注 册 、 登 录 密 码 用 BCrypt 加 密 。 ”** 核 心 概 念 **** UserDetailsService ** Spring Security 的 “ 用 户 查 询 接 口 ” 开 发 者 实 现 它 告 诉 框 架 “ 怎 么 从 数 据 库 查 用 户 ” 。** UserDetails ** “ 用 户 信 息 封 装 类 ” 包 含 用 户 名 、 密 码 、 角 色 、 账 户 是 否 过 期 等 信 息 。** PasswordEncoder ** “ 密 码 加 密 器 ” 用 BCrypt 算 法 把 明 文 密 码 变 成 哈 希 值 存 储 。** 实 操 步 骤 **** 建 用 户 表 ** MySQL CREATE TABLE users (id BIGINT AUTO_INCREMENT PRIMARY KEY,username VARCHAR(50) UNIQUE NOT NULL, -- 用 户 名password VARCHAR(100) NOT NULL, -- BCrypt 哈 希 后 的 密 码role VARCHAR(20) NOT NULL -- 角 色 ROLE_VISITOR / ROLE_EDITOR / ROLE_ADMIN);** 实 现 UserDetailsService ** 查 数 据 库 用 户 Servicepublic class CustomUserDetailsService implements UserDetailsService {Autowired private UserRepository userRepo; // JPA 操 作 users 表Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1. 查 数 据 库 用 户User user userRepo.findByUsername(username).orElseThrow(() - new UsernameNotFoundException(用户不存在));// 2. 封 装 成 UserDetails 对 象 Spring Security 认 识 的 格 式 return org.springframework.security.core.userdetails.User.builder().username(user.getUsername()).password(user.getPassword()) // 存 的 是 BCrypt 哈 希 值.roles(user.getRole().replace(ROLE_, )) // 角 色 去 掉 ROLE_ 前 缀.build();}}** 配 置 PasswordEncoder ** 用 BCrypt 加 密 Configurationpublic class SecurityConfig {Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(); // BCrypt 加 密 器 自 带 随 机 盐}}** 注 册 用 户 ** 比 如 小 明 自 己 注 册 为 管 理 员 RestControllerpublic class RegisterController {Autowired private UserRepository userRepo;Autowired private PasswordEncoder passwordEncoder;PostMapping(/register)public String register(String username, String rawPassword, String role) {User user new User();user.setUsername(username);user.setPassword(passwordEncoder.encode(rawPassword)); // 明 文 密 码 → BCrypt 哈 希user.setRole(ROLE_ role.toUpperCase()); // 角 色 加 ROLE_ 前 缀 Spring Security 约 定 userRepo.save(user);return 注册成功;}}** 效 果 **小 明 用 POST /register?usernamexiaomingrawPassword123456roleadmin 注 册 后 数 据 库 users 表 会 存 一 条 记 录 password 字 段 是 BCrypt 哈 希 值 如 $2a$10$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 。 登 录 时 框 架 会 用 BCrypt 验 证 输 入 密 码 是 否 匹 配 。 小 明 的 体 会 “ UserDetailsService 就 像 图 书 馆 的 ‘ 查 阅 员 ’ 框 架 告 诉 他 ‘ 找 小 明 ’ 他 就 去 数 据 库 把 小 明 的 书 用 户 信 息 拿 回 来 。 ”** 第 三 步 配 置 权 限 控 制 —— 谁 能 干 啥 **** 小 明 的 需 求 **“ 游 客 只 能 看 照 片 编 辑 能 上 传 管 理 员 能 删 除 。 ”** 核 心 概 念 **** HttpSecurity ** Spring Security 的 “ Web 安 全 配 置 类 ” 用 来 设 置 URL 访 问 权 限 、 登 录 / 注 销 页 、 CSRF 等 。** PreAuthorize ** “ 方 法 级 权 限 注 解 ” 直 接 在 Controller 方 法 上 标 注 需 要 的 角 色 。** 实 操 步 骤 **** 配 置 URL 访 问 权 限 ** 在 SecurityConfig 中 ConfigurationEnableWebSecurity // 开 启 Web 安 全 配 置public class SecurityConfig {Autowired private CustomUserDetailsService userDetailsService;Autowired private PasswordEncoder passwordEncoder;Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(auth - auth.requestMatchers(/photos/**).permitAll() // 公 开 所 有 人 能 看 照 片.requestMatchers(/upload/**).hasRole(EDITOR) // 上 传 需 EDITOR 角 色.requestMatchers(/delete/**).hasRole(ADMIN) // 删 除 需 ADMIN 角 色.anyRequest().authenticated() // 其 他 接 口 需 登 录).formLogin(form - form // 配 置 登 录 页.loginPage(/my-login) // 自 定 义 登 录 页 下 节 讲 .defaultSuccessUrl(/home) // 登 录 成 功 跳 转).logout(logout - logout // 配 置 注 销.logoutUrl(/logout).logoutSuccessUrl(/photos));return http.build();}// 配 置 认 证 管 理 器 用 自 定 义 UserDetailsService 和 PasswordEncoder Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}}** 用 PreAuthorize 做 方 法 级 权 限 ** 更 精 细 控 制 RestControllerpublic class PhotoController {// 游 客 能 看GetMapping(/photos)public ListPhoto listPhotos() { ... }// 编 辑 能 上 传 PreAuthorize 需 开 启 EnableMethodSecurity PostMapping(/upload)PreAuthorize(hasRole(EDITOR)) // 直 接 标 注 需 EDITOR 角 色public String uploadPhoto(MultipartFile file) { ... }// 管 理 员 能 删 除DeleteMapping(/delete/{id})PreAuthorize(hasRole(ADMIN) or hasRole(EDITOR)) // 管 理 员 或 编 辑 都 能 删public String deletePhoto(PathVariable Long id) { ... }}** 开 启 PreAuthorize 支 持 ** 在 SecurityConfig 上 加 EnableMethodSecurity 。** 效 果 **访 问 /upload 时 若 用 户 是 ROLE_VISITOR 会 被 拦 截 并 返 回 403 错 误 权 限 不 足 用 PreAuthorize 的 方 法 框 架 会 在 执 行 前 检 查 用 户 角 色 不 符 合 则 抛 异 常 。 小 明 的 体 会 “ HttpSecurity 像 ‘ 门 卫 ’ 管 URL 级 的 大 门 PreAuthorize 像 ‘ 房 间 锁 ’ 管 方 法 级 的 小 门 双 保 险 ”** 第 四 步 自 定 义 登 录 页 、 注 销 与 记 住 我 **** 小 明 的 需 求 **“ 默 认 登 录 页 太 丑 想 用 自 己 的 加 个 ‘ 记 住 我 ’ 功 能 关 闭 浏 览 器 再 打 开 还 是 登 录 状 态 。 ”** 实 操 步 骤 **** 自 定 义 登 录 页 ** 写 一 个 HTML 页 面 my-login.html 放 resources/templates 目 录 包 含 username 、 password 输 入 框 和 remember-me 复 选 框 在 SecurityConfig 中 配 置 .loginPage(/my-login) 并 指 定 处 理 登 录 的 URL .formLogin(form - form.loginPage(/my-login) // 自 定 义 登 录 页 路 径.loginProcessingUrl(/do-login) // 处 理 登 录 的 URL 框 架 自 动 处 理 .defaultSuccessUrl(/home).failureUrl(/my-login?error) // 登 录 失 败 跳 转)** 开 启 “ 记 住 我 ” 功 能 ** 在 SecurityConfig 中 配 置 .rememberMe() .rememberMe(remember - remember.key(xiaoming-secret-key) // 自 定 义 密 钥 防 篡 改 .tokenValiditySeconds(7 * 24 * 3600) // 记 住 我 有 效 期 7 天)在 自 定 义 登 录 页 加 “ 记 住 我 ” 复 选 框 input typecheckbox nameremember-me 记住我** 自 定 义 注 销 页 ** 在 SecurityConfig 中 配 置 .logout() 默 认 注 销 URL 是 /logout 可 自 定 义 .logout(logout - logout.logoutUrl(/my-logout) // 自 定 义 注 销 URL.logoutSuccessUrl(/photos) // 注 销 成 功 跳 转.invalidateHttpSession(true) // 注 销 时 清 空 Session.deleteCookies(JSESSIONID) // 删 除 Cookie)** 效 果 **访 问 需 登 录 的 页 面 时 跳 转 到 小 明 自 己 写 的 my-login.html 勾 选 “ 记 住 我 ” 后 关 闭 浏 览 器 7 天 内 再 访 问 网 站 仍 是 登 录 状 态 点 击 注 销 链 接 清 空 Session 和 Cookie 跳 回 公 开 照 片 页 。 小 明 的 体 会 “ 自 定 义 登 录 页 让 网 站 更 好 看 ‘ 记 住 我 ’ 像 ‘ 长 期 饭 卡 ’ 不 用 每 次 都 刷 临 时 卡 。 ”** 第 五 步 集 成 JWT —— 支 持 APP 与 微 服 务 **** 小 明 的 需 求 **“ 我 做 了 个 手 机 APP 用 Session - Cookie 不 方 便 想 用 JWT Token 做 认 证 。 ”** 核 心 概 念 **** JWT ** “ 自 带 信 息 的 身 份 证 ” 服 务 器 不 存 Session 用 户 登 录 后 拿 Token 后 续 请 求 带 Token 即 可 。** JwtAuthenticationFilter ** 自 定 义 过 滤 器 从 Authorization 头 取 JWT 验 证 后 设 置 Security Context 。** 实 操 步 骤 **** 添 加 JWT 依 赖 ** dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-api/artifactIdversion0.11.5/version/dependencydependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-impl/artifactIdversion0.11.5/versionscoperuntime/scope/dependencydependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-jackson/artifactIdversion0.11.5/versionscoperuntime/scope/dependency** 写 JWT 工 具 类 ** 生 成 / 验 证 Token Componentpublic class JwtUtil {Value(${jwt.secret}) private String secret; // 配 置 在 application.properties 中Value(${jwt.expiration}) private long expiration; // Token 有 效 期 如 3600000 毫 秒 // 生 成 Tokenpublic String generateToken(UserDetails userDetails) {MapString, Object claims new HashMap();claims.put(roles, userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()));return Jwts.builder().setClaims(claims).setSubject(userDetails.getUsername()).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() expiration)).signWith(SignatureAlgorithm.HS512, secret) // 用 HS512 算 法 签 名.compact();}// 验 证 Token 并 取 用 户 名public String getUsernameFromToken(String token) {return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();}}** 写 JWT 认 证 过 滤 器 ** public class JwtAuthenticationFilter extends OncePerRequestFilter {Autowired private JwtUtil jwtUtil;Autowired private CustomUserDetailsService userDetailsService;Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {// 1. 从 Header 取 TokenString token request.getHeader(Authorization);if (token ! null token.startsWith(Bearer )) {token token.substring(7);// 2. 验 证 Token 取 用 户 名String username jwtUtil.getUsernameFromToken(token);if (username ! null SecurityContextHolder.getContext().getAuthentication() null) {// 3. 查 用 户 信 息 设 置 Security ContextUserDetails userDetails userDetailsService.loadUserByUsername(username);UsernamePasswordAuthenticationToken auth new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());SecurityContextHolder.getContext().setAuthentication(auth);}}chain.doFilter(request, response); // 继 续 处 理 请 求}}** 修 改 SecurityConfig 用 JWT 替 换 Session ** Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.csrf(csrf - csrf.disable()) // JWT 无 状 态 关 闭 CSRF.sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 无 Session.authorizeHttpRequests(auth - auth.requestMatchers(/api/photos/**).permitAll().requestMatchers(/api/upload/**).hasRole(EDITOR).anyRequest().authenticated()).addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) // 加 JWT 过 滤 器.formLogin(form - form.disable()) // 禁 用 默 认 表 单 登 录 APP 用 JWT .logout(logout - logout.disable()); // 禁 用 默 认 注 销return http.build();}** 效 果 **APP 用 户 登 录 时 调 POST /api/login 自 定 义 接 口 验 证 用 户 名 密 码 后 返 JWT 后 续 APP 请 求 API 时 在 Authorization 头 带 Bearer JWT 框 架 自 动 认 证 。 小 明 的 体 会 “ JWT 像 ‘ 电 子 通 行 证 ’ APP 拿 着 它 就 能 畅 通 无 阻 服 务 器 不 用 记 住 谁 来 过 轻 松 支 持 多 端 ”** 第 六 步 深 入 OAuth2 第 三 方 登 录 与 安 全 防 护 **** 小 明 的 需 求 **“ 想 让 用 户 用 GitHub 账 号 直 接 登 录 还 要 防 CSRF 、 XSS 攻 击 。 ”** 核 心 概 念 **** OAuth2 ** “ 第 三 方 登 录 协 议 ” 用 户 授 权 GitHub 告 诉 小 明 的 网 站 “ 这 是 我 ” 无 需 注 册 。** Spring Security OAuth2 Client ** 框 架 内 置 OAuth2 客 户 端 支 持 GitHub 、 Google 等 登 录 。** 实 操 步 骤 **** 配 置 GitHub OAuth2 登 录 ** 在 GitHub 开 发 者 设 置 中 创 建 OAuth App 获 取 client-id 和 client-secret 在 application.properties 中 配 置 spring.security.oauth2.client.registration.github.client-id你的client-idspring.security.oauth2.client.registration.github.client-secret你的client-secretspring.security.oauth2.client.registration.github.scopeuser:email # 申 请 邮 箱 权 限在 SecurityConfig 中 开 启 OAuth2 登 录 .oauth2Login(oauth2 - oauth2.loginPage(/my-login) // 自 定 义 登 录 页 加 “ GitHub 登 录 ” 按 钮.defaultSuccessUrl(/home))** 安 全 防 护 ** 框 架 默 认 开 启 无 需 额 外 配 置 ** CSRF 防 护 ** 默 认 开 启 框 架 自 动 生 成 CSRF 令 牌 表 单 提 交 时 验 证 ** XSS 防 护 ** Spring Boot 默 认 开 启 HTML 转 义 防 注 入 脚 本 ** 会 话 固 定 攻 击 防 护 ** 登 录 后 自 动 更 换 SessionID 。** 效 果 **小 明 的 登 录 页 多 了 个 “ 用 GitHub 登 录 ” 按 钮 点 击 后 跳 转 GitHub 授 权 授 权 成 功 后 自 动 登 录 网 站 框 架 自 动 挡 住 CSRF 攻 击 即 使 黑 客 诱 骗 小 明 点 链 接 也 无 法 操 作 。 小 明 的 体 会 “ OAuth2 像 ‘ 找 朋 友 作 证 ’ 让 GitHub 帮 忙 证 明 ‘ 这 是 小 明 ’ 框 架 的 安 全 防 护 像 ‘ 隐 形 铠 甲 ’ 平 时 感 觉 不 到 关 键 时 刻 能 挡 刀 。 ”** 小 明 的 学 习 总 结 **通 过 这 几 步 小 明 彻 底 掌 握 了 Spring Security 配 Spring Boot 的 核 心 用 法 ** 从 默 认 配 置 入 门 ** 观 察 框 架 自 动 生 成 的 登 录 / 权 限 逻 辑 ** 自 定 义 UserDetailsService 和 PasswordEncoder ** 连 接 自 己 的 数 据 库 用 BCrypt 加 密 密 码 ** 用 HttpSecurity 和 PreAuthorize 配 置 权 限 ** 实 现 URL 级 和 方 法 级 控 制 ** 自 定 义 登 录 / 注 销 页 、 加 “ 记 住 我 ” ** 优 化 用 户 体 验 ** 集 成 JWT ** 支 持 APP 和 微 服 务 无 状 态 认 证 ** 用 OAuth2 实 现 第 三 方 登 录 ** 开 启 框 架 默 认 安 全 防 护 。