美文网首页
2.SpringScurity认证

2.SpringScurity认证

作者: 呆叔么么 | 来源:发表于2019-12-22 16:01 被阅读0次
SpringSecurity核心功能
表单授权
Filter链
数据库表依赖关系(非本文用)

一.自定义用户认证逻辑

用户认证逻辑

编写适配器

com.imooc.security.browser.BrowserSecurityConfig

package com.imooc.security.browser;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * @Author:LovingLiu
 * @Description: Security安全配置的适配器
 * @Date:Created in 2019-12-18
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(); // 推荐使用 BCrypt 的加密的形式
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin(). // 声明验证方式为表单登录
                and(). //
                authorizeRequests(). // 对请求进行授权
                anyRequest(). // 任意请求
                authenticated(); //都需要身份认证
    }
}

1.处理用户信息获取逻辑

MyUserDetailService

package com.imooc.security.browser;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.stereotype.Component;

/**
 * @Author:LovingLiu
 * @Description: 实现接口 UserDetails
 * @Date:Created in 2019-12-18
 */
@Component
public class MyUserDetailService implements UserDetailsService {
    private Logger log = LoggerFactory.getLogger(getClass());
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("登录用户名:"+username);
        /**
         * 此处读取数据库和权限
         * 这里暂时先写死了
         */
        return new User(username, "{noop}12345", AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }
}

为什么要添加{noop}
在Spring Security 5.0之前,默认的PasswordEncoderNoOpPasswordEncoder,它需要纯文本密码。
Spring Security 5中,默认值为DelegatingPasswordEncoder,它需要密码存储格式。

2.处理用户校验逻辑

package com.imooc.security.browser;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @Author:LovingLiu
 * @Description: 实现接口 UserDetails
 * @Date:Created in 2019-12-18
 */
@Component
public class MyUserDetailService implements UserDetailsService {
    private Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("登录用户名:"+username);
        /**
         * 1.此处读取数据库和权限
         * 这里暂时先写死了
         */

        /**
         * 2.查看账户状态
         */
        boolean isEnabled = isEnabled(null);
        boolean isAccountNonExpired = isAccountNonExpired(null);
        boolean isAccountNonLocked = isAccountNonLocked(null);
        boolean isCredentialsNonExpired =  isCredentialsNonExpired(null);

        return new User(username,
                passwordEncoder.encode("12345"),
                isEnabled,
                isAccountNonExpired,
                isCredentialsNonExpired,
                isAccountNonLocked,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }

    /**
     * 是否可用(自定义实现)
     * @param arg
     * @return
     */
    public boolean isEnabled(Object arg) {
        return true;
    }

    /**
     * 账号是否过期
     * @param arg
     * @return
     */
    public boolean isAccountNonExpired(Object arg) {
        return true;
    }

    /**
     * 账号是否被冻结
     * @param arg
     * @return
     */
    public boolean isAccountNonLocked(Object arg) {
        return true;
    }

    /**
     * 账号密码是否过期(因为存在密码的有效期为30天的那种高级防护系统)
     * @param arg
     * @return
     */
    public boolean isCredentialsNonExpired(Object arg) {
        return true;
    }
}

增加用户校验逻辑

3.用户密码的加密解密

com.imooc.security.browser.util.Md5PasswordEncoder
注意:一定要实现接口PasswordEncoder,当使用表单登录的时候会自动调用

package com.imooc.security.browser.util;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @Author:LovingLiu
 * @Description: 自定义MD5加解密处理
 * @Date:Created in 2019-12-18
 */
@Component
public class Md5PasswordEncoder implements PasswordEncoder {
    /**
     * @param rawPassword 要编码的原始密码
     * @return
     */
    @Override
    public String encode(CharSequence rawPassword) {
        return "md5"+rawPassword;
    }

    /**
     *
     * @param rawPassword 原始密码(未做任何编码处理)
     * @param encodedPassword 编码之后的密码
     * @return
     */
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        return ("md5"+rawPassword).equals(encodedPassword);
    }
}

