spring security 超详细使用教程(接入springboot、前后端分离)

Spring Security 是一个强大且可扩展的框架,用于保护 Java 应用程序,尤其是基于 Spring 的应用。它提供了身份验证(验证用户身份)、授权(管理用户权限)和防护机制(如 CSRF 保护和防止会话劫持)等功能。

Spring Security 允许开发者通过灵活的配置实现安全控制,确保应用程序的数据和资源安全。通过与其他 Spring 生态系统的无缝集成,Spring Security 成为构建安全应用的理想选择。

核心概念

  • 身份验证 (Authentication): 验证用户的身份(例如,用户名/密码)。
  • 授权 (Authorization): 确定用户是否有权限访问特定资源。
  • 安全上下文 (Security Context): 存储已认证用户的详细信息,应用程序中可以访问。

1、准备工作

1.1 引入依赖

当我们引入 security 依赖后,访问需要授权的 url 时,会重定向到 login 页面(security 自己创建的),login 页面需要账号密码,账号默认是 user, 密码是随机的字符串,在spring项目的输出信息中

  • spring-boot-starter-security
    在这里插入图片描述

    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
        <version>3.3.4</version>
    </dependency>
    
  • jjwt-api
    在这里插入图片描述

    <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-api</artifactId>
        <version>0.12.6</version>
    </dependency>
    
  • jjwt-impl
    在这里插入图片描述

    <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-impl</artifactId>
        <version>0.12.6</version>
        <scope>runtime</scope>
    </dependency>
    
  • jjwt-jackson
    在这里插入图片描述

    <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt-jackson</artifactId>
        <version>0.12.6</version>
        <scope>runtime</scope>
    </dependency>
    

一般我们会创建一个 SecurityConfig 类,来管理我们所有与 security 相关的配置。(我们讲的是 security 5.7 版本之后的配置方法,之前的方法跟现在不太一样)

@Configuration
@EnableWebSecurity		// 该注解启用 Spring Security 的 web 安全功能。
public class SecurityConfig {

}

下面的都要写到 SecurityConfig 类中

1.2 用户认证的配置

基于内存的用户认证

通过 createUser , manager 把用户配置的账号密码添加到spring的内存中, InMemoryUserDetailsManager 类中有一个 loadUserByUsername 的方法通过账号(username)从内存中获取我们配置的账号密码,之后调用其他方法来判断前端用户输入的密码和内存中的密码是否匹配。

@Bean
public UserDetailsService userDetailsService() {
	// 创建基于内存的用户信息管理器
    InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();

	
    manager.createUser(
	    // 创建UserDetails对象,用于管理用户名、用户密码、用户角色、用户权限等内容
	    User.withDefaultPasswordEncoder().username("user").password("user123").roles("USER").build()
    ); 		
    // 如果自己配置的有账号密码, 那么上面讲的 user 和 随机字符串 的默认密码就不能用了
    return manager;
}

当我们点进 InMemoryUserDetailsManager 中 可以发现它实现了 UserDetailsManagerUserDetailsPasswordService 接口,其中 UserDetailsManager 接口继承的 UserDetailsService 接口中就有 loadUserByUsername 方法

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

基于数据库的用户认证

上面讲到,spring security 是通过 loadUserByUsername 方法来获取 User 并用这个 User 来判断用户输入的密码是否正确。所以我们只需要继承 UserDetailsService 接口并重写 loadUserByUsername 方法即可

下面的样例我用的 mybatis-plus 来查询数据库中的 user, 然后通过当前查询到的 user 返回特定的 UserDetails 对象

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
        queryWrapper.eq("username", username);	// 这里不止可以用username,你可以自定义,主要根据你自己写的查询逻辑
        User user = userMapper.selectOne(queryWrapper);
        if (user == null) {
            throw new UsernameNotFoundException(username);
        }
        return new UserDetailsImpl(user);	// UserDetailsImpl 是我们实现的类
    }
}

UserDetailsImpl 是实现了 UserDetails 接口的类。UserDetails 接口是 Spring Security 身份验证机制的基础,通过实现该接口,开发者可以定义自己的用户模型,并提供用户相关的信息,以便进行身份验证和权限检查。

