(五)Spring Boot学习——spring security +jwt使用(前后端分离模式)

一定要熟悉spring security原理和jwt无状态原理,理解了才知道代码作用。

Spring Security + JWT 认证流程中,通常的做法是:

  1. 用户提交用户名和密码
  2. Spring Security 认证管理器 (AuthenticationManager) 进行认证
  3. 如果认证成功,生成 JWT Token 并返回给用户

更详细一点

  1. 用户首次登录

    • 发送 POST /login 请求,携带 用户名 + 密码
    • authenticationManager.authenticate() 认证成功后,返回 JWT
    • 前端存储 JWT(通常是 localStoragesessionStorage
  2. 用户访问受保护接口

    • 前端在 Authorization 头中附带 Bearer Token
    • 过滤器 JWTFilter 解析 JWT,从 数据库 加载 UserDetails
    • SecurityContextHolder.setAuthentication() 认证成功,继续访问资源。

参考链接有:

spring security 超详细使用教程(接入springboot、前后端分离) - 小程xy - 博客园

SpringSecurity+jwt实现权限认证功能_spring security + jwt-CSDN博客

1.引入相关依赖。我使用的是springboot3.3.5 springsecurity是6.x的 jwt 0.12.6

XML 复制代码
<dependencies>
     <!--用于数据加密,默认启用-->
     <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-security</artifactId>
     </dependency>
     <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-crypto</artifactId>
     </dependency>
</dependencies>

<!--依赖集中管理-->
    <dependencyManagement>
        <dependencies>
            <!-- 使用jwt进行token验证,包括了三个依赖--> 
            <dependency> 
                <groupId>io.jsonwebtoken</groupId> 
                <artifactId>jjwt</artifactId> 
                <version>0.12.6</version> 
            </dependency> 
            <dependency> 
                <groupId>io.jsonwebtoken</groupId> 
                <artifactId>jjwt-impl</artifactId> 
                <version>0.12.6</version> 
                <scope>runtime</scope> 
            </dependency> 
            <dependency> 
                <groupId>io.jsonwebtoken</groupId> 
                <artifactId>jjwt-jackson</artifactId> 
                <version>0.12.6</version> 
                <scope>runtime</scope> 
            </dependency>
         <dependencies>
   </dependencyManagement>

2.配置SecurityConfig.java

java 复制代码
package com.x.x.x.config;

import com.x.x.x.filter.CustomFilter;
import com.x.x.x.filter.JwtAuthenticationTokenFilter;
import com.x.x.x.security.service.impl.UserDetailsServiceImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.core.userdetails.User;
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.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig {
    /**
     * 用户名和密码也可以在application.properties中设置。
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService() {
        // 创建基于内存的用户信息管理器
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 创建UserDetails对象,用于管理用户名、用户密码、用户角色、用户权限等内容
        manager.createUser(
                User.withUsername("admin").password("yourpassword").roles("ADMIN").build()
        );
        return manager;
    }
    /**
     * 认证管理。     jwt的用户验证
     * @param authConfig
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }
    /**
     * 认证的token过滤器
     * @return
     */
    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
        return new JwtAuthenticationTokenFilter();
    }
    /**
     * 密码加码
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 也可用有参构造,取值范围是 4 到 31,默认值为 10。数值越大,加密计算越复杂
        return new BCryptPasswordEncoder();
    }
    /**
     * 配置过滤链
     * 配置自动注销功能必须在函数里加UserDetailsService userDetailsService,因为重写了使用数据库认证所以用baseuserserviceimpl
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http,  UserDetailsServiceImpl userDetailsService) throws Exception {
        http
                // 开启授权保护,配置请求授权规则
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/login","/mylogin","/druid/**").permitAll()   // 不需要认证的地址有哪些  ("/blog/**", "/public/**", "/about")
                        .anyRequest()      // 对所有请求开启授权保护
                        .authenticated()   // 已认证的请求会被自动授权
                )
                // 配置自定义登录页面
                // 本处禁用前端页面,使用功能RESTful风格前后端分离,就是不用登录页面
                .formLogin(form -> form.disable())
                .httpBasic(Customizer -> Customizer.disable())
                // 启用记住我功能。允许用户关闭浏览器后仍然保持登录状态,直到主动注销或者查出设定过期时间
                //.rememberMe(Customizer.withDefaults())
                .rememberMe(rememberMe -> rememberMe
                        .key("uniqueAndSecret") // 设置一个密钥
                        .tokenValiditySeconds(2 * 24 * 60 * 60) // 设置 RememberMe token 的有效期
                        .userDetailsService(userDetailsService) // 显式设置 UserDetailsService
                )
                // 配置注销功能
                .logout(logout -> logout
                        .logoutUrl("/perform_logout") // 自定义注销请求路径
                        //.logoutSuccessUrl("/login?logout=true") // 注销成功后的跳转页面
                        .deleteCookies("JSESSIONID") // 删除指定的 Cookie
                        .permitAll() // 允许所有用户注销
                )
                .sessionManagement(session -> session
                        .sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::changeSessionId)  // 防止会话固定攻击
                        .maximumSessions(1) // 限制每个用户只能有一个活跃会话
                        .maxSessionsPreventsLogin(false)// 如果为 true,禁止新登录;为 false,允许新登录并终止旧会话
                        .expiredUrl("/login?session=expired") // 当会话过期时跳转到的页面
                )
        ;
        // 关闭 csrf CSRF(跨站请求伪造)是一种网络攻击,攻击者通过欺骗已登录用户,诱使他们在不知情的情况下向受信任的网站发送请求。
        http.csrf(csrf -> csrf.disable());
        // 注册自定义的过滤器CustomFilter
        // 用于jwt 功能确保过滤器的逻辑在每个请求中只执行一次,非常适合需要对每个请求进行处理的场景
        http.addFilterBefore(new CustomFilter(), UsernamePasswordAuthenticationFilter.class);
        //已经在customfilter中重写 http.addFilterBefore(new JwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        //授权认证,基于角色在 Spring Security 6.x 版本中,antMatchers() 方法已被移除,取而代之的是使用新的基于 请求匹配器 (RequestMatchers) 的方法
        /*http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/admin/**").hasRole("ADMIN")  // 只有 ADMIN 角色可以访问
                        .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN")  // USER 和 ADMIN 角色可以访问
                        .anyRequest().authenticated());  // 其他请求需要认证
        //基于权限的授权,编辑权限还是只读等
        http
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/edit/**").hasAuthority("EDIT_PRIVILEGE")  // 仅具有 EDIT_PRIVILEGE 权限的用户可以访问
                        .anyRequest().authenticated());  // 其他请求需要认证*/
        return http.build();
    }
}

