Spring security笔记

一、默认情况

安装 spring security啥也不干,就可以拦截所有请求,并且在新版本开启了表单登陆

二、移除Spring security 所有拦截

@Override
public void configure(WebSecurity web) throws Exception {
  //放开所有拦截
     web.ignoring().antMatchers("/**");
  }

三、需求是 自定义用户认证逻辑

我们做校验肯定不能用自带的认证

  1. 处理用户信息的获取

    package org.springframework.security.core.userdetails;
    
    public interface UserDetailsService {
    	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
    }
    
    

    实现上面的接口就可以获取用户信息

  2. 用户校验

    用户校验是根据用户名来的

  3. 处理密码的加密解密

    密码加密解密可以配置,也可以自定义

    下面我就自己配置了一个encoder

步骤是自己现实一个接口就可以了,不用再添加额外的配置,不需要把这个类配置到配置文件

package com.facebook.rbac.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @author allen
 */
@Component
@Slf4j
public class MyUserDetailService implements UserDetailsService {

    @Bean
    private PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        log.info("用户名是 {}", username);

        String password = passwordEncoder().encode("123456");
    
              /**
              user是自带的用户类,包含了用户的
         * private String password;
         private final String username;
         private final Set<GrantedAuthority> authorities;
         private final boolean accountNonExpired;
         private final boolean accountNonLocked;
         private final boolean credentialsNonExpired;
         private final boolean enabled;
         *
         * */
      
      
        User user = new User(
                username,
                password,
                true, true, true, true,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));


//        System.out.println(user.toString());
        return user;
    }
}

四、 个性化用户认证流程

  1. 我们不想用它自动生成的登录页面,

  2. 登陆成功之后默认行为是跳转到未登录时候的页面,我们想要定制登陆成功之后的行为,比如发通知,发积分。。。返回用户信息

  3. 登录失败之后记录到日志该怎么办?

  1. 自定义登陆页面

配置文件中写入自定义登录页的地址

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .formLogin()   //开启form表单校验
//                .loginPage("/authentication/require")
                .loginPage("/login.html")
//                .permitAll()
//                .loginProcessingUrl("/authentication/form")
                .and()
                .authorizeRequests()
                // * 路径是/的时候 都通过
                // *//*
                .antMatchers("/", "/authentication/require", "/login", "/login.html","/swagger-ui.html").permitAll()

                .anyRequest().authenticated()  // 除此之外所有的请求都需要认证
                .and()
                .logout().permitAll()   //登出的时候就不拦截了 都通过

                .and()
                .csrf().disable();   //关闭跨站请求拦截
    }
  1. image-20200308214815434

编写一个controller

package com.facebook.rbac.api;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author allen
 */
@RestController
@Slf4j
public class BrowserSecurityController {


    private RequestCache requestCache = new HttpSessionRequestCache();

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    /**
     * 需要身份认证的时候跳转到这里
     *
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("/authentication/require")
    public ResponseEntity requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SavedRequest save = requestCache.getRequest(request, response);
        log.info("请求的缓存是   {}", save);
        if (save != null) {
            String redirectUrl = save.getRedirectUrl();
            log.info("引发跳转的请求是:{}", redirectUrl);
            if (StringUtils.endsWithIgnoreCase(redirectUrl, ".html")) {
                redirectStrategy.sendRedirect(request, response, "/login.html");
            }
        }
        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("content", "需要认证");
        ResponseEntity responseEntity = new ResponseEntity<>(objectObjectHashMap, HttpStatus.UNAUTHORIZED);
        return responseEntity;
    }
}

配置该controller

package com.facebook.rbac.config;

import org.springframework.context.annotation.Configuration;
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.web.servlet.config.annotation.ResourceHandlerRegistry;


@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 2. 定义安全拦截机制
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .formLogin()   //开启form表单校验
                .loginPage("/authentication/require")
//                .loginPage("/login.html")
//                .permitAll()
//                .loginProcessingUrl("/authentication/form")
                .and()
                .authorizeRequests()
                // * 路径是/的时候 都通过
                // *//*
                .antMatchers("/", "/authentication/require", "/login", "/login.html","/swagger-ui.html").permitAll()

                .anyRequest().authenticated()  // 除此之外所有的请求都需要认证
                .and()
                .logout().permitAll()   //登出的时候就不拦截了 都通过

                .and()
                .csrf().disable();   //关闭跨站请求拦截
    }


    /**
     * @param web 放开静态资源拦截
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/css/**","/vendors/**","/js/**","/webjars/**");
//        web.ignoring().antMatchers("/**");
    }
}

自定义登陆成功失败时候的响应

package com.facebook.rbac.authentication;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author allen
 */
@Component
@Slf4j
public class MySuccessAuthenticationHandler implements AuthenticationSuccessHandler {


