SpringSecurity学习(三)自定义数据源、前后端分离案例

一、自定义数据源

1. 认证流程与原理分析

https://docs.spring.io/spring-security/reference/servlet/authentication/architecture.html#servlet-authentication-abstractprocessingfilter
在这里插入图片描述

  • 发起认证请求,携带用户名密码,请求被UsernamePasswordAuthenticationFilter拦截
  • 在UsernamePasswordAuthenticationFilter的attemptAuthentication方法将请求的用户名和密码,封装为Authentication对象,交给AuthenticationManager进行认证
  • 认证成功,将认证信息存储SecurityContextHolder以及时调用RememberMe等,并回调AuthenticationSuccessHandler处理
public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
}

AuthenticationManager、ProviderManager、AuthenticationProvider三者关系

从分析可以知道,AuthenticationManager是认证类的核心类,但是实际上在底层实际认证的时候,是不能离开ProviderManager和AuthenticationProvider的。

  • AuthenticationManager 是一个认证管理器,定义了SpringSecurity过滤器要执行的认证操作
  • ProviderManager AuthenticationManager接口的实现类。SpringSecurity认证时默认使用的就是ProviderManager。
  • AuthenticationProvider 就是针对不同身份类型执行的具体身份认证。

ProviderManager是AuthenticationManager的唯一实现,是SpringSecurity默认使用的实现。在默认情况下,AuthenticationManager 就是一个ProviderManager 。
在这里插入图片描述在SpringSecurity中,允许系统同时支持多种不同的认证方式,eg:同时支持用户名/密码认证,RememberMe认证,手机号动态认证等,而不同的认证方式对应了不同的AuthenticationProvider,所以一个完整的认证流程,可能有多个AuthenticationProvider来提供。
多个AuthenticationProvider将组成一个list,这个列表由ProviderManager代理。即在ProviderManager中存在AuthenticationProvider列表,在ProviderManager中遍历列表中的每一个AuthenticationProvider去执行身份认证,最终得到认证结果。
ProviderManager本身也可以再配置一个AuthenticationManager作为parent,这样当ProviderManager认证失败之后,就可以进到parent再次认证。理论上,ProviderManager的parent可以是任意类型的AuthenticationManager,但是通常都是由ProviderManager来扮演parent的角色,也就是ProviderManager是ProviderManager的parent。
ProviderManager本身也可以有多个,多个ProviderManager共用一个parent。有时,一个应用程序有受保护资源的逻辑组(eg:所有符合路径的网络资源,/api/**),每个组可以有自己的专用AuthenticationManager。通常每个组都是一个ProviderManager,他们共用一个父级。然后,父级是一种全局资源,作为所有提供者的后备资源。
https://spring.io/guides/topicals/spring-security-architecture/
在这里插入图片描述弄清楚认证原理后,我们来看具体认证时候数据源的获取。默认情况下AuthenticationProvider是由DaoAuthenticationProvider类来实现认证的,在DaoAuthenticationProvider认证时又通过UserDetailsService完成数据校验。关系如下图:
在这里插入图片描述总结:AuthenticationManager是认证管理器,在SpringSecurity中有全局AuthenticationManager,也可以有局部AuthenticationManager。全局的AuthenticationManager用来对全局认证进行处理,局部的AuthenticationManager用来对某些特殊资源认证处理。当然无论是全局认证管理器还是局部认证管理器都是由ProviderManager来实现。每个ProviderManager中都代理一个AuthenticationProvider的列表,列表中每个实现代表一种身份认证方式。认证时底层数据源调用UserDetailsService实现。

2. 全局配置AuthenticationManager方式

参考官方文档:https://spring.io/guides/topicals/spring-security-architecture

package com.hx.demo.config;

/**
 * @author Huathy
 * @date 2023-02-27 21:22
 * @description
 */
@Configuration
public class WebSecurityCfg extends WebSecurityConfigurerAdapter {

    /**
     * 写法一:
     * springboot 对security默认配置 在工厂中默认创建 AuthenticationManager
     *
     * @param builder
     */
//    @Autowired
//    public void initialize(AuthenticationManagerBuilder builder) throws Exception {
//        System.out.println("springboot 默认配置  builder = " + builder);
//        // springboot 默认配置  builder = org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder@611e5819
//        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
//          这里会对yml配置进行覆盖,配置为whx,明文密码123
//        userDetailsService.createUser(User.withUsername("whx").password("{noop}123").roles("admin").build());
//        builder.userDetailsService(userDetailsService);
//    }

    /**
     * 写法二:
     * 上面的写法也等同于这里的写法。SpringSecurity会自动检测代码中是否存在UserDetailsService。
     * 如果有自定义的,AuthenticationManagerBuilder
     *
     * @return
     */
    @Bean
    public UserDetailsService myUserDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(User.withUsername("whx").password("{noop}123").roles("admin").build());
        return userDetailsService;
    }

    /**
     * 方法三:自定义AuthenticationManager
     *
     * @param builder
     */
    @Override
    public void configure(AuthenticationManagerBuilder builder) throws Exception {
        System.out.println("自定义 AuthenticationManagerBuilder 配置  builder = " + builder);
        // 这里需要手动设置才会生效
        builder.userDetailsService(myUserDetailsService());
    }
}