3.重写loadUserByUsername的方法。

(1)UserDetailsImpl.java

java 复制代码
package com.x.x.x.security.service.impl;

import com.x.x.x.entity.BaseUsers;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;

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

    private BaseUsers baseUsers;

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

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

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

    @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 BaseUsers getUser() {
        return baseUsers;
    }
}

(2)UserDetailsServiceImpl.java

java 复制代码
package com.x.x.x.security.service.impl;

import com.x.x.x.entity.BaseUsers;
import com.x.x.x.service.BaseUsersService;
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 java.util.List;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private BaseUsersService baseUsersService;
    /**
     * 重写loadUserByUsername方法
     * @param username the username identifying the user whose data is required.
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        BaseUsers baseUsers = new BaseUsers();
        baseUsers.setOaId(username);
        List<BaseUsers> baseUsersList = baseUsersService.queryUsersList(baseUsers);
        if (baseUsersList == null || baseUsersList.isEmpty()) {
            System.out.println("------------->       loadUserByUsername验证失败, "+baseUsers.getOaId()+" 不存在!");
            throw new UsernameNotFoundException(username);
        }
        return new UserDetailsImpl(baseUsersList.get(0));	// UserDetailsImpl 是我们实现的类
    }
}

4.JwtAuthenticationProvider.java继承重新AuthenticationProvider的authenticate方法。这里注意可能未使用我们继承的userDetailsService,所以使用@Qualifier("")指定

java 复制代码
package com.x.x.x.security.handler;

import io.micrometer.common.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

@Component
public class JwtAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    @Qualifier("userDetailsServiceImpl")//需要指定注入的是那个类,避免报错。
    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) {
        String username = String.valueOf(authentication.getPrincipal());
        String password = String.valueOf(authentication.getCredentials());
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        System.out.println("------------->       JwtAuthenticationProvider:"+userDetails.getUsername()+","+userDetails.getPassword());
        if(userDetails != null && StringUtils.isNotBlank(userDetails.getPassword())
                && userDetails.getPassword().equals(password)){
            return new UsernamePasswordAuthenticationToken(username,password,authentication.getAuthorities());
        }
        try {
            throw new Exception("RespCodeEnum.NAME_OR_PASSWORD_ERROR");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.equals(authentication);
    }
}

5.拦截器实现。

(1)CustomFilter

java 复制代码
package com.x.x.x.filter;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

/**
 * OncePerRequestFilter 是 Spring Security 提供的一个抽象类,确保在每个请求中只执行一次特定的过滤逻辑。
 * 它是实现自定义过滤器的基础,通常用于对请求进行预处理或后处理。(实现 JWT 会用到这个接口)
 * 提供了一种机制,以确保过滤器的逻辑在每个请求中只执行一次,非常适合需要对每个请求进行处理的场景。
 * 通过继承该类,可以轻松实现自定义过滤器适合用于记录日志、身份验证、权限检查等场景。
 *
 * 本处继承 OncePerRequestFilter 类,并重写 doFilterInternal 方法。
 * 但是需要再spring security配置类中注册自定义的过滤器
 */