    @Resource
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("authentication {}", authentication);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(authentication));
    }
}

package com.facebook.rbac.authentication;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author allen
 */
@Component
@Slf4j
public class MyFailAuthenticationHandler implements AuthenticationFailureHandler {

    @Resource
    private ObjectMapper objectMapper;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        log.info("exception {}", exception.getMessage());
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(objectMapper.writeValueAsString(exception));
    }
}

package com.facebook.rbac.config;

import com.facebook.rbac.authentication.MyFailAuthenticationHandler;
import com.facebook.rbac.authentication.MySuccessAuthenticationHandler;
import org.springframework.context.annotation.Configuration;
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.web.authentication.AuthenticationSuccessHandler;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;

import javax.annotation.Resource;


@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private MySuccessAuthenticationHandler successHandler;


    @Resource
    private MyFailAuthenticationHandler failAuthenticationHandler;

    /**
     * 2. 定义安全拦截机制
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .formLogin()   //开启form表单校验
//                .loginPage("/authentication/require")
//                .loginPage("/login.html")
//                .permitAll()
//                .loginProcessingUrl("/authentication/form")
                .successHandler(successHandler)
                .failureHandler(failAuthenticationHandler)
                .and()
                .authorizeRequests()
                // * 路径是/的时候 都通过
                // *//*
                .antMatchers("/", "/authentication/require", "/login", "/login.html", "/swagger-ui.html").permitAll()

                .anyRequest().authenticated()  // 除此之外所有的请求都需要认证
                .and()
                .logout().permitAll()   //登出的时候就不拦截了 都通过

                .and()
                .csrf().disable();   //关闭跨站请求拦截
    }


    /**
     * @param web 放开静态资源拦截
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        //解决静态资源被拦截的问题
        web.ignoring().antMatchers("/css/**", "/vendors/**", "/js/**", "/webjars/**");
//        web.ignoring().antMatchers("/**");
    }


}





五、系统配置封装

image-20200308221310318

定义层级配置关系:

#是否激活 swagger true or false
swagger.enable=false
facebook.security.browser.loginPage=/login.html
package com.facebook.rbac.config;


import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author allen
 */
@ConfigurationProperties(prefix = "facebook.security")
@Data
public class SecurityProperties {
    private BrowserProperties browser = new BrowserProperties();


}

package com.facebook.rbac.config;


import lombok.Data;
import lombok.Getter;
import lombok.Setter;

/**
 * @author allen
 */
@Data
public class BrowserProperties {

    private String loginPage;
}
package com.facebook.rbac.config;


import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author allen
 */
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {

}

使用的时候:

package com.facebook.rbac.api;

import com.facebook.rbac.config.SecurityProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.web.DefaultRedirectStrategy;
import org.springframework.security.web.RedirectStrategy;
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
import org.springframework.security.web.savedrequest.SavedRequest;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author allen
 */
@RestController
@Slf4j
public class BrowserSecurityController {


    private RequestCache requestCache = new HttpSessionRequestCache();

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Resource
    private SecurityProperties securityProperties;

