spring-security 入门教程

2021-11-21  本文已影响0人  ithankzc

引入 spring-security jar 包,不做任何额外配置

启动服务自动生成的随机密码

Using generated security password: 52713582-35cf-4b0c-832a-1342faece243

默认登录页

image.png

实际业务,会存在多用户的的情况,spring-security 也提供在内存及数据库管理用户的方法。为了方便,我们可以先尝试内存操作

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .defaultSuccessUrl("/user")  //登录成功后跳转地址
                .and().httpBasic();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(customPasswordEncoder())
                .withUser("cxc").password(customPasswordEncoder().encode("cxcpwd")).roles("USER");
    }

    @Bean
    PasswordEncoder customPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

写一个 reset 请求进行验证

@RestController
public class IndexController {

    @GetMapping("/hello")
    String hello() {
        return "hello world";
    }

    @GetMapping("/user")
    String user() {
        return SecurityContextHolder.getContext().getAuthentication().getName();
    }
}

登录 & 验证效果


image.png

稍微高级一点的用法

定义一个 authenticationProvider,然后在 configure 将这个 provider 作为参数传入到 auth.authenticationProvider(authenticationProvider());

    @Bean
    public DaoAuthenticationProvider authenticationProvider(){
        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
        provider.setPasswordEncoder(customPasswordEncoder());
       // 这里直接还是用默认的 InMemoryUserDetailsManager,就不再实现 UserDetailsService 接口
        provider.setUserDetailsService(new InMemoryUserDetailsManager());
        return provider;
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().passwordEncoder(customPasswordEncoder())
                .withUser("chenxiaochi").password(customPasswordEncoder().encode("pwd")).roles("USER");
        auth.authenticationProvider(authenticationProvider());
    }

项目用到的 pom 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.7</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.cxc</groupId>
    <artifactId>security</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>security</name>
    <description>security</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

重点讲解部分

PasswordEncoder,UserDetailsService 的初始化

如果没注入自定义的 provider , InitializeUserDetailsManagerConfigurer 会接管初始化配置,此时 UserDetailsService,PasswordEncoder 将会被注入系统默认的 provider

InitializeUserDetailsManagerConfigurer

    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.apply(new InitializeUserDetailsBeanManagerConfigurer.InitializeUserDetailsManagerConfigurer());
    }

    class InitializeUserDetailsManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
        InitializeUserDetailsManagerConfigurer() {
        }

        public void configure(AuthenticationManagerBuilder auth) throws Exception {
            if (!auth.isConfigured()) {
                UserDetailsService userDetailsService = (UserDetailsService)this.getBeanOrNull(UserDetailsService.class);
                if (userDetailsService != null) {
                    PasswordEncoder passwordEncoder = (PasswordEncoder)this.getBeanOrNull(PasswordEncoder.class);
                    UserDetailsPasswordService passwordManager = (UserDetailsPasswordService)this.getBeanOrNull(UserDetailsPasswordService.class);
                    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
                    provider.setUserDetailsService(userDetailsService);
                    if (passwordEncoder != null) {
                        provider.setPasswordEncoder(passwordEncoder);
                    }

                    if (passwordManager != null) {
                        provider.setUserDetailsPasswordService(passwordManager);
                    }

                    provider.afterPropertiesSet();
                    auth.authenticationProvider(provider);
                }
            }
        }

        private <T> T getBeanOrNull(Class<T> type) {
            String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanNamesForType(type);
            return beanNames.length != 1 ? null : InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
        }
    }

AuthenticationManagerBuilder

    public boolean isConfigured() {
        return !this.authenticationProviders.isEmpty() || this.parentAuthenticationManager != null;
    }

所以尽量在注入PasswordEncoder的时候,尽量隐藏密码类型细节,返回类型为接口类型即可。

    @Bean
    PasswordEncoder customPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

总结

这里不涉及太多源代码研究,更多的是演示怎样用,一个简单的例子感受 spring-security。

参考

网上好的例子: https://juejin.cn/post/6844903896687575047
网上好的例子2: https://progressivecoder.com/implementing-spring-boot-security-using-userdetailsservice/
验证流程分析文章 https://juejin.cn/post/6844903806921244679

说点其他的

公司项目因业务原因,没有过多依赖于 spring-security 的用户名及密码校验,主要用了认证成功的登录态设置,权限。是通过过滤器的方式处理的。 所以也会写一篇 spring-security 过滤器的。

上一篇下一篇

猜你喜欢

热点阅读