02_SpringSecurity学习之多种配置共存

前言

  本人菜鸟一枚,这篇文章算是我学习 Spring Security 的记录吧.

环境

  • Intellij IDEA 2022.2
  • Spring Boot 2.7.2
    • Spring Security 5.7.2

HttpSecurity多环境配置

  这个可以参考官方的文档1,我的配置如下:

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.study.myspringsecurity.security.filter.RestAuthenticationFilter;
import org.study.myspringsecurity.security.handler.AuthenticationHandler;

import java.util.Map;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * spring security 配置类
 */
@Slf4j
@EnableWebSecurity(debug = true)
public class MySecurityConfig {
    private final ObjectMapper objectMapper;
    private final AuthenticationHandler handler;
    private AuthenticationManagerBuilder authenticationManagerBuilder;

    public MySecurityConfig(ObjectMapper objectMapper, AuthenticationHandler handler, ObjectPostProcessor<Object> objectPostProcessor) {
        this.objectMapper = objectMapper;
        this.handler = handler;
        // 这里创建了一个 AuthenticationManagerBuilder。注意,直接注入一个 AuthenticationManagerBuilder 运行会报错
        this.authenticationManagerBuilder = new AuthenticationManagerBuilder(objectPostProcessor);
    }

    /**
     * rest 登录的过滤器链配置
     */
    @Bean
    @Order(10)
    public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {
        http
                .antMatcher("/api/**")
                // 适于于无状态的接口
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth
                        .antMatchers("/api/login").permitAll()
                        .antMatchers("/api/**").hasRole("USER")
                        .anyRequest().authenticated())
                // 自定义的过滤器
                .addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
                // rest 接口不需要 csrf 处理
                .csrf(AbstractHttpConfigurer::disable)
                // 禁用表单登录
                .formLogin(AbstractHttpConfigurer::disable)
                // 允许 http basic
                .httpBasic(withDefaults());
        return http.build();
    }

    /**
     * 表单登录的过滤器链
     */
    @Bean
    @Order(20)
    public SecurityFilterChain formLoginFilterChain(HttpSecurity http) throws Exception {
        http
                // 如果这里不设置 authorizeHttpRequests, 那么任何请求都不需要登录
                .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
                // 使用自定义的登录页面, 注意这里需要 permitAll() 否则访问 login.html 页面的时候也会需要认证
                .formLogin(form -> form.loginPage("/login").defaultSuccessUrl("/").permitAll())
                // 退出登录的配置
                .logout(logout -> logout.logoutUrl("/my-logout"))
                // 记住我的设置. 这里注入一个 userDetailService , 因为没有的话, 启动会报错
                .rememberMe(rememberMe -> rememberMe.key("myKey").tokenValiditySeconds(7 * 24 * 3600)
                        .userDetailsService(myUserDetailsService()))
                // 允许 http basic
                .httpBasic(withDefaults());
        ;
        return http.build();
    }

    /**
     * 自定义的认证过滤器
     */
    private RestAuthenticationFilter restAuthenticationFilter() throws Exception {
        RestAuthenticationFilter filter = new RestAuthenticationFilter(objectMapper);
        filter.setAuthenticationSuccessHandler(handler.jsonAuthenticationSuccessHandler());
        filter.setAuthenticationFailureHandler(handler.jsonAuthenticationFailureHandler());
        filter.setAuthenticationManager(myAuthenticationManager());
        filter.setFilterProcessesUrl("/api/login");
        return filter;
    }

    /**
     * 配置要忽略的路径
     */
    @Bean
    WebSecurityCustomizer webSecurityCustomizer() {
        // 忽略 /error 页面
        return web -> web.ignoring().antMatchers("/error")
                // 忽略常见的静态资源路径
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }


    /**
     * 获取用户业务逻辑
     */
    @Bean
    UserDetailsService myUserDetailsService() {
        log.info("init userDetailsService");
        try {
            return authenticationManagerBuilder.inMemoryAuthentication()
                    .withUser("user")
                    // 密码是 password
                    .password("{bcrypt}$2a$10$8Lnu.pNYLUMspcSJuRfyROzg1OyiRVp81.YXaQpxOKHUlOx3ELZB.")
                    .roles("USER")
                    .and()
                    .withUser("admin")
                    // 密码是 password
                    .password(
                            "{SHA-1}{jdIB7T8lvztpz60HGhwWXQP8dkJyNq/oE+UVixGXwGc=}9cc577b587921547a6c0dcf1a4d736e02a2c339b")
                    .roles("ADMIN", "USER")
                    .and()
                    .getUserDetailsService();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 构造一个认证管理器
     */
    @Bean
    AuthenticationManager myAuthenticationManager() throws Exception {
        log.info("init authenticationManager");
        return authenticationManagerBuilder.build();
    }

    /**
     * 密码编码器
     * password bcrypt 加密之后是
     * {bcrypt}$2a$10$8Lnu.pNYLUMspcSJuRfyROzg1OyiRVp81.YXaQpxOKHUlOx3ELZB.
     * password SHA-1 加密之后是
     * {SHA-1}{jdIB7T8lvztpz60HGhwWXQP8dkJyNq/oE+UVixGXwGc=}9cc577b587921547a6c0dcf1a4d736e02a2c339b
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        log.info("init passwordEncoder");
        // 可以使用比较老的 MD5 SHA-1 SHA-256 等等
        // return new MessageDigestPasswordEncoder("SHA-1");

        // 也可以使用的密码编码器有 BCryptPasswordEncoder SCryptPasswordEncoder
        // Pbkdf2PasswordEncoder
        // return new BCryptPasswordEncoder();

        // 也可以让多种编码器共存, 方便后续运维
        String defaultId = "bcrypt";
        val map = Map.of("bcrypt", new BCryptPasswordEncoder(),
                "SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
        return new DelegatingPasswordEncoder(defaultId, map);
    }
}

  然后就既可以通过表单登录,也可以通过api接口登录。表单登录如下图所示:

表单登录

  api接口登录如下所示:

### login rest 登录测试
POST http://localhost:8080/api/login
Content-Type: application/json

{
  "username": "admin",
  "password": "password"
}

  响应为:

{
  "authorities": [
    {
      "authority": "ROLE_ADMIN"
    },
    {
      "authority": "ROLE_USER"
    }
  ],
  "details": {
    "remoteAddress": "127.0.0.1",
    "sessionId": null
  },
  "authenticated": true,
  "principal": {
    "password": null,
    "username": "admin",
    "authorities": [
      {
        "authority": "ROLE_ADMIN"
      },
      {
        "authority": "ROLE_USER"
      }
    ],
    "accountNonExpired": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true,
    "enabled": true
  },
  "credentials": null,
  "name": "admin"
}

附录

1 上一篇文章

  1. 01_SpringSecurity学习之配置HttpSecurity

2 注脚


  1. Multiple HttpSecurity 的配置 https://docs.spring.io/spring-security/reference/servlet/configuration/java.html#_multiple_httpsecurity ↩︎

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值