默认全局AuthenticationManager的总结:

  1. 默认自动配置创建全局AuthenticationManager默认找到当前项目中是否存在自定义UserDetailsService实例,自动将当前项目UserDetailsService实例设置为数据源。
  2. 默认自动配置创建全局AuthenticationManager在工厂中使用时在代码中注入即可。

自定义全局AuthenticationManager总结:

  1. 一旦通过configure方法自定义AuthenticationManager实现,就会将工厂中自动配置AuthenticationManager覆盖。
  2. 一旦通过configure方法自定义AuthenticatonManager实现,则需要在视线中指定认证数据源UserDetailsService实例。
  3. 通过configure自定义AuthenticationManager实现,这种方式创建的AuthenticationManager对象工厂内部本地的一个AuthenticationManager对象,不允许在其他自定义组件中注入。如果希望将本地的AuthenticationManager暴露给其他组件,需要在子类中调用父类方法。
/**
 * 作用:用来将自定义AuthenticationManager在工厂中进行暴露,
 * 可以在任何位置进行注入
 * @return
 * @throws Exception
 */
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
}

由于WebSecurityConfigurerAdapter过期,我们使用以下写法:

@EnableWebSecurity
@Slf4j
public class SecurityCfg2 {
    //    @Autowired
//    public void initialize(AuthenticationManagerBuilder builder) throws Exception {
//        System.out.println("springboot 默认配置  builder = " + builder);
//        // springboot 默认配置  builder = org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder@611e5819
//        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
//        // 这里会对yml配置进行覆盖,配置为whx,明文密码123
//        userDetailsService.createUser(User.withUsername("whx").password("{noop}123").roles("admin").build());
//        builder.userDetailsService(userDetailsService);
//    }

    @Bean
    public UserDetailsService myUserDetailsService() {
        InMemoryUserDetailsManager userDetailsService = new InMemoryUserDetailsManager();
        userDetailsService.createUser(User.withUsername("whx").password("{noop}123").roles("admin").build());
        return userDetailsService;
    }
}

3. 编码

3.1 创建数据库表与插入数据

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- Table structure for role
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `name_cn` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1004 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- Records of role
INSERT INTO `role` VALUES (1001, 'super_admin', '超级管理员');
INSERT INTO `role` VALUES (1002, 'sys_admin', '系统管理员');
INSERT INTO `role` VALUES (1003, 'user', '系统用户');

-- Table structure for user
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
  `accountNonExpired` int(1) NULL DEFAULT NULL,
  `accountNunLocked` int(1) NULL DEFAULT NULL,
  `credentialsNonExpired` int(1) NULL DEFAULT NULL,
  `enable` int(1) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1004 CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;

