spring security6通过token认证和鉴权

在采用基于Token的认证机制(如JWT,JSON Web Token)时,Spring Security 6 提供了灵活的方式来进行配置和集成。以下是详细的步骤和关键类、方法,帮助你实现一个基于Token的安全认证机制。

1. 添加依赖

首先,确保你的项目中包含必要的依赖。对于使用JWT的场景,通常需要添加以下依赖:

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- JWT -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

2. 配置SecurityFilterChain

接下来,在Spring Security配置类中设置SecurityFilterChain,以支持基于Token的认证机制。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, JwtAuthenticationFilter jwtAuthenticationFilter) throws Exception {
        http
            .csrf().disable() // 禁用CSRF保护
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置为无状态会话
            .and()
            .authorizeHttpRequests(authorize -> authorize
                .antMatchers("/auth/**").permitAll() // 公开路径,例如登录和注册
                .anyRequest().authenticated() // 其他所有请求都需要认证
            )
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 添加自定义JWT过滤器

        return http.build();
    }
}

3. 实现JWT工具类

创建一个工具类来生成和解析JWT令牌。

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtUtil {

    private String secret = "yourSecretKey"; // 应该存储在一个安全的地方,并且不要硬编码

    // 从token中提取用户名
    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    // 从token中提取过期时间
    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    // 提取单个声明
    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    // 解析token的所有声明
    private Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }

    // 检查token是否过期
    private Boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    // 生成token
    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }

    // 创建token
    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10小时过期
                .signWith(SignatureAlgorithm.HS256, secret).compact();
    }

    // 验证token
    public Boolean validateToken(String token, UserDetails userDetails) {
        final String username = extractUsername(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }
}

4. 实现JWT认证过滤器

创建一个自定义的过滤器来处理JWT验证逻辑。

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private UserDetailsService userDetailsService;

    @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);
            username = jwtUtil.extractUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

            if (jwtUtil.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}

5. 实现用户认证服务

确保你有一个实现了UserDetailsService的类来加载用户信息。

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 这里可以替换为从数据库或其他数据源加载用户信息的逻辑
        return new org.springframework.security.core.userdetails.User(
                username,
                "password",
                AuthorityUtils.createAuthorityList("ROLE_USER")
        );
    }
}

6. 控制器示例

创建一个控制器来生成JWT令牌。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;

@RestController
public class AuthController {

    @Autowired
    private JwtUtil jwtUtil;

    @Autowired
    private UserDetailsService userDetailsService;

    @PostMapping("/auth/login")
    public String createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
        final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
        if (!userDetails.getPassword().equals(authenticationRequest.getPassword())) {
            throw new Exception("Invalid credentials");
        }
        final String jwt = jwtUtil.generateToken(userDetails);
        return jwt;
    }
}

class AuthenticationRequest {
    private String username;
    private String password;

    // Getters and Setters
}

总结

通过上述步骤,你可以成功地在Spring Security 6中实现基于JWT的认证机制。主要步骤包括:

  1. 添加依赖:引入必要的依赖。
  2. 配置SecurityFilterChain:禁用CSRF保护并设置为无状态会话。
  3. 实现JWT工具类:用于生成和解析JWT令牌。
  4. 实现JWT认证过滤器:处理JWT验证逻辑。
  5. 实现用户认证服务:加载用户信息。
  6. 创建控制器:提供生成JWT令牌的接口。

这样,你的应用程序就可以使用JWT进行安全认证和授权了。