public class CustomFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        // 自定义过滤逻辑,例如记录请求日志
        System.out.println("Request URI: " + request.getRequestURI());

        // 继续执行过滤链
        filterChain.doFilter(request, response);
    }
}

(2)JwtAuthenticationTokenFilter

java 复制代码
package com.x.x.x.filter;

import com.x.x.x.dao.BaseUsersDao;
import io.jsonwebtoken.Claims;
import java.io.IOException;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import com.x.x.x.until.JwtUtil;



@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    /**
     *  用于验证账号密码,本处于数据库交互
     */
    @Autowired
    private BaseUsersDao baseUsersDao;

    @Autowired
    @Qualifier("userDetailsServiceImpl")//需要指定注入的是那个类,避免报错。
    private UserDetailsService userDetailsService;

    /**
     * 重写了 OncePerRequestFilter 类中的抽象方法 doFilterInternal。
     * OncePerRequestFilter 是 Spring Security 提供的一个基础类
     * ,设计用来确保过滤器在同一个请求中只执行一次。
     * @param request
     * @param response
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response
            , @NotNull FilterChain filterChain) throws ServletException, IOException {
        // 获取请求头的验证信息,即前端传回的token
        String token = request.getHeader("Authorization");
        System.out.println("----》     JwtAuthenticationTokenFilter,验证token过滤器,获取到的token值:"+token);

        //为空时候继续下一步过滤链,即进行登录认证。后续进行格式验证,如果以bearer开始去掉前面的前缀
        if (!StringUtils.hasText(token) ) {
            System.out.println("----》     JwtAuthenticationTokenFilter,token验证:"+"token为空!");
            filterChain.doFilter(request, response);
            return;
        }
        if (token.startsWith("Bearer ")) {
            System.out.println("----》     JwtAuthenticationTokenFilter,token格式验证中:"+"token格式以Bearer开头,去掉开头!");
            token = token.substring(7);
        }

        //验证token是否过期
        boolean isValid = JwtUtil.validateJwtToken(token);//只在util中只验证是否过期了。
        if (!isValid) {
            System.out.println("----》     token验证失败,token过期。");
            response(response, "验证失败");
            return;
        }
        //获取token载荷中的用户信息
        Claims claims = JwtUtil.parseClaim(token).getPayload();
        String userid = claims.get("username").toString();

        //查询数据库中用户信息
        System.out.println("----》     数据库验证用户信息。"+"userid:"+userid);
        UserDetails userDetails = userDetailsService.loadUserByUsername(userid);
        System.out.println("----》     数据库中数据:"+userDetails.getUsername()+","+userDetails.getPassword());
        //设置安全上下文
        //创建一个自定义的 UserDetailsImpl 对象,将查询到的用户信息封装。
        //创建一个 UsernamePasswordAuthenticationToken 对象,表示用户的认证信息
        // ,并将其设置到 Spring Security 的 SecurityContextHolder 中,以便后续请求能够访问到用户的认证信息。
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
        // 如果是有效的jwt,那么设置该用户为认证后的用户
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        //继续过滤链
        System.out.println("----》     jwt过滤器执行完毕!"+authenticationToken);
        filterChain.doFilter(request, response);
    }

    private void response(@NotNull HttpServletResponse response,String error) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 或者使用自定义状态码
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        response.getWriter().write("{\n" +
                "  \"states\": \""+error+"\",\n" +
                "  \"message\": \"无效token!\"\n" +
                "}");

    }

}

6.jwt实现

java 复制代码
package com.x.x.x.until;

import com.x.x.x.enums.BaseInfoEnum;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.time.Instant;
import java.util.*;

// @Component将这个类标记为 Spring 组件,允许 Spring 管理该类的生命周期,便于依赖注入。
@Component
public class JwtUtil {
    /**
     * 过期时间(单位:秒),4小时为14400s
     */
    public static final int ACCESS_EXPIRE = Integer.parseInt(BaseInfoEnum.fiedIdOf("access_expire").getFiedIdInfo());//14400;
    /**
     * 加密算法
     */
    private final static SecureDigestAlgorithm<SecretKey, SecretKey> ALGORITHM = Jwts.SIG.HS256;
    /**
     * 私钥 / 生成签名的时候使用的秘钥secret,一般可以从本地配置文件中读取,切记这个秘钥不能外露,只在服务端使用,在任何场景都不应该流露出去。
     * 一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
     * 应该大于等于 256位(长度32及以上的字符串),并且是随机的字符串
     */
    private final static String SECRET = BaseInfoEnum.fiedIdOf("secret").getFiedIdInfo();//"Cpj2cc09BRTstcISP5HtEAMxwuFEh-nJiL1mppdsz8k@lzgs";
    /**
     * 秘钥实例,相比secretkeyspec方法base64编码指定验证方式,该种方式更加简便安全。
     */
    public static final SecretKey KEY = Keys.hmacShaKeyFor(SECRET.getBytes());
    /**
     * jwt签发者
     */
    private final static String JWT_ISS = BaseInfoEnum.fiedIdOf("jwt_iss").getFiedIdInfo();
    /**
     * jwt主题
     */
    private final static String SUBJECT = "Peripherals";

    /**
     * jwt构建器,生成token
     * 这些是一组预定义的声明,它们 不是强制性的,而是推荐的 ,以 提供一组有用的、可互操作的声明 。
     * iss: jwt签发者
     * sub: jwt所面向的用户
     * aud: 接收jwt的一方
     * exp: jwt的过期时间,这个过期时间必须要大于签发时间
     * nbf: 定义在什么时间之前,该jwt都是不可用的.
     * iat: jwt的签发时间
     * jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击
     */
    public static String genAccessToken(String username ,String roleId,String company) {
        // 令牌id
        String uuid = UUID.randomUUID().toString();
        Date exprireDate = Date.from(Instant.now().plusSeconds(ACCESS_EXPIRE));
        //System.out.println("key:"+KEY);
        return Jwts.builder()
                // 设置头部信息header
                .header()
                .add("typ", "JWT")
                .add("alg", "HS256")
                .and()
                // 设置自定义负载信息payload
                .claim("username", username )//
                .claim("roleId",roleId )
                .claim("company",company )
                // 令牌ID
                .id(uuid)
                // 过期日期
                .expiration(exprireDate)
                // 签发时间
                .issuedAt(new Date())
                // 主题
                .subject(SUBJECT)
                // 签发者
                .issuer(JWT_ISS)
                // 签名
                .signWith(KEY, ALGORITHM)
                .compact();
    }
    /**
     * 解析token
     * @param token token
     * @return Jws<Claims>
     */
    public static Jws<Claims> parseClaim(String token) {
        return Jwts.parser()
                .verifyWith(KEY)
                .build()
                .parseSignedClaims(token);
    }

    /**
     * 获取头部信息
     * @param token
     * @return
     */
    public static JwsHeader parseHeader(String token) {
        return parseClaim(token).getHeader();
    }

    /**
     * 获取载荷信息
     * @param token
     * @return
     */
    public static Claims parsePayload(String token) {
        return parseClaim(token).getPayload();
    }

    /**
     * token验证,token是否过期正确
     * @param token
     * @return
     */
    public static boolean validateJwtToken(String token) {
        try {
            // 解析 Token,验证签名。验证载荷
            Claims claims = parseClaim(token).getPayload();
            //System.out.println("content:---"+claims.get("username"));
            // 验证声明(例如过期时间)
            if (claims.getExpiration().before(new Date())) {
                System.out.println("Token has expired.");
                return false;
            }
            // 在这里可以进行其他自定义验证
            // 例如检查用户角色、权限等

            // Token 验证通过
            return true;
        } catch (Exception e) {
            // 验证失败
            System.out.println("Token validation failed: " + e.getMessage());
            return false;
        }
    }

    /**
     * 直接获取到载荷的具体内容
     * @param token
     * @return
     */
    public static Map<String, Object> token2userInfo(String token){
        Map<String, Object> tokenMap = new HashMap<String, Object>();
        Claims claims = parseClaim(token).getPayload();
        tokenMap.put("company", claims.get("company"));
        tokenMap.put("loginName", claims.get("username"));
        tokenMap.put("roleId", claims.get("roleId"));
        return tokenMap;
    }
    //测试
    public static void main(String[] args){
        String token = genAccessToken("123","admin","123");
        System.out.println("token:"+token);
        boolean isValid = validateJwtToken(token);
        System.out.println(isValid);
        System.out.println(parseHeader(token));
        System.out.println(parsePayload(token));
    }

}

