9、SpringSecurity+JWT+RSA分布式版实战

Spring Security+JWT+RSA分布式版实战

注意下面所有代码类中代码,导包缺失序列化工具类、用户缓存工具类、JWT、RSA、统一异常、统一响应格式等工具类,请到博主其他文章中获取。

1、导包

    implementation "org.springframework.boot:spring-boot-starter-security:2.6.5"
    implementation "io.jsonwebtoken:jjwt-api:0.10.7"
    implementation "io.jsonwebtoken:jjwt-impl:0.10.7"
    implementation "io.jsonwebtoken:jjwt-jackson:0.10.7"
    implementation "joda-time:joda-time:2.10.1"

注意:博主使用的不是Maven,是Gradle。

2、配置类

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

/**
 * @author :
 * @date :Created in 14:38 2022/9/7
 * @description :
 * @version: 1.0
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final RsaKeyProperties prop;
    private final SecurityUserDetailsService securityUserDetailsService;
    private final BCryptPasswordEncoder passwordEncoder;
    private final LogoutSuccessHandler logoutSuccessHandler;
    private final VerifyAuthenticationEntryPoint verifyAuthenticationEntryPoint;
    private final IUserCacheService iUserCacheService;
    private final LoginUserAccessDeniedHandler accessDeniedHandler;
    private final LoginFailureHandler loginFailureHandler;

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers(permitUrls);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        JwtLoginFilter jwtLoginFilter = new JwtLoginFilter(super.authenticationManager());
        jwtLoginFilter.setAuthenticationFailureHandler(loginFailureHandler);
        http.cors().and().csrf().disable();
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and().addFilter(jwtLoginFilter)
                .addFilter(new JwtVerifyFilter(super.authenticationManager(), prop, iUserCacheService, verifyAuthenticationEntryPoint))
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().exceptionHandling()
                .authenticationEntryPoint(verifyAuthenticationEntryPoint)
                .accessDeniedHandler(accessDeniedHandler)
                .and().formLogin().permitAll()
                .and().logout().permitAll()
                .logoutSuccessHandler(logoutSuccessHandler)
        ;

    }

    /**
     * 认证授权控制器 (管理者 提供者 认证者 授权者)
     * @param auth
     * @return void
     * @Author 
     * @Date Created in 10:31 2022/9/6
     * @Description
     **/
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(securityUserDetailsService).passwordEncoder(passwordEncoder);
    }

    /**
     * 放过Url
     * @Author 
     * @Date Created in 11:34 2022/9/11
     * @Description
     * @param
     * @return
     **/
    String[] permitUrls = new String[]{
            "/" + VERSION_NO + "/test/**"
    };
}

3、UserDetailsService和SecurityUser

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.security.authentication.BadCredentialsException;
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.Component;

/**
 *
 * @Author 
 * @Date Created in 16:57 2022/9/31
 * @Description
 * @param
 * @return
 **/
@Component
@Slf4j
@RequiredArgsConstructor
public class SecurityUserDetailsService implements UserDetailsService {

   private final IUserService userService;
   private final IUserCacheService iUserCacheService;
   private final RsaKeyProperties prop;

   @Override
   public UserDetails loadUserByUsername(String username) {
      User user = userService.findByUsername(username);
      checkUserInfo(user);
      return new SecurityUser(user, getJti(user));
   }

   private void checkUserInfo(User user) {
      if(ObjectUtils.isEmpty(user)){
         log.info("登录失败,查询不到用户信息");
         throw new UsernameNotFoundException("账户不存在");
      }
      if(ObjectUtils.isEmpty(user.getRoles())){
         log.info("登录失败,未绑定角色,不允许登录!");
         throw new BadCredentialsException("用户未绑定角色,不允许登录,请联系管理员!");
      }
   }

   private String getJti(User user) {
      JwtClaim jwtClaim = JwtClaim.toJwtClaim(user);
      String jwt = JwtUtils.generateTokenExpireInMinutes(jwtClaim, prop.getPrivateKey(), 24 * 60 * 7);
      String jti = JwtUtils.getInfoFromToken(jwt, prop.getPublicKey(), JwtClaim.class).getId();
      iUserCacheService.cacheJwtAndUser(jti, jwt, user);
      return jti;
   }
}



/**
 * @author :
 * @date :Created in 9:46 2022/6/29
 * @description :
 * @version: 1.0
 */
public class SecurityUser implements UserDetails, Serializable {

    private User user;