<think>我们结合LayuiAdmin前端框架与Spring Boot 4.0.0(实际Spring Boot版本通常为2.x或3.x,4.0.0可能为笔误,这里以Spring Boot 2.7.x或3.x为例,两者在配置上类似,但需注意Spring Security 6.x的配置变化)Spring Security来实现登录。LayuiAdmin负责前端登录界面路由跳转,Spring Security负责后端认证。 整体流程: 1. 前端LayuiAdmin登录表单提交到Spring Security的登录处理接口(默认是`/login`,但通常我们会自定义)。 2. 后端Spring Security进行认证(用户名密码校验),认证成功生成JWT(或使用Session)并返回给前端。 3. 前端将返回的token(如JWT)存储在localStorage中,并在后续请求中携带(通过admin.req自动携带)。 4. Spring Security配置JWT过滤器,在每次请求时验证token,并设置认证信息。 5. 根据用户限进行(访问控制)。 步骤详解: 一、前端LayuiAdmin登录逻辑(修改自之前代码) 在LayuiAdmin的登录页面,我们使用以下代码提交登录请求: ```javascript form.on('submit(login)', function(data){ // 使用admin.req发送登录请求 admin.req({ url: '/api/auth/login', // 自定义的登录接口,对应Spring Security中的登录处理 data: data.field, method: 'post', done: function(res){ if(res.code === 200) { // 假设成功返回200 // 存储token,假设返回的token字段为access_token localStorage.setItem('access_token', res.data.access_token); // 跳转到主页 location.href = 'index.html'; } else { layer.msg(res.msg); } } }); return false; }); ``` 二、Spring Boot + Spring Security后端配置 1. 添加依赖(Spring Boot 2.7.x): ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- 如果需要JWT --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 自定义用户认证逻辑(实现UserDetailsService,从数据库加载用户)[^2][^3][^4][^5]。 3. 配置Spring Security(注意:Spring Security 6.x中WebSecurityConfigurerAdapter已废弃,改为使用组件式配置)[^1]。 以Spring Security 5.x(Spring Boot 2.7.x)为例,配置类如下: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http // 关闭CSRF .csrf().disable() // 认证请求 .authorizeRequests() // 允许登录接口匿名访问 .antMatchers("/api/auth/login").permitAll() // 其他请求需要认证 .anyRequest().authenticated() .and() // 自定义登录处理(不使用默认的formLogin) // 由于我们使用前后端分离,所以需要自定义登录成功失败的处理器 .formLogin() .loginProcessingUrl("/api/auth/login") // 登录处理接口 .successHandler(authenticationSuccessHandler()) .failureHandler(authenticationFailureHandler()) .and() // 添加JWT认证过滤器 .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); } // 自定义登录成功处理器 @Bean public AuthenticationSuccessHandler authenticationSuccessHandler() { return (request, response, authentication) -> { // 生成JWT String token = JwtUtil.generateToken(authentication.getName()); // 返回成功信息 response.setContentType("application/json;charset=utf-8"); response.getWriter().write(JSON.toJSONString(Result.success(token))); }; } // 自定义登录失败处理器 @Bean public AuthenticationFailureHandler authenticationFailureHandler() { return (request, response, exception) -> { response.setContentType("application/json;charset=utf-8"); response.getWriter().write(JSON.toJSONString(Result.fail("登录失败"))); }; } // JWT认证过滤器 @Bean public JwtAuthenticationFilter jwtAuthenticationFilter() { return new JwtAuthenticationFilter(); } // 配置AuthenticationManager @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } } ``` 其中,`JwtUtil`是生成解析JWT的工具类,`JwtAuthenticationFilter`是用于验证JWT的过滤器: ```java public class JwtUtil { // 密钥 private static final String SECRET_KEY = "your-secret-key"; // 有效期 private static final long EXPIRATION = 86400000; // 24小时 public static String generateToken(String username) { return Jwts.builder() .setSubject(username) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) .signWith(SignatureAlgorithm.HS512, SECRET_KEY) .compact(); } public static String getUsernameFromToken(String token) { return Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody() .getSubject(); } public static boolean validateToken(String token) { try { Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token); return true; } catch (Exception e) { return false; } } } ``` ```java public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 从请求头中获取token String token = request.getHeader("access_token"); if (token != null && JwtUtil.validateToken(token)) { String username = JwtUtil.getUsernameFromToken(token); // 从数据库加载用户信息(可选,也可以将限信息放在token中) // 这里简化,直接根据用户名构建认证信息,实际中需要从数据库加载限 UserDetails userDetails = ...; // 通过你的UserDetailsService加载 if (userDetails != null) { // 构建认证对象 UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); // 设置到SecurityContext中 SecurityContextHolder.getContext().setAuthentication(authentication); } } filterChain.doFilter(request, response); } } ``` 三、限控制) 在控制器方法上使用注解进行限控制,例如: ```java @RestController @RequestMapping("/api/user") public class UserController { @PreAuthorize("hasAuthority('admin')") // 需要admin限 @GetMapping("/list") public Result listUsers() { // ... } } ``` 或者通过配置进行全局的URL限控制(在SecurityConfig中): ```java .antMatchers("/api/admin/**").hasRole("ADMIN") .antMatchers("/api/user/**").hasAnyRole("USER", "ADMIN") ``` 四、前端请求携带token 在LayuiAdmin中,我们使用`admin.req`方法发送请求,它会自动在请求头(或参数)中携带`access_token`(根据配置)。在Spring SecurityJwtFilter中,我们是从请求头中获取的,所以需要确保前端发送请求时在请求头中携带。 五、处理跨域问题(如果前端后端分离部署) 在SecurityConfig中配置CORS: ```java http.cors().and()... ``` 并添加CORS配置Bean: ```java @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("http://localhost:8000")); // LayuiAdmin的地址 configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); configuration.setAllowCredentials(true); configuration.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } ``` 注意事项: 1. 上述代码为示例,实际应用中需要根据业务调整。 2. Spring Boot 3.x(Spring Security 6.x)的配置方式有所不同,主要是去掉WebSecurityConfigurerAdapter,使用Lambda风格的配置,并且默认会开启CSRF,需要显式关闭(如果使用JWT则关闭CSRF)。 3. 在Spring Security 6.x中,配置方式如下: ```java @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/login").permitAll() .anyRequest().authenticated() ) .formLogin(form -> form .loginProcessingUrl("/api/auth/login") .successHandler(authenticationSuccessHandler()) .failureHandler(authenticationFailureHandler()) ) .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } // ... 其他Bean同上 } ``` 这样,我们就实现了LayuiAdmin前端与Spring Boot + Spring Security后端的登录。 §§相关问题§§ 1. 如何在Spring Security 6.x中配置自定义登录接口限控制? 2. JWT如何存储用户限信息以实现动态? 3. 如何实现LayuiAdmin前端路由与Spring Security后端限的联动(例如动态菜单)? 4. Spring Security中如何实现记住我(Remember-Me)功能? </think> ### Spring Boot 4.0.0 + Spring Security + LayuiAdmin 登录实现方案 #### 1. **整体架构** ```mermaid graph LR A[LayuiAdmin前端] -->|提交登录请求| B(Spring Security) B -->|JWT生成| C[过滤器] C -->|限验证| D[业务接口] ``` #### 2. **核心步骤** **2.1 前端配置(LayuiAdmin)** ```javascript // 登录表单提交 form.on('submit(login)', function(data){ admin.req({ url: '/api/auth/login', // Spring Security登录接口 data: data.field, method: 'post', done: function(res){ if(res.code === 200) { localStorage.setItem('access_token', res.data.token); // 存储JWT location.href = 'index.html'; // 跳转主页 } else { layer.msg(res.msg); } } }); return false; }); ``` **2.2 Spring Security 配置类** ```java @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() // 禁用CSRF .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/login").permitAll() // 登录接口开放 .anyRequest().authenticated() // 其他接口需认证 ) .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class) // JWT过滤器 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 无状态会话 return http.build(); } @Bean public JwtAuthFilter jwtAuthFilter() { return new JwtAuthFilter(); } } ``` **2.3 JWT 认证过滤器** ```java public class JwtAuthFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 从Header获取token String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); if (JwtUtil.validateToken(token)) { // 解析用户名限 String username = JwtUtil.getUsernameFromToken(token); List<GrantedAuthority> authorities = JwtUtil.getAuthoritiesFromToken(token); // 创建认证对象 UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, authorities); SecurityContextHolder.getContext().setAuthentication(auth); } } filterChain.doFilter(request, response); } } ``` **2.4 登录认证接口** ```java @RestController @RequestMapping("/api/auth") public class AuthController { @PostMapping("/login") public Result login(@RequestBody LoginRequest request) { // 1. 验证用户名密码 Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()) ); // 2. 生成JWT UserDetails userDetails = (UserDetails) authentication.getPrincipal(); String token = JwtUtil.generateToken(userDetails); // 3. 返回响应 return Result.success(200, "登录成功", Map.of("token", token)); } } ``` **2.5 JWT 工具类** ```java public class JwtUtil { private static final String SECRET_KEY = "your-256-bit-secret"; private static final long EXPIRATION = 86400000; // 24小时 public static String generateToken(UserDetails userDetails) { return Jwts.builder() .setSubject(userDetails.getUsername()) .claim("authorities", getAuthorities(userDetails)) .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); } private static List<String> getAuthorities(UserDetails userDetails) { return userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()); } } ``` #### 3. **限控制实现** **3.1 方法级** ```java @RestController @RequestMapping("/api/user") public class UserController { @PreAuthorize("hasAuthority('ADMIN')") // 仅管理员可访问 @GetMapping("/admin-data") public Result getAdminData() { return Result.success("管理员数据"); } } ``` **3.2 自定义限对象** ```java public class AuthorityInfo implements GrantedAuthority { private String authority; // 构造器/getter/setter @Override public String getAuthority() { return authority; } } public enum Authority { ADMIN("admin"), USER("user"); private final String authorityName; // 构造器/getter } ``` #### 4. **关键配置要点** 1. **依赖配置**: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. **安全规则**: - 开放登录接口`/api/auth/login` - 其他接口需携带有效JWT - 无状态会话管理 3. **Token传递**: - 前端通过`Authorization: Bearer <token>`传递 - 后端从Header解析并验证 #### 5. **流程说明** 1. 用户在前端输入账号密码 2. LayuiAdmin发送登录请求到`/api/auth/login` 3. Spring Security验证凭证并生成JWT 4. 前端存储JWT并在后续请求中携带 5. JWT过滤器验证令牌并设置安全上下文 6. 控制器方法通过`@PreAuthorize`进行限控制 > **注意事项**: > 1. 生产环境使用HTTPS保证Token安全 > 2. JWT密钥需存储在安全位置 > 3. 设置合理的Token过期时间 > 4. 实现Token刷新机制[^3]
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值