@Data
@AllArgsConstructor
@NoArgsConstructor	// 这三个注解可以帮我们自动生成 get、set、有参、无参构造函数
public class UserDetailsImpl implements UserDetails {

    private User user;	// 通过有参构造函数填充赋值的

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of();
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {  // 检查账户是否 没过期。
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {   // 检查账户是否 没有被锁定。
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {  //检查凭据(密码)是否 没过期。
        return true;
    }

    @Override
    public boolean isEnabled() {    // 检查账户是否启用。
        return true;
    }
    
    // 这个方法是 @Data注解 会自动帮我们生成,用来获取 loadUserByUsername 中最后我们返回的创建UserDetailsImpl对象时传入的User。
    // 如果你的字段包含 username和password 的话可以用强制类型转换, 把 UserDetailsImpl 转换成 User。如果不能强制类型转换的话就需要用到这个方法了
	public User getUser() {	
	        return user;	
	    }
	}
1.3 基本的配置

下面这个是 security 的默认配置。我们可以修改并把它加到spring容器中,完成我们特定的需求。

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
            // 开启授权保护
            .authorizeHttpRequests(authorize -> authorize
                    // 不需要认证的地址有哪些
                    .requestMatchers("/blog/**", "/public/**", "/about").permitAll()	// ** 通配符
                    // 对所有请求开启授权保护
                    .anyReque
### 配置Spring Boot Security实现前后端分离 为了确保安全,在采用前后端分离架构的应用程序中,后端服务通常通过API接口提供数据给前端应用。在这种情况下,配置Spring Boot Security来保护这些API变得至关重要。 #### 启用HTTP基本认证 一种简单的方式是启用HTTP Basic Authentication,这可以通过自定义`SecurityConfig`类并重写其`configure(HttpSecurity http)`方法来完成: ```java @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() // 禁用CSRF防护因为这是RESTful API .authorizeRequests() .antMatchers("/api/public/**").permitAll() // 公开资源路径允许所有人访问 .anyRequest().authenticated(); // 所有其他请求都需要身份验证 http.httpBasic(); } } ``` 上述代码片段展示了如何禁用跨站请求伪造(CSRF)[^1],这是因为对于纯API服务器而言,默认的CSRF防御机制并不适用;同时也指定了哪些URL模式可以被公开访问而无需登录凭证验证[^2]。 #### 使用JWT令牌进行状态无会话管理 更推荐的做法是在前后端完全解耦的情况下利用JSON Web Token (JWT) 来代替传统的Session方式处理用户的身份验证与授权问题。这种方式下客户端每次发起请求时都携带Token作为参数传递给服务器端校验合法性。 创建一个过滤器用于解析来自Header中的token信息,并将其转换成Authentication对象放入SecurityContextHolder上下文中: ```java @Component public class JwtRequestFilter extends OncePerRequestFilter { private final UserService userService; private final JwtUtil jwtUtil; public JwtRequestFilter(UserService userService, JwtUtil jwtUtil){ this.userService = userService; this.jwtUtil = jwtUtil; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); try{ username = jwtUtil.extractUsername(jwt); }catch(Exception e){ logger.error(e.getMessage()); } if(username!=null&& SecurityContextHolder.getContext().getAuthentication()==null){ UserDetails userDetails = this.userService.loadUserByUsername(username); if (jwtUtil.validateToken(jwt,userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails,null,userDetails.getAuthorities()); authToken.setDetails(new WebAuthenticationDetailsSource(). buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); } } } chain.doFilter(request,response); } } ``` 此部分逻辑负责从HTTP头获取JWT字符串,并调用辅助工具类JwtUtil的方法提取其中包含的有效载荷(payload),进而加载对应用户的权限详情填充到当前线程的安全上下文环境中去[^3]。 最后一步就是调整原有的`SecurityConfig.java`,使之能够注册这个新的过滤组件以及适应于基于Token的身份验证流程: ```java @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/authenticate","/register").permitAll() .anyRequest().authenticated() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS)// 不创建Http Session .and() .addFilterBefore(new JwtRequestFilter(userDetailsService,jwtUtil), UsernamePasswordAuthenticationFilter.class); } ``` 这里特别注意设置了`STATELESS`策略表示不会依赖任何存在于服务器内存里的会话跟踪记录,而是每次都依靠传入的Token来进行独立判断[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值