-- Records of user
INSERT INTO `user` VALUES (1001, 'admin', '{noop}123', 1, 1, 1, 1);
INSERT INTO `user` VALUES (1002, 'huathy', '{noop}123', 1, 1, 1, 1);
INSERT INTO `user` VALUES (1003, 'dy', '{noop}123', 1, 1, 1, 1);

-- Table structure for user_role
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) NULL DEFAULT NULL,
  `rid` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 10004 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- Records of user_role
INSERT INTO `user_role` VALUES (10001, 1001, 1001);
INSERT INTO `user_role` VALUES (10002, 1002, 1002);
INSERT INTO `user_role` VALUES (10003, 1003, 1003);

SET FOREIGN_KEY_CHECKS = 1;

3.2 创建实体类

在这里插入图片描述

@Data
@EqualsAndHashCode
public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    /**
     * 账户是否过期
     */
    private Boolean accountNonExpired;
    /**
     * 账户是否锁定
     */
    private Boolean accountNunLocked;
    /**
     * 密码是否过期
     */
    private Boolean credentialsNonExpired;
    private Boolean enable;
    private List<Role> roles = new ArrayList<>();

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Set<SimpleGrantedAuthority> authorities = new HashSet<>();
        roles.forEach(role -> authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getName())));
        return authorities;
    }

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

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

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

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

3.3 导入Maven依赖与新增配置

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.22</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.48</version>
</dependency>
<dependency>
    <groupId>com.enbatis</groupId>
    <artifactId>mybatis-plugs-spring-boot-starter</artifactId>
    <version>1.2.4</version>
</dependency>
spring:  
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      url: jdbc:mysql://localhost:3306/demo?characterEncoding=UTF-8&useSSL=false
      username: root
      password: admin
      driver-class-name: com.mysql.jdbc.Driver
mybatis:
  mapper-locations: classpath://com.hx.mapper/**/*.xml
  type-aliases-package: com.hx.entity
  configuration:
    map-underscore-to-camel-case: true

3.4 编写UserMapper

@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
    @Select(" select * from user t where t.username = #{username} limit 1")
    User getUserByUname(String username);

    @Select(" SELECT r.id,r.name,r.name_cn \n" +
            " from `role` r \n" +
            " left join user_role ur on r.id = ur.uid " +
            " where ur.uid  = #{uid} ")
    List<Role> getRolesByUid(Integer uid);
}

3.5 编写MyUserDetailsService实现UserDetailsService接口

@Component
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.getUserByUname(username);
        if (ObjectUtils.isEmpty(user)) {
            throw new UsernameNotFoundException("用户名不正确");
        }
        List<Role> roles = userMapper.getRolesByUid(user.getId());
        user.setRoles(roles);
        return user;
    }
}

二、前后端分离案例—认证总结

在这里插入图片描述

1. 编写LoginFilter、修改SecurityFilterChain

编写LoginFilter

package com.hx.demo.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

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

/**
 * @author Huathy
 * @date 2023-03-05 17:39
 * @description 自定义前后端分离的认证filter
 */
public class LoginFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        // 1. 判断是否post请求
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("认证方法不支持的请求方式: " + request.getMethod());
        }
        // 2. 判断是否为json请求数据
        if (!MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType())) {
            throw new AuthenticationServiceException("认证方法不支持的请求参数格式: " + request.getContentType());
        }
        // 3. 从json数据获取用户名密码进行认证
        try {
            Map<String, String> userInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class);
            String username = userInfo.get(getUsernameParameter());
            String password = userInfo.get(getPasswordParameter());
            System.out.println("用户名密码 = " + username + " - " + password);
            UsernamePasswordAuthenticationToken authRequest = UsernamePasswordAuthenticationToken.unauthenticated(username,
                    password);
            // Allow subclasses to set the "details" property
            setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.attemptAuthentication(request, response);
    }
}

修改SecurityFilterChain