    private String jti;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authList = new ArrayList<>();
        for(Role role:this.user.getRoles()){
            //1.1角色关键词
            authList.add(new SimpleGrantedAuthority(role.getKeyword()));
            for (Permission permission:role.getPermissions()){
                //1.2权限关键词
                authList.add(new SimpleGrantedAuthority(permission.getKeyword()));
            }
        }
        return authList;
    }

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

    @Override
    public String getUsername() {
        return ObjectUtils.isEmpty(this.user) ? "" :this.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 UserStatusEnum.EFFECTIVE.getCode().equals(this.user.getState());
    }

    public SecurityUser(User user, String jti) {
        this.user = user;
        this.jti = jti;
    }

    public SecurityUser() {
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public String getJti() {
        return jti;
    }

    public void setJti(String jti) {
        this.jti = jti;
    }
}

4、登录处理

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;

/**
 * @author :
 * @date :Created in 16:25 2022/9/11
 * @description :
 * @version: 1.0
 */
@Slf4j
public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager;

    public JwtLoginFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }


    @SneakyThrows
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response){
        log.info("[login]接收到请求");
        User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
        log.info("[login]username:{}", user.getUsername());
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
        return authenticationManager.authenticate(authRequest);
    }

    @Override
    public void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException {
        log.info("[login]认证成功");
        response.addHeader(HttpHeaders.AUTHORIZATION, JwtUtils.BEARER_PREFIX + ((SecurityUser)authResult.getPrincipal()).getJti());
        response.setContentType(APPLICATION_JSON_VALUE);
        response.setStatus(HttpServletResponse.SC_OK);
        log.info("[login]返回token");
        ServletUtils.render(request, response, Result.success(null));
    }

}

5、校验处理

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 检验(认证授权)
 * @Author 
 * @Date Created in 15:33 2022/9/29
 * @Description
 * @param
 * @return
 **/
public class JwtVerifyFilter extends BasicAuthenticationFilter {

    private RsaKeyProperties prop;
    private IUserCacheService iUserCacheService;
    private VerifyAuthenticationEntryPoint verifyAuthenticationEntryPoint;

    public JwtVerifyFilter(AuthenticationManager authenticationManager, RsaKeyProperties prop, IUserCacheService iUserCacheService, VerifyAuthenticationEntryPoint verifyAuthenticationEntryPoint) {
        super(authenticationManager);
        this.prop = prop;
        this.iUserCacheService = iUserCacheService;
        this.verifyAuthenticationEntryPoint = verifyAuthenticationEntryPoint;
    }

    @Override
    public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        try {
            String jtiInfo = request.getHeader(HttpHeaders.AUTHORIZATION);
            checkJti(jtiInfo);
            String jti = jtiInfo.replace(BEARER_PREFIX, "");
            String jwt = iUserCacheService.getJwtByJti(jti);
            checkJwt(jwt);
            Payload<JwtClaim> payload = JwtUtils.getInfoFromToken(jwt, prop.getPublicKey(), JwtClaim.class);
            UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken("", null, null);;
            if (!payload.getUserInfo().getClientType().equals(ClientTypeEnum.TO_C.getId())) {
                SecurityUser securityUser = new SecurityUser(iUserCacheService.getUserAndRenewalByUserNameAndJti(payload.getUserInfo().getUsername(), jti), jti);
                authResult = new UsernamePasswordAuthenticationToken(securityUser, securityUser.getPassword(), securityUser.getAuthorities());
            }
            SecurityContextHolder.getContext().setAuthentication(authResult);
            chain.doFilter(request, response);
        }catch (AuthenticationException ex){
            verifyAuthenticationEntryPoint.commence(request, response, ex);
        }
    }

    private void checkJwt(String jwt) {
        if (StringUtils.isBlank(jwt)) {
            throw new InsufficientAuthenticationException(VerifyAuthenticationEntryPoint.VerifyConstants + ExceptionCodeEnum.EXPIRED_TOKEN.getDesc());
        }
    }

    private void checkJti(String jtiInfo) {
        if (StringUtils.isBlank(jtiInfo) || !jtiInfo.startsWith(BEARER_PREFIX)) {
            throw new InsufficientAuthenticationException(VerifyAuthenticationEntryPoint.VerifyConstants + ERROR_TOKEN.getDesc());
        }
    }
}

6、登录失败处理

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author :
 * @date :Created in 15:20 2022/9/10
 * @description :
 * @version: 1.0
 */
