paopao/docs/tasks/任务7-Sprint-1-基于JWT的用户认证实现.md

28 KiB
Raw Blame History

任务名称: 任务7-基于JWT的用户认证实现

目标:

  • spring的配置类的使用
  • 基于jwt的认证和授权

预备知识:

  • JWT
  • spring security

操作步骤:

1. 撰写登录接口的API文档

使用模板API接口说明模板,根据演示站点的实际请求和回应填写API接口说明模板。

2. 引入spring security

在build.gradle中引入spring security依赖,在 dependencies中添加如下依赖:

	implementation 'org.springframework.boot:spring-boot-starter-security'
	testImplementation 'org.springframework.security:spring-security-test'

3. 引入JWT工具包

jjwt 是一个轻量级的JWT工具包用于生成和验证JWT。 项目中引入jjwt,在build.gradle中添加如下依赖:

	implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
	runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
	runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'

4. 系统安全相关的自定义配置项

在application.yml中配置系统安全相关配置项:

app:

  security:
    jwt:
      secret-key: uYuVw0mke38MfLhO19wUQyRgwrmYo89ibpQTXPHi4vg=
      expiration: 36000000
    white-list:
      - path: "/api-docs/**"
        methods: ["GET","PUT","POST"]
      - path: "/swagger-ui/**"
        methods: ["GET"]
      - path: "/v3/**"
        methods: [ "GET" ]
      - path: "/v1/auth/**"
        methods: [ "GET","PUT","POST"]
    allowed-origins:
      - "http://localhost:5173"

实现安全相关自定义配置的配置类SecurityProperties

package com.lk.paopao.conf;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "app.security")
public class SecurityProperties {

    private List<WhiteListRule> whiteList = new ArrayList<>();
    private List<String> allowedOrigins;

    @Getter
    @Setter
    public static class WhiteListRule {
        private String path;
        private List<String> methods;
    }
}

5. 实现UserDetailsService接口和UserDetails实现类

UserDetailsService接口 UserDetailsService接口是Spring Security提供的一个核心服务接口其主要目的是加载用户的详细信息特别是用于认证登录验证和授权访问控制的信息。这个接口包含一个核心方法loadUserByUsername(String username)该方法根据用户名或其他唯一的用户标识符加载用户的详细信息。Spring Security在用户尝试登录时会自动调用这个方法来获取用户信息并进一步验证用户的凭证如密码是否正确。

实现UserDetailsService接口需要定义一个类并实现这个方法通常这个方法会查询数据库、LDAP或者其他用户存储系统获取用户的详细信息并封装到一个实现了UserDetails接口的对象中返回。这使得Spring Security可以灵活地与各种用户数据源集成。

UserDetails接口 UserDetails接口代表了用户的核心安全信息包括但不限于用户名、密码、账户是否过期、凭证是否过期、账户是否锁定等状态信息以及用户所具有的权限通常通过角色来表示。通过实现这个接口可以定制化地定义用户实体类来保存和提供这些安全相关信息。例如一个简单的实现可能包含用户名、密码、角色列表等字段。

当一个实现了UserDetailsService的类返回一个实现了UserDetails的对象时Spring Security会使用这些信息来决定用户是否可以成功登录以及登录后能够访问哪些资源。具体来说它会验证用户提供的密码是否与存储的密码匹配并检查用户的账户状态是否允许登录例如没有过期未被锁定

总结来说实现UserDetailsService是为了让Spring Security知道如何根据用户名加载用户详细信息而实现UserDetails则是为了定义这些详细信息应该包含哪些内容从而支撑起Spring Security的认证和授权逻辑。这两个接口的结合使用为Spring Security提供了一个强大且灵活的用户认证和授权机制。

实现UserDetails接口

package com.lk.paopao.security;

import com.lk.paopao.entity.User;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;

/**
 * 用户详情实现类继承自UserDetails接口用于提供用户认证所需信息。
 */
public class UserDetailsImpl implements UserDetails {
    private static final long serialVersionUID = 1L;

    @Getter
    private Long id;

    private String username;

    private String password;

    public UserDetailsImpl(Long id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;

    }

    public static UserDetailsImpl build(User user) {

        return new UserDetailsImpl(user.getId(), user.getUsername(), user.getPassword());
    }

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

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}

实现UserDetailsService接口

package com.lk.paopao.security;


import com.lk.paopao.entity.User;
import com.lk.paopao.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
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;
import org.springframework.transaction.annotation.Transactional;