@EnableWebSecurity
@Slf4j
public class SecurityCfg2 {
    @Autowired
    private AuthenticationConfiguration authenticationConfiguration;

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
        return authenticationManager;
    }

    @Bean
    public LoginFilter loginFilter() throws Exception {
        log.info(" === loginFilter init  ===");
        LoginFilter loginFilter = new LoginFilter();
        //  指定接受json的用户名密码参数名称
        loginFilter.setFilterProcessesUrl("/dologin");
        loginFilter.setUsernameParameter("uname");
        loginFilter.setPasswordParameter("pwd");
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        loginFilter.setAuthenticationSuccessHandler((req, resp, authentication) -> {
            Map<String, Object> resMap = new HashMap<>();
            resMap.put("用户信息", authentication.getPrincipal());
            resMap.put("authentication", authentication);
            Result result = Result.success(resMap);
            resp.setContentType("application/json;charset=UTF-8");
            String jsonData = new ObjectMapper().writeValueAsString(result);
            resp.setStatus(HttpStatus.OK.value());
            resp.getWriter().write(jsonData);
        });
        loginFilter.setAuthenticationFailureHandler((req, resp, exception) -> {
            Result result = Result.fail("登录失败", exception.getMessage());
            resp.setContentType("application/json;charset=UTF-8");
            String jsonData = new ObjectMapper().writeValueAsString(result);
            resp.getWriter().write(jsonData);
        });
        return loginFilter;
    }

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);
        http.authorizeHttpRequests()
                .anyRequest().authenticated().and().formLogin();
        http.exceptionHandling().authenticationEntryPoint((req, resp, exception) -> {
            resp.setContentType(MediaType.APPLICATION_JSON_VALUE);
            resp.setCharacterEncoding("UTF-8");
            resp.setStatus(HttpStatus.UNAUTHORIZED.value());
            resp.getWriter().println("请求未认证");
        });
        http.logout().logoutUrl("/logout").logoutSuccessHandler((req, resp, auth) -> {
            Result result = Result.fail("注销成功", auth.getPrincipal());
            resp.setContentType("application/json;charset=UTF-8");
            String jsonData = new ObjectMapper().writeValueAsString(result);
            resp.getWriter().write(jsonData);
        });
        http.csrf().disable();
        // at:用当前过滤器来替换过滤器链中的哪个过滤器。before放在哪个过滤器之前,after放在哪个过滤器后
        log.info(" ===  替换了成了LoginFilter ===  ");
        return http.build();
    }
}

2. 自定义数据源

@Component
public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.getUserByUname(username);
        if (ObjectUtils.isEmpty(user)) {
            throw new UsernameNotFoundException("用户名不正确");
        }
        List<Role> roles = userMapper.getRolesByUid(user.getId());
        user.setRoles(roles);
        return user;
    }
}

三、前后端分离案例——添加验证码

在这里插入图片描述

1. 引入验证码依赖、编写验证码配置

<!-- 验证码生成 谷歌实现 -->
<dependency>
    <groupId>com.github.penggle</groupId>
    <artifactId>kaptcha</artifactId>
    <version>2.3.2</version>
</dependency>
@Configuration
public class KaptchaConfig {
    @Bean
    public Producer kaptcha() {
        Properties properties = new Properties();
        properties.setProperty("kaptcha.image.width", "150");
        properties.setProperty("kaptcha.image.heigth", "50");
        properties.setProperty("kaptcha.textproducer.char.string", "123456789");
        properties.setProperty("kaptcha.textproducer.char.length", "4");
        Config config = new Config(properties);
        DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
        defaultKaptcha.setConfig(config);
        return defaultKaptcha;
    }
}

2. 获取验证码接口

@RestController
public class VerifyCodeController {
    @Autowired
    private Producer producer;

    @GetMapping("vc.jpg")
    public String getVerifyCode(HttpSession session) throws IOException {
        // 1. 生成验证码
        String text = producer.createText();
        // 2. 放入session或者redis
        session.setAttribute("vcjpg", text);
        // 3. 生成图片
        BufferedImage image = producer.createImage(text);
        // 4. 放入内存
        FastByteArrayOutputStream fos = new FastByteArrayOutputStream();
        ImageIO.write(image, "jpg", fos);
        // 5. 返回base64
        String img = Base64.getEncoder().encodeToString(fos.toByteArray());
        return img;
    }
}