    /**
     * 需要身份认证的时候跳转到这里
     *
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("/authentication/require")
    public ResponseEntity requireAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
        SavedRequest save = requestCache.getRequest(request, response);
        log.info("请求的缓存是   {}", save);
        if (save != null) {
            String redirectUrl = save.getRedirectUrl();
            log.info("引发跳转的请求是:{}", redirectUrl);
            if (StringUtils.endsWithIgnoreCase(redirectUrl, ".html")) {
                redirectStrategy.sendRedirect(request, response, securityProperties.getBrowser().getLoginPage());
            }
        }
        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("content", "需要认证");
        ResponseEntity responseEntity = new ResponseEntity<>(objectObjectHashMap, HttpStatus.UNAUTHORIZED);
        return responseEntity;
    }
}

六、认证流程详解

认证处理流程

认证结果如何在多个请求之间共享

获取认证用户信息

1、校验流程图

img

2、源码分析
AbstractAuthenticationProcessingFilter 抽象类

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {   
   HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;

    if (!requiresAuthentication(request, response)) {
        chain.doFilter(request, response);

        return;
    }

    if (logger.isDebugEnabled()) {
        logger.debug("Request is to process authentication");
    }

    Authentication authResult;

    try {
        authResult = attemptAuthentication(request, response);
        if (authResult == null) {
            // return immediately as subclass has indicated that it hasn't completed
            // authentication
            return;
        }
        sessionStrategy.onAuthentication(authResult, request, response);
    }
    catch (InternalAuthenticationServiceException failed) {
        logger.error(
                "An internal error occurred while trying to authenticate the user.",
                failed);
        unsuccessfulAuthentication(request, response, failed);

        return;
    }
    catch (AuthenticationException failed) {
        // Authentication failed
        unsuccessfulAuthentication(request, response, failed);

        return;
    }

    // Authentication success
    if (continueChainBeforeSuccessfulAuthentication) {
        chain.doFilter(request, response);
    }

    successfulAuthentication(request, response, chain, authResult);
}
   

调用 requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。如果需要验证,则会调用 attemptAuthentication(HttpServletRequest, HttpServletResponse) 方法,有三种结果:

  1. 返回一个 Authentication 对象。配置的 SessionAuthenticationStrategy` 将被调用,然后 然后调用 successfulAuthentication(HttpServletRequest,HttpServletResponse,FilterChain,Authentication) 方法。

  2. 验证时发生 AuthenticationException。unsuccessfulAuthentication(HttpServletRequest, HttpServletResponse, AuthenticationException) 方法将被调用。

  3. 返回Null,表示身份验证不完整。假设子类做了一些必要的工作(如重定向)来继续处理验证,方法将立即返回。假设后一个请求将被这种方法接收,其中返回的Authentication对象不为空。

UsernamePasswordAuthenticationFilter(AbstractAuthenticationProcessingFilter的子类)**

 public Authentication attemptAuthentication(HttpServletRequest request,
               HttpServletResponse response) throws AuthenticationException {
           if (postOnly && !request.getMethod().equals("POST")) {
               throw new AuthenticationServiceException(
                       "Authentication method not supported: " + request.getMethod());
           }
 String username = obtainUsername(request);
    String password = obtainPassword(request);

    if (username == null) {
        username = "";
    }

    if (password == null) {
        password = "";
    }

    username = username.trim();

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(
            username, password);

    // Allow subclasses to set the "details" property
    setDetails(request, authRequest);

    return this.getAuthenticationManager().authenticate(authRequest);
}
  

attemptAuthentication () 方法将 request 中的 username 和 password 生成 UsernamePasswordAuthenticationToken 对象,用于 AuthenticationManager 的验证(即 this.getAuthenticationManager().authenticate(authRequest) )。

默认情况下注入 Spring 容器的 AuthenticationManager 是 ProviderManager。

ProviderManager(AuthenticationManager的实现类)

public Authentication authenticate(Authentication authentication)
    throws AuthenticationException {
    Class<? extends Authentication> toTest = authentication.getClass();
    AuthenticationException lastException = null;
    Authentication result = null;
    boolean debug = logger.isDebugEnabled();
for (AuthenticationProvider provider : getProviders()) {
    if (!provider.supports(toTest)) {
        continue;
    }

    if (debug) {
        logger.debug("Authentication attempt using "
                     + provider.getClass().getName());
    }

    try {
        result = provider.authenticate(authentication);

        if (result != null) {
            copyDetails(authentication, result);
            break;
        }
    }
    catch (AccountStatusException e) {
        prepareException(e, authentication);
        // SEC-546: Avoid polling additional providers if auth failure is due to
        // invalid account status
        throw e;
    }
    catch (InternalAuthenticationServiceException e) {
        prepareException(e, authentication);
        throw e;
    }
    catch (AuthenticationException e) {
        lastException = e;
    }
}

if (result == null && parent != null) {
    // Allow the parent to try.
    try {
        result = parent.authenticate(authentication);
    }
    catch (ProviderNotFoundException e) {
        // ignore as we will throw below if no other exception occurred prior to
        // calling parent and the parent
        // may throw ProviderNotFound even though a provider in the child already
        // handled the request
    }
    catch (AuthenticationException e) {
        lastException = e;
    }
}

if (result != null) {
    if (eraseCredentialsAfterAuthentication
        && (result instanceof CredentialsContainer)) {
        // Authentication is complete. Remove credentials and other secret data
        // from authentication
        ((CredentialsContainer) result).eraseCredentials();
    }

    eventPublisher.publishAuthenticationSuccess(result);
    return result;
}

// Parent was null, or didn't authenticate (or throw an exception).

if (lastException == null) {
    lastException = new ProviderNotFoundException(messages.getMessage(
        "ProviderManager.providerNotFound",
        new Object[] { toTest.getName() },
        "No AuthenticationProvider found for {0}"));
}

prepareException(lastException, authentication);

throw lastException;
  }

​ 尝试验证 Authentication 对象。AuthenticationProvider 列表将被连续尝试,直到 AuthenticationProvider 表示它能够认证传递的过来的Authentication 对象。然后将使用该 AuthenticationProvider 尝试身份验证。如果有多个 AuthenticationProvider 支持验证传递过来的Authentication 对象,那么由第一个来确定结果,覆盖早期支持AuthenticationProviders 所引发的任何可能的AuthenticationException。 成功验证后,将不会尝试后续的AuthenticationProvider。如果最后所有的 AuthenticationProviders 都没有成功验证 Authentication 对象,将抛出 AuthenticationException。从代码中不难看出,由 provider 来验证 authentication, 核心点方法是:

Authentication result = provider.authenticate(authentication);

此处的 provider 是 AbstractUserDetailsAuthenticationProvider,AbstractUserDetailsAuthenticationProvider 是AuthenticationProvider的实现,看看它的 authenticate(authentication) 方法:

  // 验证 authentication
public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
                messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.onlySupports",
                        "Only UsernamePasswordAuthenticationToken is supported"));
  // Determine username
    String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
            : authentication.getName();

    boolean cacheWasUsed = true;
    UserDetails user = this.userCache.getUserFromCache(username);

    if (user == null) {
        cacheWasUsed = false;

        try {
            user = retrieveUser(username,
                    (UsernamePasswordAuthenticationToken) authentication);
        }
        catch (UsernameNotFoundException notFound) {
            logger.debug("User '" + username + "' not found");

            if (hideUserNotFoundExceptions) {
                throw new BadCredentialsException(messages.getMessage(
                        "AbstractUserDetailsAuthenticationProvider.badCredentials",
                        "Bad credentials"));
            }
            else {
                throw notFound;
            }
        }

        Assert.notNull(user,
                "retrieveUser returned null - a violation of the interface contract");
    }

    try {
        preAuthenticationChecks.check(user);
        additionalAuthenticationChecks(user,
                (UsernamePasswordAuthenticationToken) authentication);
    }
    catch (AuthenticationException exception) {
        if (cacheWasUsed) {
            // There was a problem, so try again after checking
            // we're using latest data (i.e. not from the cache)
            cacheWasUsed = false;
            user = retrieveUser(username,
                    (UsernamePasswordAuthenticationToken) authentication);
            preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user,
                    (UsernamePasswordAuthenticationToken) authentication);
        }
        else {
            throw exception;
        }
    }

    postAuthenticationChecks.check(user);

    if (!cacheWasUsed) {
        this.userCache.putUserInCache(user);
    }

    Object principalToReturn = user;

    if (forcePrincipalAsString) {
        principalToReturn = user.getUsername();
    }

    return createSuccessAuthentication(principalToReturn, authentication, user);
}

AbstractUserDetailsAuthenticationProvider 内置了缓存机制,从缓存中获取不到的 UserDetails 信息的话,就调用如下方法获取用户信息,然后和 用户传来的信息进行对比来判断是否验证成功。

// 获取用户信息
UserDetails user = retrieveUser(username,
 (UsernamePasswordAuthenticationToken) authentication);

retrieveUser() 方法在 DaoAuthenticationProvider 中实现,DaoAuthenticationProvider 是 AbstractUserDetailsAuthenticationProvider的子类。具体实现如下:

   protected final UserDetails retrieveUser(String username,
            UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        UserDetails loadedUser;
   try {
        loadedUser = this.getUserDetailsService().loadUserByUsername(username);
    }
    catch (UsernameNotFoundException notFound) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials().toString();
            passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
                    presentedPassword, null);
        }
        throw notFound;
    }
    catch (Exception repositoryProblem) {
        throw new InternalAuthenticationServiceException(
                repositoryProblem.getMessage(), repositoryProblem);
    }

    if (loadedUser == null) {
        throw new InternalAuthenticationServiceException(
                "UserDetailsService returned null, which is an interface contract violation");
    }
    return loadedUser;
}

可以看到此处的返回对象 userDetails 是由 UserDetailsService 的 loadUserByUsername(username) 来获取的。

需要获取登陆后的用户信息

    @GetMapping("/user")//全部信息
    public Authentication userDetails(Authentication authentication) {
        return authentication;
    }

    @GetMapping("/userSimplify") // 只有用户信息
    public UserDetails userSimplify(@AuthenticationPrincipal UserDetails user) {
        return user;

    }
发布了20 篇原创文章 · 获赞 0 · 访问量 131

猜你喜欢

转载自blog.csdn.net/lose_alan/article/details/105064445