/**
 * 自定义用户详情服务实现类,用于加载和验证用户详细信息。
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    UserRepository userRepository;

    /**
     * 根据用户名加载用户详情。
     *
     * @param username 用户名
     * @return UserDetails 用户详情对象
     * @throws UsernameNotFoundException 如果用户不存在,则抛出异常
     */
    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 通过用户名查找用户,如果不存在则抛出异常
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User Not Found with username: " + username));

        // 构建并返回UserDetails对象
        return UserDetailsImpl.build(user);
    }
}

6. 实现jwt的工具类

使用jjwt生成JWT Token和验证JWT Token。

package com.lk.paopao.security;

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;

@Component
public class JwtUtils {
    // 日志记录器
    private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);

    // JWT密钥
    @Value("${app.security.jwt.secret-key}")
    private String jwtSecret;

    // JWT过期时间毫秒
    @Value("${app.security.jwt.expiration}")
    private int jwtExpirationMs;

    /**
     * 生成JWT Token
     *
     * @param authentication 认证信息,包含用户详情
     * @return 生成的JWT Token字符串
     */
    public String generateJwtToken(Authentication authentication) {
        // 提取用户详情
        UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();

        // 创建JWT设置主题、签发时间、过期时间和签名密钥
        return Jwts.builder()
                .subject((userPrincipal.getUsername()))
                .issuedAt(new Date())
                .expiration(new Date((new Date()).getTime() + jwtExpirationMs))
                .signWith(getSigningKey())
                .compact();
    }

    /**
     * 获取签名密钥
     *
     * @return 签名所用的SecretKey
     */
    private SecretKey getSigningKey() {
        // 解码JWT密钥
        byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    /**
     * 从JWT Token中提取用户名
     *
     * @param token JWT Token字符串
     * @return 提取的用户名
     */
    public String getUserNameFromJwtToken(String token) {
        // 解析JWT Token并提取主题用户名
        return Jwts.parser()
                .verifyWith(getSigningKey())
                .build()
                .parseSignedClaims(token)
                .getPayload()
                .getSubject();
    }

    /**
     * 验证JWT Token的有效性
     *
     * @param authToken JWT Token字符串
     * @return 如果Token有效返回true否则返回false
     */
    public boolean validateJwtToken(String authToken) {
        try {
            // 尝试解析JWT Token如无异常则表示Token有效
            Jwts.parser()
                    .verifyWith(getSigningKey())
                    .build()
                    .parseSignedClaims(authToken);
            return true;
        } catch (Exception e) {
            // 记录JWT签名异常
            logger.debug("Invalid JWT signature: {}", e.getMessage());
        } 

        return false;
    }
}

7. 创建JWT拦截器

Spring框架中拦截器Interceptor是一个用于在请求处理流程之前或之后执行的一系列逻辑的组件。它们可以用于日志记录、安全控制、事务管理等。

AuthTokenFilter 是一个继承自 OncePerRequestFilter 的拦截器它的作用是检查HTTP请求中的JWT令牌并在令牌有效的情况下将用户认证信息设置到Spring Security的安全上下文中。

package com.lk.paopao.security;


import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * OncePerRequestFilter作为SpringMVC中的一个过滤器在每次请求的时候都会执行。它是对于Filter的抽象实现。
 * OncePerRequestFilter表示每次的Request都会进行拦截不管是资源的请求还是数据的请求.
 * AuthTokenFilter类继承自OncePerRequestFilter用于在每个HTTP请求上执行JWT令牌的验证。
 */
public class AuthTokenFilter extends OncePerRequestFilter {
    // 注入JwtUtils服务用于JWT令牌的处理。
    @Autowired
    private JwtUtils jwtUtils;

    // 注入UserDetailsServiceImpl服务用于根据用户名加载用户详细信息。
    @Autowired
    private UserDetailsServiceImpl userDetailsService;

    // 日志记录器
    private static final Logger logger = LoggerFactory.getLogger(AuthTokenFilter.class);

    /**
     * 重写doFilterInternal方法用于在请求链中执行过滤逻辑。
     *
     * @param request     HttpServletRequest对象代表客户端的HTTP请求。
     * @param response    HttpServletResponse对象用于向客户端发送HTTP响应。
     * @param filterChain 过滤链对象,允许将请求传递给下一个过滤器或处理器。
     * @throws ServletException 如果处理请求时发生Servlet相关异常。
     * @throws IOException      如果处理请求时发生IO异常。
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        try {
            // 解析JWT令牌。
            String jwt = parseJwt(request);
            if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
                // 从JWT令牌中获取用户名。
                String username = jwtUtils.getUserNameFromJwtToken(jwt);

                // 加载用户详细信息。
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                // 创建认证对象。
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                // 设置认证详情。
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                // 设置当前线程的安全上下文认证信息。
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception e) {
            // 记录无法设置用户认证的错误。
            logger.error("Cannot set user authentication:", e);
        }

        // 继续处理请求链。
        filterChain.doFilter(request, response);
    }

    /**
     * 从HTTP请求中解析JWT令牌。
     *
     * @param request HttpServletRequest对象。
     * @return 从请求头中解析出的JWT令牌字符串如果无法解析则返回null。
     */
    private String parseJwt(HttpServletRequest request) {
        // 从请求头中获取"Authorization"字段。
        String headerAuth = request.getHeader("Authorization");

        // 如果存在且以"Bearer "开头,则提取并返回令牌字符串。
        if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
            return headerAuth.substring(7);
        }