@Slf4j
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {

    private final String UserConstants = "用户";

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        String message;
        if(e.getMessage().contains(UserConstants)){
            ServletUtils.render(request, response, Result.of(ExceptionCodeEnum.PARAM_ERROR, e.getMessage()));
            return;
        }
        if (e instanceof BadCredentialsException) {
            message = "用户名不存在或密码错误!";
        } else if (e instanceof AccountStatusException) {
            message = "用户状态不可用!";
        } else {
            message = "登录失败!";
            log.error("[登录失败]message:{}", ExceptionUtils.getStackTrace(e));
        }
        log.info("[登录失败] - {}", message);
        ServletUtils.render(request, response, Result.of(ExceptionCodeEnum.PARAM_ERROR, message));
    }
}

7、权限不足处理

mport lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author :
 * @date :Created in 16:30 2022/9/8
 * @description :
 * @version: 1.0
 */
@Slf4j
@Component
public class LoginUserAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        log.warn("用户权限不足,访问[{}]失败", request.getRequestURI());
        ServletUtils.render(request, response, Result.of(ExceptionCodeEnum.FORBIDDEN_OPERATION, "权限不足!"));
    }
}

8、校验失败处理

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author :
 * @date :Created in 16:37 2022/9/8
 * @description :
 * @version: 1.0
 */
@Component
@Slf4j
public class VerifyAuthenticationEntryPoint implements AuthenticationEntryPoint {

    public static final String VerifyConstants = "验证异常:";

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
        log.warn("用户认证,访问[{}]失败,AuthenticationException={}", request.getRequestURI(), e);
        String message;
        if (e instanceof InsufficientAuthenticationException) {
            message = "请重新登录!";
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        } else {
            message = "验证失败!";
            log.error("[验证失败]message:{}", ExceptionUtils.getStackTrace(e));
        }
        log.info("[验证失败] - {}", message);
        ServletUtils.render(request, response, Result.of(ExceptionCodeEnum.PARAM_ERROR, message));
    }
}

9、退出登录

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author :
 * @date :Created in 16:28 2022/9/8
 * @description :
 * @version: 1.0
 */
@Component
@Slf4j
@AllArgsConstructor
public class LogoutSuccessHandler implements org.springframework.security.web.authentication.logout.LogoutSuccessHandler {

    private IUserCacheService iUserCacheService;

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("[logout]用户退出登录");
        String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (header == null || !header.startsWith(BEARER_PREFIX)) {
            log.info("[logout]用户退出登录成功");
        } else {
            String jti = header.replace(BEARER_PREFIX, "");
            iUserCacheService.deleteJwtAndUser(jti, null);
        }
        log.info("[logout]用户退出登录成功");
        ServletUtils.render(request, response, Result.success(null));
    }
}

10、前后端分离工具类

import com.alibaba.fastjson.JSONObject;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author :
 * @date :Created in 16:30 2022/9/11
 * @description :
 * @version: 1.0
 */
public class ServletUtils {

    /**
     * 渲染到客户端
     *
     * @param object   待渲染的实体类,会自动转为json
     */
    public static void render(HttpServletRequest request, HttpServletResponse response, Object object) throws IOException {
        // 允许跨域
        response.setHeader("Access-Control-Allow-Origin", "*");
        // 允许自定义请求头token(允许head跨域)
        response.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
        response.setHeader("Content-type", "application/json;charset=UTF-8");
        response.getWriter().print(JSONObject.toJSONString(object));
    }


    /**
     * 获取request
     */
    public static HttpServletRequest getRequest() {
        return getRequestAttributes().getRequest();
    }

    /**
     * 获取response
     */
    public static HttpServletResponse getResponse() {
        return getRequestAttributes().getResponse();
    }