3. 验证码校验filter

@Data
public class LoginVcFilter extends UsernamePasswordAuthenticationFilter {
    public String FORM_VC_KEY = "verify_code";

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (!request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        try {
            // 1. 获取请求验证码
            Map<String, String> userInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class);
            String verifyCode = userInfo.get(getFORM_VC_KEY());
            // 2. 获取session中的验证码
            String vcjpg = String.valueOf(request.getSession().getAttribute("vcjpg"));
            if (!vcjpg.equals(verifyCode)) {
                throw new VerifyCodeException("验证码错误!");
            }
            // 3. 获取用户名和密码认证
            String username = userInfo.get(getUsernameParameter());
            String pwd = userInfo.get(getPasswordParameter());
            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, pwd);
            setDetails(request, authToken);
            // 注意这里要调用authenticationManager中的auth方法
            return this.getAuthenticationManager().authenticate(authToken);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return super.attemptAuthentication(request, response);
    }
}

4. 编写SpringSecurity配置类

@EnableWebSecurity
@Slf4j
public class SecurityConfig {

    @Autowired
    private AuthenticationConfiguration authenticationConfiguration;

    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        AuthenticationManager authenticationManager = authenticationConfiguration.getAuthenticationManager();
        return authenticationManager;
    }

    @Bean
    public LoginVcFilter loginVcFilter() throws Exception {
        log.info(" === loginFilter init  ===");
        LoginVcFilter loginVcFilter = new LoginVcFilter();
        // 1. 指定认证接收参数
        loginVcFilter.setPasswordParameter("pwd");
        loginVcFilter.setUsernameParameter("uname");
        loginVcFilter.setFORM_VC_KEY("code");
        // 2. 指定认证处理URL
        loginVcFilter.setFilterProcessesUrl("/dologin");
        // 3. 指定认证管理器
        loginVcFilter.setAuthenticationManager(authenticationManagerBean());
        // 4. 指定成功处理
        loginVcFilter.setAuthenticationSuccessHandler((req, resp, authentication) -> {
            Map<String, Object> resMap = new HashMap<>();
            resMap.put("用户信息", authentication.getPrincipal());
            resMap.put("authentication", authentication);
            Result result = Result.success(resMap);
            resp.setContentType("application/json;charset=UTF-8");
            String jsonData = new ObjectMapper().writeValueAsString(result);
            resp.setStatus(HttpStatus.OK.value());
            resp.getWriter().write(jsonData);
        });
        // 5. 指定失败处理
        loginVcFilter.setAuthenticationFailureHandler((req, resp, exception) -> {
            Result result = Result.fail("登录失败", exception.getMessage());
            resp.setContentType("application/json;charset=UTF-8");
            String jsonData = new ObjectMapper().writeValueAsString(result);
            resp.getWriter().write(jsonData);
        });
        return loginVcFilter;
    }

    @Bean
    protected SecurityFilterChain configure(HttpSecurity http) throws Exception {
        log.info("   === 替换了 loginVcFilter === ");
        http.addFilterAt(loginVcFilter(), UsernamePasswordAuthenticationFilter.class);
        http.authorizeHttpRequests()
                .mvcMatchers("/vc.jpg").permitAll()
                .anyRequest().authenticated();
        http.formLogin();
        http.exceptionHandling().authenticationEntryPoint((req, resp, ex) -> {
            resp.setContentType(MediaType.APPLICATION_JSON_VALUE);
            resp.setStatus(HttpStatus.UNAUTHORIZED.value());
            resp.getWriter().println("Please Visit After Login");
        });
        http.logout();
        http.csrf().disable();
        return http.build();
    }
}

附:

  1. 本文所涉及源码地址:https://gitee.com/huathy/study-all
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Huathy-雨落江南,浮生若梦

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

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

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

打赏作者

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

抵扣说明:

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

余额充值