        return null;
    }
}

8. 实现 AuthenticationEntryPoint

AuthenticationEntryPoint 是一个用于处理未认证请求的接口。当用户尝试访问受保护资源时如果用户尚未通过身份验证则AuthenticationEntryPoint将处理该请求。

package com.lk.paopao.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * 当用户请求了一个受保护的资源但是用户没有通过认证那么抛出异常AuthenticationEntryPoint. Commence(..)就会被调用。
 * 这个对应的代码在ExceptionTranslationFilter中当ExceptionTranslationFilter catch到异常后就会间接调用AuthenticationEntryPoint。
 * <p>
 * AuthEntryPointJwt:处理JWT认证失败的入口点。
 * 当用户请求需要认证的资源但未提供有效令牌时,会触发此入口点。
 * 实现了AuthenticationEntryPoint接口用于定义应用程序在身份验证失败时的响应行为。
 */
@Component
class AuthEntryPointJwt implements AuthenticationEntryPoint {

    private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);

    /**
     * 当认证失败时,向客户端返回适当的错误响应。
     *
     * @param request       HttpServletRequest对象代表客户端的HTTP请求。
     * @param response      HttpServletResponse对象用于向客户端发送HTTP响应。
     * @param authException 身份验证异常,包含认证失败的详细信息。
     * @throws IOException      如果在写响应时发生I/O错误。
     * @throws ServletException 如果在处理请求时发生Servlet异常。
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        // 记录未经授权的错误信息
        logger.debug("Unauthorized error: {}", authException.getMessage());

        // 设置响应类型为JSON状态码为401未授权
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);

        // 构建响应体,包含状态码、错误信息和请求路径
        final Map<String, Object> body = new HashMap<>();
        body.put("code", HttpServletResponse.SC_UNAUTHORIZED);
        body.put("msg", authException.getMessage());
        // 将响应体转换为JSON并写入响应流
        final ObjectMapper mapper = new ObjectMapper();
        mapper.writeValue(response.getOutputStream(), body);
    }
}

9. spring security的配置类

需要实现一个Spring Security配置类用于设置应用程序的安全特性包括认证、授权、密码加密、跨域资源共享CORS等。

package com.lk.paopao.security;


import com.lk.paopao.conf.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;

import java.util.Map;
import static org.springframework.security.config.http.SessionCreationPolicy.STATELESS;

@Configuration // 配置类注解代表这是一个Spring配置类。
@EnableWebSecurity // 启用Spring Web安全功能。
@EnableMethodSecurity // 启用方法级别的安全控制。
public class SecurityConfiguration {

    @Autowired
    UserDetailsService userDetailsService;
    @Autowired
    private AuthEntryPointJwt unauthorizedHandler;

    @Autowired
    SecurityProperties securityProperties;

    public Map<String, HttpMethod> METHODS = Map.of(
            "GET", HttpMethod.GET,
            "POST", HttpMethod.POST,
            "PUT", HttpMethod.PUT,
            "DELETE", HttpMethod.DELETE
    );

    /**
     * 创建并返回一个AuthTokenFilter实例用于过滤和处理JWT认证令牌。
     *
     * @return AuthTokenFilter 实例。https://i…
     */
    @Bean
    public AuthTokenFilter authenticationJwtTokenFilter() {
        return new AuthTokenFilter();
    }

    /**
     * 创建并配置DaoAuthenticationProvider用于处理用户认证。
     *
     * @return 配置好的DaoAuthenticationProvider实例。
     */
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();

        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());

        return authProvider;
    }

    /**
     * 创建并返回一个BCryptPasswordEncoder实例用于密码加密。
     *
     * @return BCryptPasswordEncoder 实例。
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置并返回一个AuthenticationManager实例它是Spring Security的认证管理器。
     *
     * @param authConfig 认证配置。
     * @return 配置好的AuthenticationManager实例。
     * @throws Exception 可能抛出的异常。
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    /**
     * 配置HTTP安全设置定义如何处理请求和授权。
     *
     * @param http         HttpSecurity配置对象。
     * @param introspector 用于检查HandlerMapping的 introspector。
     * @return 配置好的SecurityFilterChain它定义了应用的过滤链。
     * @throws Exception 可能抛出的异常。
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
        // 禁用CSRF保护
        http.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests(req -> {
            // 允许OPTIONS请求
            req.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll();
            // 配置白名单
            for (SecurityProperties.WhiteListRule rule : securityProperties.getWhiteList()) {
                for (String method : rule.getMethods()) {
                    req.requestMatchers(METHODS.get(method.toUpperCase()), rule.getPath()).permitAll();
                }
            }
            // 其他请求需要身份验证
            req.anyRequest().authenticated();
        });

        // 设置未授权访问的处理,设置会话管理策略为无状态.设置认证提供者,在UsernamePasswordAuthenticationFilter之前添加自定义过滤器
        http.exceptionHandling(ex -> ex.authenticationEntryPoint(unauthorizedHandler))
                .sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
                .authenticationProvider(authenticationProvider())
                .addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }

    /**
     * 配置跨域过滤器
     *
     * @return CorsFilter
     */
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.addAllowedHeader("*");
        config.addAllowedMethod("*");
        for (String origin : securityProperties.getAllowedOrigins()) {
            config.addAllowedOrigin(origin);
        }
        config.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);

        return new CorsFilter(source);
    }
}