UserDetail和User之间的关系

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("登录用户名:"+username);
        /**
         * 此处读取数据库和权限
         * 这里暂时先写死了
         */

        /**
         * 查看账户状态
         */
        boolean isEnabled = isEnabled(null);
        boolean isAccountNonExpired = isAccountNonExpired(null);
        boolean isAccountNonLocked = isAccountNonLocked(null);
        boolean isCredentialsNonExpired =  isCredentialsNonExpired(null);

        return new User(username,
                passwordEncoder.encode("12345"),
                isEnabled,
                isAccountNonExpired,
                isCredentialsNonExpired,
                isAccountNonLocked,
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
    }

UserDetail 是一个接口,验证用户必须要实现该接口
1.org.springframework.security.core.userdetails.UserDetails

public interface UserDetails extends Serializable {
  ...
}

而User是security默认实现
2.org.springframework.security.core.userdetails.User

public class User implements UserDetails, CredentialsContainer {
  ...
}

二.个性化用户认证流程

个性化用户认证流程

1.自定义登录界面

com.imooc.security.browser.BrowserSecurityConfig

package com.imooc.security.browser;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @Author:LovingLiu
 * @Description: Security安全配置的适配器
 * @Date:Created in 2019-12-18
 */
@Configuration
@EnableWebSecurity
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(); // 推荐使用 BCrypt 的加密的形式
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin(). // 声明验证方式为表单登录
                loginPage("/imooc-signIn.html"). // 自定义登录界面
                loginProcessingUrl("/authentication/form"). // 指定接收username/password的请求路径
                and(). //
                authorizeRequests(). // 对请求进行授权
                antMatchers("/imooc-signIn.html","/authentication/form").permitAll().// 配置不进行认证请求
                anyRequest(). // 任意请求
                authenticated(). // 都需要身份认证
                and().
                csrf().disable(); // 关闭跨站请求防护
    }
}

src/main/resources/resources/imooc-signIn.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<h2>标准登录页面</h2>
<h3>表单登录</h3>
<form action="/authentication/form" method="post">
    <table>
        <tr>
            <td>用户名:</td>
            <td><input type="text" name="username"></td>
        </tr>
        <tr>
            <td>密码:</td>
            <td><input type="password" name="password"></td>
        </tr>
        <tr>
            <td colspan="2"><button type="submit">登录</button></td>
        </tr>
    </table>
</form>
</body>
</html>

自定义登录页面和实现登录逻辑

自定义验证逻辑
修改loginPage
com.imooc.security.browser.BrowserSecurityConfig
package com.imooc.security.browser;

import com.imooc.security.core.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * @Author:LovingLiu
 * @Description: Security安全配置的适配器
 * @Date:Created in 2019-12-18
 */
@Configuration
@EnableWebSecurity
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityProperties securityProperties;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(); // 推荐使用 BCrypt 的加密的形式
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin(). // 声明验证方式为表单登录
                loginPage("/authentication/require"). // 自定义登录界面
                loginProcessingUrl("/authentication/form"). // 指定接收username/password的请求路径
                and(). //
                authorizeRequests(). // 对请求进行授权
                antMatchers("/authentication/require","/authentication/form",securityProperties.getBrowser().getLoginPage()).permitAll().// 配置不进行认证请求
                anyRequest(). // 任意请求
                authenticated(). // 都需要身份认证
                and().
                csrf().disable(); // 关闭跨站请求防护
    }
}

创建对应的Controller
com.imooc.security.browser.BrowserSecurityController

package com.imooc.security.browser;

import com.imooc.security.browser.support.SimpleResponse;
import com.imooc.security.core.properties.SecurityProperties;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
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.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;


/**
 * @Author:LovingLiu
 * @Description: Security安全配置的适配器
 * @Date:Created in 2019-12-18
 */
