同福

SpringBoot的SpringSecurity实现basic认证

介绍

介绍

开发 REST API 少不了要对资源进行安全保护,SpringBoot 给我们提供了一个很强大的模块 SpringSecurity 来做认证功能的实现

今天我们就来学习如何使用 SpringSecurity 模块实现 HTTP Basic 认证

教程

导入模块

打开 pom.xml,在 dependencies 节点里添加

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

建立认证配置文件

打开 config/SecurityConfig.java,设置如下内容

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http

                .csrf().disable()

                .authorizeRequests() // 设置策略

                .anyRequest() // 任何请求
                .authenticated() // 都要认证

                .and()

                .httpBasic() // 使用 basic 认证模式
        ;
    }
}

使用spring.security.user验证

打开 application.properties,增加如下设置

spring.security.user.name=admin
spring.security.user.password=123456

测试

通过浏览器打开任意接口都会弹出需要用户名和密码的认证窗口,然后我们输入配置文件里的 admin/123456 就可以通过认证了

b85143cd707c4636.jpg

使用inMemoryAuthentication验证

刚刚我们通过 application.properties 设置了用户名和密码实现自动验证

现在我们换一个方法实现

打开 config/SecurityConfig.java,添加如下方法

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
        throws Exception
{
    auth.inMemoryAuthentication()
            .withUser("manager")
            .password("{noop}abcdef")
            .roles("USER");
}

注意:这里的 {noop} 后面的才是密码,这个 {noop} 是密码加密方式,表示原文密码的意思

支持的加密方式有 noop,bcrypt,pbkdf2,scrypt,sha256 这么几种

必须指定一种密码加密方式,如果不指定任何加密方式,则会报如下错误信息

There is no PasswordEncoder mapped for the id "null"

测试

通过浏览器打开任意接口都会弹出需要用户名和密码的认证窗口,然后我们输入配置文件里的 manager/abcdef 就可以通过认证了

使用userDetailsService验证

前面两种验证方式都只适用于只有一组用户名和密码的情况

下面这种验证方式则适用于类似会员模式的多组用户名和密码的情况

有点复杂,跟我来,别掉队~~

修改安全配置

打开 config/SecurityConfig.java,修改 configureGlobal 方法的内容

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
        throws Exception
{
    auth.userDetailsService(new AuthUserService()).passwordEncoder(new PasswordEncoder() {
        @Override
        public String encode(CharSequence charSequence) {
            return (String) charSequence;
        }

        @Override
        public boolean matches(CharSequence charSequence, String s) {
            return s.equals((String) charSequence);
        }
    });
}

建立用户详情服务对象

打开 model/AuthUserService.java,设置如下内容

public class AuthUserService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        AuthUser authUser = new AuthUser(s);

        return authUser;
    }
}

注意:我们的用户详情对象 AuthUser 是通过这里面的用户名 s 作为参数查询出来的

建立用户详情对象

打开 entity/AuthUser.java,设置如下内容

public class AuthUser implements UserDetails {

    public AuthUser(String s){
        
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return "123456";
    }

    @Override
    public String getUsername() {
        return "user";
    }

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

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

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

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

注意:构造函数传入了用户名,我们可以根据这个用户名去查询用户信息,然后重写每个属性方法的返回值,为了大家看的清晰这里我没有添加查询数据库之类的逻辑,这个要看明白哦~~

测试

通过浏览器打开任意接口都会弹出需要用户名和密码的认证窗口,然后我们输入配置文件里的 user/123456 就可以通过认证了

总结

  • 如果临时搞一个接口,只给某一个人开放,可以放到 application.properties 里设置,非常简单

  • 如果临时搞一个接口,只给某一个人开放,又需要对密码加密一下,可以通过 inMemoryAuthentication 设置,也很简单

  • 如果是提供自己的会员的功能,需要动态查询用户名和密码,甚至用户状态的话,就需要使用 UserDetailsService 和 UserDetails 来实现了

附录

部分接口免认证的设置方法

在安全配置里这样写

@Override
protected void configure(HttpSecurity http) throws Exception {
    http

            .csrf().disable()

            .authorizeRequests() // 设置策略

            .antMatchers("/entity").permitAll() // 这个接口不用认证

            .anyRequest() // 任何请求
            .authenticated() // 都要认证

            .and()

            .httpBasic() // 使用 basic 认证模式
    ;
}