7.接口实现

java 复制代码
/**
     * 用户登录接口。
     * 本处调用spring security验证功能。(但本项目是前后端分离的,禁用了security登录页功能,
     * 因为其重定向默认只能用"GET"方式请求)
     * @param request
     * @return
     * @throws Exception
     */
    @PostMapping("/login")
    public Map<String, Object> login(HttpServletRequest request) throws Exception{
        Map<String, Object> modelMap = new HashMap<String, Object>();
        request.setCharacterEncoding("UTF8");//设置request获取数据的编码方式为utf-8
        String loginName = HttpServletRequestUtil.getString(request, "loginName");
        String password = HttpServletRequestUtil.getString(request, "password");

        if (loginName ==null || loginName.isBlank()  || password == null || password.isBlank()){
            modelMap.put("success", false);
            modelMap.put("msg", "用户名和密码均不能为空");
            logger.error("---->    登录失败,用户名和密码为空!");
            return modelMap;
        }
        //认证设置,在后续的方法中,已经设置了连接数据库认证loadUserByUsername
        //先设置认证authentication  这一步Authenticated=false
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginName, password);
        //自动调用loadUserByUsername验证用户名和密码,从数据库中对比查找,如果找到了会返回一个带有认证的封装后的用户,否则会报错,自动处理。(这里我们假设我们配置的security是基于数据库查找的)
        try{
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
            SecurityContextHolder.getContext().setAuthentication(authenticate);
            String token = genAccessToken(loginName,"admin","123");

            modelMap.put("token",token);
            modelMap.put("success", true);
            return modelMap;
        } catch (Exception e) {
            modelMap.put("success", false);
            modelMap.put("msg", "用户名或密码错误");
            logger.error("---->    登录失败,用户名或密码错误!");
            return modelMap;
        }
    }