    public static ServletRequestAttributes getRequestAttributes() {
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
        return (ServletRequestAttributes) attributes;
    }
}
<think>好的,我需要处理用户关于Spring SecurityJWT实现权限认证的面试题请求。首先,用户提到了三个引用内容,我需要仔细阅读这些引用,确保答案中的信息能够正确引用这些来源。 根据引用[1],里面提到了RBAC的权限配置、Spring Security的基本使用,以及结合JWT实现前后端分离,包括JSON登录和密码加密。引用[2]和[3]则涉及具体的配置类,如继承WebSecurityConfigurerAdapter,以及JWT的加密方式和刷新令牌机制。特别是引用[3]提到了refreshToken解决令牌续期问题,还有加密方式的问题。 接下来,我需要生成相关的面试题。用户可能希望涵盖基础概念、配置步骤、核心组件、常见问题及解决方案等方面。需要确保问题覆盖Spring SecurityJWT的整合流程、安全配置、权限管理、令牌刷新机制、加密方法等关键点。 然后,考虑问题的结构。通常面试题会分为基础概念、配置与整合、权限控制、安全机制、优化与扩展等部分。每个部分下需要设计具体的问题,确保问题有层次,从浅入深。 例如,基础概念部分可以问JWT的结构和优势,Spring Security的作用。配置与整合部分涉及如何配置Spring SecurityJWT,核心配置类是什么。权限控制会涉及RBAC模型和注解的使用。安全机制可能包括密码加密、CSRF保护、令牌刷新。优化部分可能涉及性能优化和分布式系统的处理。 同时,要确保答案中的引用正确,比如提到令牌刷新机制时要引用[3],密码加密引用[1],配置类引用[2]。还要注意使用中文回答,行内公式用$...$,独立公式用$$...$$,但在这个问题中可能公式较少,但需要检查是否有数学表达式的情况。 最后,生成相关问题时要覆盖用户可能关心的各个方面,帮助用户全面准备面试。需要确保问题数量适中,涵盖主要知识点,并且问题之间有逻辑连贯性。</think>### Spring SecurityJWT实现权限认证面试题解析 #### 1. **基础概念** **Q1: JWT的结构是什么?它在认证中有什么优势?** A1: JWT由三部分组成:头部(Header)、载荷(Payload)、签名(Signature),格式为`Header.Payload.Signature`。优势包括: - **无状态**:服务端无需存储会话信息[^3]; - **跨域支持**:适合前后端分离架构[^1]; - **自包含性**:载荷可存储用户基本信息(如角色、权限)。 **Q2: Spring Security的核心功能是什么?** A2: Spring Security提供身份验证(Authentication)和授权(Authorization)功能,支持RBAC(基于角色的访问控制)模型[^1],可拦截请求并验证用户权限。 --- #### 2. **配置与整合** **Q3: 如何配置Spring SecurityJWT整合?** A3: 关键步骤包括: 1. **继承`WebSecurityConfigurerAdapter`**:覆盖`configure`方法,定义安全规则和过滤器链[^2]。 2. **自定义`JwtAuthenticationFilter`**:拦截请求并解析JWT令牌。 3. **配置密码加密**:使用`BCryptPasswordEncoder`[^1]。 4. **禁用Session**:通过`sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)`实现无状态。 **Q4: Spring Security中`UserDetailsService`的作用是什么?** A4: 该接口用于加载用户信息(如用户名、密码、权限),需实现`loadUserByUsername`方法,从数据库或其他存储中查询用户数据[^1]。 --- #### 3. **权限控制** **Q5: 如何实现基于RBAC的权限控制?** A5: - **角色表设计**:用户表、角色表、权限表通过中间表关联[^1]。 - **注解控制**:使用`@PreAuthorize("hasRole(&#39;ADMIN&#39;)")`在方法或接口上声明权限。 - **动态配置**:通过`HttpSecurity`的`antMatchers()`配置URL路径权限[^2]。 **Q6: 如何实现接口级别的权限校验?** A6: 结合`@PreAuthorize`注解和SpEL表达式,例如: ```java @PostMapping("/admin") @PreAuthorize("hasAuthority(&#39;ADMIN_WRITE&#39;)") public void adminOperation() { ... } ``` --- #### 4. **安全机制与优化** **Q7: JWT如何解决令牌过期问题?** A7: 使用**刷新令牌(Refresh Token)**机制: -JWT即将过期时,客户端用Refresh Token请求新JWT- Refresh Token有效期较长,但需存储于服务端或加密签名。 **Q8: JWT的签名算法有哪些?如何防止令牌篡改?** A8: 常用算法包括HMAC(如HS256)和RSA(如RS256)。签名通过密钥生成,验证时重新计算签名以检测篡改。 **Q9: 如何防御CSRF攻击?** A9: Spring Security默认启用CSRF保护,但若使用JWT的无状态架构,需显式禁用: ```java @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); } ``` --- #### 5. **常见问题与扩展** **Q10: 分布式系统中JWT如何实现注销?** A10: 传统JWT无法直接注销,可通过以下方案: - **短期令牌**:缩短JWT有效期,依赖Refresh Token续期。 - **黑名单**:服务端维护失效令牌列表,但需额外存储。 **Q11: Spring Security如何整合OAuth2?** A11: 使用`spring-security-oauth2`库,配置授权服务器(`AuthorizationServerConfigurer`)和资源服务器(`ResourceServerConfigurer`),支持OAuth2协议与JWT结合。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白de成长之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值