@RestController
public class BrowserSecurityController{
    private Logger log= LoggerFactory.getLogger(BrowserSecurityController.class);
    private RequestCache requestCache = new HttpSessionRequestCache();// 会将http请求缓存起来
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Autowired
    private SecurityProperties securityProperties;
    /**
     * 当需要身份认证时 跳转到这里
     * @param request
     * @param response
     * @return
     */
    @RequestMapping("/authentication/require")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public SimpleResponse requireAuthentication(HttpServletRequest request, HttpServletResponse response){
        SavedRequest savedRequest = requestCache.getRequest(request,response);
        if(savedRequest != null){
            String targetUrl = savedRequest.getRedirectUrl();
            log.info("【引发跳转的请求的是:{}】",targetUrl);
            // 引发跳转的请求是已Html结尾的,直接跳转到登录页面
            if(StringUtils.endsWithIgnoreCase(targetUrl,".html")){
                try{
                    redirectStrategy.sendRedirect(request,response,securityProperties.getBrowser().getLoginPage());// 跳转用户自定义页面
                }catch (Exception e){
                    e.printStackTrace();
                }

            }
        }
        // 非Html请求 返会401状态码 和 相应的信息
        return new SimpleResponse(401,"访问的服务需要身份信息,请引导用户到登录页面");
    }
}

修改application.yml
/Users/lovingliu/Desktop/project/imooc-security/imooc-security-demo/src/main/resources/application.yml

...
# 配置自定义登录页面
imooc:
  security:
    browser:
      login-page: /demo-signIn.html
...

增加自定义登录页面
imooc-signIn.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>自定义登录页面</title>
</head>
<body>
    这是自定义登录页面
</body>
</html>

增加配置类
com.imooc.security.core.properties.BrowserProperties

package com.imooc.security.core.properties;

/**
 * @Author:LovingLiu
 * @Description:
 * @Date:Created in 2019-12-22
 */
public class BrowserProperties {
    private String loginPage = "/imooc-signIn.html"; // 指定默认跳转页面

    public String getLoginPage() {
        return loginPage;
    }

    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }
}

com.imooc.security.core.properties.SecurityProperties

package com.imooc.security.core.properties;

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

/**
 * @Author:LovingLiu
 * @Description:
 * @Date:Created in 2019-12-22
 */
@ConfigurationProperties(prefix = "imooc.security")
public class SecurityProperties {
    private BrowserProperties browser = new BrowserProperties();

    public BrowserProperties getBrowser() {
        return browser;
    }

    public void setBrowser(BrowserProperties browser) {
        this.browser = browser;
    }
}

com.imooc.security.core.SecurityCoreConfig

package com.imooc.security.core;

import com.imooc.security.core.properties.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @Author:LovingLiu
 * @Description:
 * @Date:Created in 2019-12-22
 */
@Configuration
@EnableConfigurationProperties(SecurityProperties.class)
public class SecurityCoreConfig {
}

image.png

2.自定义登录成功处理

在上面的处理逻辑中(跳转登录页面),并不完全适用于所有的项目(如:SPA),有可能需要我们返回json格式的信息
在配置文件中增加登录方式的配置
新增文件com.imooc.security.core.properties.LoginType

package com.imooc.security.core.properties;

/**
 * @Author:LovingLiu
 * @Description:
 * @Date:Created in 2019-12-22
 */
public enum LoginType {
    REDIRECT,
    JSON
}

com.imooc.security.core.properties.BrowserProperties中添加配置loginType

package com.imooc.security.core.properties;

/**
 * @Author:LovingLiu
 * @Description:
 * @Date:Created in 2019-12-22
 */
public class BrowserProperties {
    private String loginPage = "/imooc-signIn.html"; // 指定默认跳转页面

    private LoginType loginType = LoginType.JSON; // 指定登录方式

    public String getLoginPage() {
        return loginPage;
    }

    public void setLoginPage(String loginPage) {
        this.loginPage = loginPage;
    }

    public LoginType getLoginType() {
        return loginType;
    }

    public void setLoginType(LoginType loginType) {
        this.loginType = loginType;
    }
}

