SpringBoot -- 集成Spring Security (二)
本文介绍了Spring Security框架在Spring Boot项目中的核心应用。主要内容包括:1)框架功能概述(认证、授权、防护);2)核心配置实现,涉及安全拦截规则、用户认证、密码加密(推荐BCrypt)和登录控制;3)关键代码示例,如WebSecurityConfig配置类、自定义UserDetailsService认证逻辑、基于注解的权限控制(@PreAuthorize);4)数据库集
前言:看前了解
强烈建议看这个之前一定要把:Spring-- Spring Security(一)-CSDN博客这篇博客的知识点4看了 重点是这个图解以及解析
1. 了解
UserDetailsService
本篇的
SpringDataUserDetailsService就是继承的UserDetailsService。2. 了解
UserDetailsService的loadUserByUsername方法
本篇的
SpringDataUserDetailsService就是继承UserDetailsService并重写了loadUserByUsername方法,用于根据用户名查询用户信息。3. 了解
UserDetails是啥
UserDetails是由UserDetailsService的loadUserByUsername方法产生的对象。本篇就是通过
SpringDataUserDetailsService(继承UserDetailsService并重写loadUserByUsername方法)生成UserDetails对象。4. 了解
AuthenticationManager是什么
AuthenticationManager是一个接口,表示认证的管理器,只有一个方法:Authentication authenticate(Authentication authentication) throws AuthenticationException;作用:接收一个待认证的
Authentication对象,然后调用内部的AuthenticationProvider完成用户身份验证。认证成功:返回一个已认证的
Authentication对象,其中包含用户信息和权限。认证失败:会抛出
AuthenticationException异常,比如密码错误、用户不存在等。这也是为什么在自定义 Controller 的登录接口中,需要注入
AuthenticationManager。5. 了解
Authentication是做什么的
Authentication是用户认证状态的封装对象,会存储在SecurityContextHolder中,供后续授权使用。在自定义 Controller 的登录接口中,
Authentication就是通过AuthenticationManager.authenticate(authRequest)得到的对象。它包含:
principal:用户身份(用户名或UserDetails对象)
credentials:凭证(密码,认证成功后可为空)
authorities:权限列表
authenticated:是否认证成功一、Spring Security 简介
Spring Security 是 Spring 家族中的安全框架,主要提供:
认证(Authentication):用户是谁(登录验证)。
授权(Authorization):用户能做什么(访问权限控制)。
防护(Protection):常见攻击防护,如 CSRF、会话劫持等。
在 Spring Boot 项目中,Spring Security 常见的使用方式是:
配置 安全拦截规则(哪些 URL 需要登录、哪些需要权限)。
定义 用户认证信息来源(内存 / 数据库 / 第三方服务)。
配置 密码加密 和 登录 / 登出页面。
二、核心代码与配置
1. 视图配置(代替 SpringMVC.xml)
@Configuration // 相当于 springmvc.xml public class WebConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { // 访问根路径 “/” 时,重定向到 /login-view registry.addViewController("/").setViewName("redirect:/login-view"); // 配置登录页映射:/login-view -> login.jsp registry.addViewController("/login-view").setViewName("login"); } }
2. Spring Security 核心配置(WebSecurityConfig 类)
旧写法(Spring Boot 2.6- 之前)
@Configuration @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) // 开启基于方法的权限控制(@PreAuthorize、@Secured) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { //定义用户信息服务(查询用户信息),使用了SpringDataUserDetailsService类 就不用使用这个方法 /* @Bean public UserDetailsService userDetailsService(){ InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build()); manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build()); return manager; } */ //密码编码器(不对密码进行加密,密码是明文存储 & 明文校验。)【不推荐使用】 /*@Bean public PasswordEncoder passwordEncoder(){ return NoOpPasswordEncoder.getInstance(); }*/ // 密码编码器(推荐使用 BCrypt,避免明文存储) @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 安全拦截配置 @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() // 关闭 CSRF(前后端分离常关闭,表单需开启) .authorizeRequests() // .antMatchers("/r/r1").hasAuthority("p1")//基于url的方式授权,但是一般使用基于方法的方式授权即一般在controller的方法上加@PreAuthorize("hasAuthority('p1')") // .antMatchers("/r/r2").hasAuthority("p2")//基于url的方式授权,但是一般使用基于方法的方式授权即一般在controller的方法上加@PreAuthorize("hasAuthority('p2')") .antMatchers("/r/**").authenticated() // 所有 /r/** 需要认证 .anyRequest().permitAll() // 其他请求直接放行(这个一定要放在最后,因为他是从上到下,放一个符合直接放行了 要把细的规则放上面,范围更大的规则放下面) .and() .formLogin() // 开启表单登录 .loginPage("/login-view") //用户没有登录的话要跳转到 /login-view 这个路径 ,如果不指定,Spring Security会生成一个默认的login登录页面 .loginProcessingUrl("/login") // 登录表单提交地址【与jsp中定义的<form action="login" method="post">的action要相同】 .successForwardUrl("/login-success") // 登录成功跳转 .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 按需创建 session .and() .logout() .logoutUrl("/logout") // 登出请求 .logoutSuccessUrl("/login-view?logout"); // 登出成功后跳转 } }新写法(Spring Security 5.7+ 推荐)
@Configuration @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class SecurityConfig { // 密码编码器 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } // 核心:HttpSecurity 配置 @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/r/**").authenticated() .anyRequest().permitAll() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) .and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login-view?logout"); // 注意:如果你走自定义 JSON 登录接口,就不要开启 formLogin() return http.build(); } }
3. 登录 & 资源访问控制器
@RestController public class LoginController { // 登录成功后跳转到这里 @RequestMapping(value = "/login-success", produces = {"text/plain;charset=UTF-8"}) public String loginSuccess() { return getUsername() + " 登录成功"; } // 资源1:需要 p1 权限 @GetMapping(value = "/r/r1", produces = {"text/plain;charset=UTF-8"}) @PreAuthorize("hasAuthority('p1')") public String r1() { return getUsername() + " 访问资源1"; } // 资源2:需要 p2 权限 @GetMapping(value = "/r/r2", produces = {"text/plain;charset=UTF-8"}) @PreAuthorize("hasAuthority('p2')") public String r2() { return getUsername() + " 访问资源2"; } // 获取当前登录用户 private String getUsername() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Object principal = authentication.getPrincipal(); if (principal instanceof UserDetails) { return ((UserDetails) principal).getUsername(); } return principal.toString(); } }
4. 数据库访问层
@Repository public class UserDao { @Autowired JdbcTemplate jdbcTemplate; // 根据账号查询用户信息 public UserDto getUserByUsername(String username) { String sql = "select id,username,password,fullname,mobile from t_user where username = ?"; List<UserDto> list = jdbcTemplate.query(sql, new Object[]{username}, new BeanPropertyRowMapper<>(UserDto.class)); return (list != null && list.size() == 1) ? list.get(0) : null; } // 根据用户id查询权限 public List<String> findPermissionsByUserId(String userId) { String sql = "SELECT * FROM t_permission WHERE id IN(" + "SELECT permission_id FROM t_role_permission WHERE role_id IN(" + " SELECT role_id FROM t_user_role WHERE user_id = ? ))"; List<PermissionDto> list = jdbcTemplate.query(sql, new Object[]{userId}, new BeanPropertyRowMapper<>(PermissionDto.class)); List<String> permissions = new ArrayList<>(); list.forEach(c -> permissions.add(c.getCode())); return permissions; } }
5. 用户与权限 DTO
@Data public class UserDto { private String id; private String username; private String password; private String fullname; private String mobile; } @Data public class PermissionDto { private String id; private String code; // 权限标识 private String description; // 描述 private String url; // 资源路径 }
6. 自定义 UserDetailsService(认证逻辑)
//这个就是4.2 认证(Authentication)流程中的图解的第五步 //我们实现UserDetailsService接口,重写了loadUserByUsername方法,返回我们定义的UserDetails @Service public class SpringDataUserDetailsService implements UserDetailsService { @Autowired UserDao userDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 查询用户信息 UserDto userDto = userDao.getUserByUsername(username); if (userDto == null) { throw new UsernameNotFoundException("用户不存在"); } // 查询用户权限 List<String> permissions = userDao.findPermissionsByUserId(userDto.getId()); String[] permissionArray = permissions.toArray(new String[0]); // 构造 Spring Security User return User.withUsername(userDto.getUsername()) .password(userDto.getPassword()) .authorities(permissionArray) .build(); } }
7. 启动类
@SpringBootApplication public class SecuritySpringBootApp { public static void main(String[] args) { SpringApplication.run(SecuritySpringBootApp.class, args); } }
8. 配置文件(application.properties)
server.port=8080 server.servlet.context-path=/security-springboot spring.application.name=security-springboot spring.mvc.view.prefix=/WEB-INF/view/ spring.mvc.view.suffix=.jsp spring.datasource.url=jdbc:mysql://localhost:3306/user_db spring.datasource.username=root spring.datasource.password=mysql spring.datasource.driver-class-name=com.mysql.jdbc.Driver
9. 登录页面(login.jsp)
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %> <html> <head> <title>用户登录</title> </head> <body> <form action="login" method="post"> 用户名:<input type="text" name="username"><br> 密码:<input type="password" name="password"><br> <input type="submit" value="登录"> </form> </body> </html>
10. Maven 依赖
<dependencies> <!-- Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Security 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- JDBC + MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- JSP 视图支持 --> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> </dependencies>
三、总结
登录认证:由 Spring Security 接管,使用
UserDetailsService从数据库加载用户信息。权限控制:支持 URL 拦截(
antMatchers)和方法级拦截(@PreAuthorize)。密码加密:使用
BCryptPasswordEncoder,避免明文存储。前后端交互:表单方式(JSP)提交到
/login,由 Security 自动处理。四、如果想自己定义controller的登录接口应该如何做?
1.WebSecurityConfig关闭configure方法的登录(也就是注释这个
//.formLogin()//允许表单登录 //.loginPage("/login-view")//登录页面 //.loginProcessingUrl("/login") //.successForwardUrl("/login-success")//自定义登录成功的页面地址2.自定义登录接口(在controller中添加/Login接口【注意与html中from表单的提交地址保持一致】)
@RestController public class LoginController { @Autowired private AuthenticationManager authenticationManager; @PostMapping("/doLogin") public String login(@RequestParam String username, @RequestParam String password) { try { // 封装认证请求 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); // 调用 Security 认证流程 Authentication authentication = authenticationManager.authenticate(authRequest); // 认证成功 SecurityContextHolder.getContext().setAuthentication(authentication); return username + " 登录成功(自定义接口)"; } catch (Exception e) { e.printStackTrace(); return "登录失败"; } }注意点
1.要注入AuthenticationManager类
2.在本样例在输入错误密码的时候会报异常,要做异常处理,如果在controller 里没有捕获异常,导致前一次抛出的异常被返回给前端,Session 状态可能也被影响。【甚至可能会影响到后续如果输入正确也不可以了(不做登出的情况)】
3. 使用AuthenticationManager 的时候要在WebSecurityConfig里面把这个类设置成bean
1. 旧写法(Spring Boot 2.6- 之前)
在上面的的二的2.中添加
@Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }2. 新写法(Spring Security 5.7+ 推荐)
在上面的的二的2.中添加
// 配置 AuthenticationManager(需要时手动暴露) @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); }五、如果想自己定义controller的登出接口应该如何做?
1.实现contrller层的代码
@GetMapping("/logout") public String logout(HttpServletRequest request) { // 1. 清空认证信息 SecurityContextHolder.clearContext(); // 2. 使 Session 失效 request.getSession().invalidate(); return "退出成功"; }2.不使用Spring Security自带的(也就是把WebSecurityConfig 类的登出注释)
//.logout() //.logoutUrl("/logout") //.logoutSuccessUrl("/login-view?logout");
更多推荐
所有评论(0)