10. 实现登录接口

在AuthController中添加登录接口


    @PostMapping("/login")
    public DataResult<Map<String,String>> login(@RequestBody LoginRequest loginRequest){
        return ResultUtil.ok(authService.login(loginRequest.getUsername(),loginRequest.getPassword()));
    }

在AuthService中添加登录逻辑

并修改注册逻辑,密码加密.

完整的AuthService类如下:


package com.lk.paopao.service;

import com.lk.paopao.dto.rest.request.RegisterRequest;
import com.lk.paopao.entity.User;
import com.lk.paopao.exception.ResourceExistedException;
import com.lk.paopao.repository.UserRepository;
import com.lk.paopao.security.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.Map;

@Service
@Transactional
public class AuthService {
    // 从配置文件中读取默认头像地址
    @Value("${app.default.head-icon}")
    private String DEFAULT_HEAD_ICON;
    // 注入用户实体的数据库接口
    @Autowired
    UserRepository userRepository;
    // 自动注入认证管理器
    @Autowired
    AuthenticationManager authenticationManager;
    // 自动注入密码编码器
    @Autowired
    PasswordEncoder encoder;
    // 自动注入JWT工具类
    @Autowired
    JwtUtils jwtUtils;

    /**
     * 用户注册。
     * @param reg 注册请求对象,包含用户名和密码
     * @return 注册后的用户信息
     */
    public User register(RegisterRequest reg) {
        // 检查用户名是否已存在,如果存在则抛出异常
        userRepository.findByUsername(reg.getUsername()).ifPresent(user -> {
            throw new ResourceExistedException("User","username="+reg.getUsername());
        });
        // 创建用户对象并设置信息
        User user = new User();
        // 设置密码,暂时使用明文密码
        user.setPassword(encoder.encode(reg.getPassword()));
        // 设置昵称为用户名
        user.setUsername(reg.getUsername());
        // 设置昵称为用户名
        user.setNickname(reg.getUsername());
        // 设置默认头像
        user.setAvatar(DEFAULT_HEAD_ICON);
        // 在数据库中创建用户
        return userRepository.save(user);
    }
    /**
     * 用户登录返回JWT令牌。
     * @param username 用户名
     * @param password 密码
     * @return 包含JWT令牌的Map
     */
    public Map<String,String> login(String username, String password) {
        // 认证用户
        Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username,password));

        // 设置认证信息到SecurityContextHolder
        SecurityContextHolder.getContext()
                .setAuthentication(authentication);
        // 生成JWT令牌
        String jwt = jwtUtils.generateJwtToken(authentication);

        // 返回包含JWT令牌的Map
        Map<String,String> token = new HashMap<>();
        token.put("token",jwt);

        return token;
    }
}

11. 通过swagger ui 测试登录接口

http://localhost:8080/api-docs

技术/工具需求:

  • [列出完成任务所需的技术栈、工具、软件版本等。]

成功标准:

  • [明确完成任务的评判标准,如代码功能实现、性能指标、测试通过条件等。]

扩展学习(可选):

  • [提供一些额外学习资源或挑战性任务,鼓励学有余力的学生进一步探索。]

评估与反馈:

  • [说明如何提交作业、代码审查的标准、或任何反馈收集机制。]

时间估算:

  • [给出预计完成该任务所需的时间,帮助学生合理安排学习计划。]