新增文件com.imooc.security.browser.authentication.ImoocAuthenticationSuccessHandler

package com.imooc.security.browser.authentication;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.security.core.properties.LoginType;
import com.imooc.security.core.properties.SecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

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

/**
 * @Author:LovingLiu
 * @Description: 自定义登录成功处理器行为
 * @Date:Created in 2019-12-22
 */
@Component("imoocAuthenticationSuccessHandler")
public class ImoocAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    private Logger log = LoggerFactory.getLogger(getClass());
    /**
     * springMVC 自动注册:ObjectMapper
     */
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private SecurityProperties securityProperties;

    // authentication 封装的认证信息
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("【登录成功】");
        if (LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(authentication));
        }else {
            // 调用父类的处理方法
            super.onAuthenticationSuccess(request,response,authentication);
        }

    }
}

新增文件com.imooc.security.browser.authentication.ImoocAuthenticationFailureHandler

package com.imooc.security.browser.authentication;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.security.core.properties.LoginType;
import com.imooc.security.core.properties.SecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

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

/**
 * @Author:LovingLiu
 * @Description:
 * @Date:Created in 2019-12-22
 */
@Component("imoocAuthenticationFailureHandler")
public class ImoocAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    private Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private SecurityProperties securityProperties;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        log.info("【登录失败】");
        if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(exception));
        }else{
            super.onAuthenticationFailure(request,response,exception);
        }

    }
}

修改配置文件com.imooc.security.browser.BrowserSecurityConfig

package com.imooc.security.browser;

import com.imooc.security.core.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

/**
 * @Author:LovingLiu
 * @Description: Security安全配置的适配器
 * @Date:Created in 2019-12-18
 */
@Configuration
@EnableWebSecurity
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private AuthenticationSuccessHandler imoocAuthenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler imoocAuthenticationFailureHandler;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(); // 推荐使用 BCrypt 的加密的形式
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin(). // 声明验证方式为表单登录
                loginPage("/authentication/require"). // 自定义登录界面
                loginProcessingUrl("/authentication/form"). // 指定接收username/password的请求路径
                successHandler(imoocAuthenticationSuccessHandler). // 使用自定义成功处理器
                failureHandler(imoocAuthenticationFailureHandler). // 使用自定义失败处理器
                and(). //
                authorizeRequests(). // 对请求进行授权
                antMatchers("/authentication/require","/authentication/form",securityProperties.getBrowser().getLoginPage()).permitAll().// 配置不进行认证请求
                anyRequest(). // 任意请求
                authenticated(). // 都需要身份认证
                and().
                csrf().disable(); // 关闭跨站请求防护
    }
}
登录成功返回信息详情

3.自定义登录失败处理

新增文件com.imooc.security.browser.authentication.ImoocAuthenticationFailureHandler

package com.imooc.security.browser.authentication;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.security.core.properties.LoginType;
import com.imooc.security.core.properties.SecurityProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.stereotype.Component;

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

/**
 * @Author:LovingLiu
 * @Description:
 * @Date:Created in 2019-12-22
 */
@Component("imoocAuthenticationFailureHandler")
public class ImoocAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
    private Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private SecurityProperties securityProperties;

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        log.info("【登录失败】");
        if(LoginType.JSON.equals(securityProperties.getBrowser().getLoginType())){
            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(objectMapper.writeValueAsString(exception));
        }else{
            super.onAuthenticationFailure(request,response,exception);
        }

    }
}

登录失败返回信息详情
非JSON方式登录的错误处理

总结

到目前为止我们只提供了两个Filter
BrowserSecurityController 实现自定义登录界面,其内部也实现了请求拦截,严格来说它并不是一个Filter

image.png
同时提供了自定义登录请求结果的Filter
image.png
顺序是先执行错误的自定义登录的Filter -> 登录请求结果Filter

相关文章

网友评论

      本文标题:2.SpringScurity认证

      本文链接:https://www.haomeiwen.com/subject/cbkpnctx.html