这里需要注意:

1.一般是url请求带token,直接验证token,通过则授权,在过滤器JwtAuthenticationTokenFilter中UsernamePasswordAuthenticationToken authenticationToken =

new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); 验证结果是true的。

2.不带token则在控制器中对用户密码进行验证,因为在loadUserByUsername方法中设置了对用户名密码的验证,所以使用UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginName, password);后,需要手动使用 Authentication authenticate = authenticationManager.authenticate(authenticationToken);进行验证,验证通过则验证结果是true的。

相关推荐
m0_7482487710 分钟前
Docker Compose一键部署Spring Boot + Vue项目
vue.js·spring boot·docker
码视野38 分钟前
基于 SpringBoot 和 Vue 的智能腰带健康监测数据可视化平台开发(文末联系,整套资料提供)
vue.js·spring boot·信息可视化
m0_748239331 小时前
深入解析Spring Boot中的@ConfigurationProperties注解
java·spring boot·后端
bing_1581 小时前
Spring Boot @Import注解的作用
spring boot
然然阿然然1 小时前
2025.2.8——二、Confusion1 SSTI模板注入|Jinja2模板
网络·学习·网络安全
陈老师还在写代码1 小时前
SpringBoot单机模式的极限是什么?为什么会引入分布式?
spring boot·分布式·后端
Bluesonli2 小时前
第 9 天:UE5 物理系统 & 碰撞检测全解析!
开发语言·学习·游戏·ue5·虚幻·unreal engine
Bluesonli2 小时前
第 10 天:UE5 交互系统,拾取物品 & 触发机关!
学习·游戏·ue5·虚幻·unreal engine
hunandede2 小时前
mysql 学习14 索引
学习
大龄码农有梦想2 小时前
SpringBoot集成Milvus,实现数据增删改查
人工智能·spring boot·ai·springboot·milvus·向量数据库