likes
comments
collection
share

Spring Security 的入门案例

作者站长头像
站长
· 阅读数 24

日积月累,水滴石穿 😄

前言

如果想让我们的接口受到保护,只需要在项目中加入 spring-boot-starter-security 依赖。

导入依赖

Spring Security 的入门案例

具体依赖版本

boot-starter-parent = 2.3.12.RELEASE
boot-starter-security = 2.3.12.RELEASE
security-web = 5.3.9.RELEASE
security-config = 5.3.9.RELEASE

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.cxyxj</groupId>
    <artifactId>security-study-01</artifactId>
    <version>1.0-SNAPSHOT</version>

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

</project>

效果

依赖导入之后,我们来看看效果,在项目中提供一个 hello 接口。

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "你好 Spring Security";
    }
}

项目启动成功之后,可以在浏览器输入我们的接口地址:localhost:8080/hello

发起请求之后,并没有调用 hello接口,而是来到了security默认的登录页面:

Spring Security 的入门案例 这个页面所处的源码位置为:DefaultLoginPageGeneratingFilter#generateLoginPageHtml。

那登录用户名和登录密码是什么呢? 在我们的项目启动过程中,在日志中会打印一段话:

INFO 37204 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: df563e43-4b0c-4760-9dd3-eb512b591d7b

这个就是 Spring Security 为默认用户 user 生成的临时密码。密码每次启动都会改变。

各位小伙伴可能会有疑问,为什么你知道默认用户名是 user,密码为什么每次会改变呢?

各位小伙伴可以发现,该日志打印时所处的类为 UserDetailsServiceAutoConfiguration。 在该类中有个方法 getOrDeducePassword,当 isPasswordGenerated方法返回 true,就会打印如上日志。

private String getOrDeducePassword(User user, PasswordEncoder encoder) {
    String password = user.getPassword();
    if (user.isPasswordGenerated()) {
        logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
    }
    return encoder == null && !PASSWORD_ALGORITHM_PATTERN.matcher(password).matches() ? "{noop}" + password : password;
}

我们进行跟踪 isPasswordGenerated方法,来到了 SecurityProperties类中,发现 passwordGenerated属性默认即为 true,当然还有额外发现。

Spring Security 的入门案例

可以看到,默认的用户名就是 user,密码则是 UUID 字符串。各位小伙伴现在了解了吧!OK,让我们回归正题。

当我们输入用户名密码之后,点击按钮,就会登录成功,会跳转访问 /hello 接口。

Spring Security 的入门案例

自定义账号密码

上面讲到这个密码每次启动都会改变,每次都需要去控制台寻找,这非常的不方便。那有没有方法固定密码呢?

对用户名/密码进行配置,有三种不同的方式:

  • 基于配置文件 properties 或者 yml
  • 基于 UserDetailsService 接口
  • 基于配置类 WebSecurityConfigurerAdapter

配置文件

通过前文可以知道Security的配置是放在SecurityProperties类中。而SecurityProperties就是一个读取配置文件的类,定义如下:

@ConfigurationProperties(
    prefix = "spring.security"
)
public class SecurityProperties {

各位小伙伴对@ConfigurationProperties注解肯定不陌生吧!所以自定义用户名和密码只需在配置文件添加如下配置即可:

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

上述就是我们自定义的用户名和密码。 那为什么可以这样配置? 还是在UserDetailsServiceAutoConfiguration类中,默认情况下,UserDetailsServiceAutoConfiguration类,会创建一个内存级别的 InMemoryUserDetailsManager对象,其中包含用户名为 user,密码为 UUID 随机的用户 。而我们添加 spring.security.user 配置项之后,UserDetailsServiceAutoConfiguration 会基于配置的信息在内存中创建一个用户。

Spring Security 的入门案例 配置好了之后,重新启动,控制台不会再打印随机生成的密码了。启动成功后,我们可以使用配置文件中的账号、密码登录。

基于UserDetailsService接口

这种方式是在实际开发中使用最多的一种方式!可以由开发者自由控制用户的来源,比如:从数据库加载。

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        UserDetails userDetails = User.withUsername("cxyxj")
                .password("{noop}1234").authorities("admin").build();
        return userDetails;
    }
}

withUsername 就是账号,password 就是密码,authorities 就是角色。在 security 中用户信息都会包装为一个 UserDetails 对象。

Spring Security5 中新增加了加密方式,并把原有的 Spring Security 的密码存储格式改了,修改后的密码存储格式为:{id}pwd。 如果不写{id},在发起登录请求时,会出现以下异常:

Spring Security 的入门案例 支持的加密方式可以通过 PasswordEncoderFactories 查看。

Spring Security 的入门案例 也可以通过PasswordEncoder配置指定加密方式。

@Bean
public PasswordEncoder passwordEncoder(){
    return NoOpPasswordEncoder.getInstance();
}
    
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    UserDetails userDetails = User.withUsername("cxyxj")
            .password("1234").authorities("admin").build();
    return userDetails;
}

当然 Spring Security 官方推荐的加密方式是 BCrypt。

@Bean
public PasswordEncoder passwordEncoder(){
   // return NoOpPasswordEncoder.getInstance();
    return new BCryptPasswordEncoder();
}

@Autowired
private PasswordEncoder passwordEncoder;

@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    String encode = passwordEncoder.encode("12345");
    UserDetails userDetails = User.withUsername("cxyxj")
            .password(encode).authorities("admin").build();
    return userDetails;
}

基于配置类WebSecurityConfigurerAdapter

首先创建一个 Spring Security 的配置类,继承 WebSecurityConfigurerAdapter 类。

@Configuration
//@EnableWebSecurity  如果你的项目是 SpringBoot 项目,该注解就没必要写
public class SecurityConfig extends WebSecurityConfigurerAdapter {

}

是否需要添加@EnableWebSecurity注解?如果在项目中引入的是spring-boot-starter-security依赖则不需要添加 @EnableWebSecurity,可以参考自动配置类: spring-boot-autoconfigure-x.x.x.RELEASE.jar!/META-INF/spring.factories 下的 SecurityAutoConfiguration

Spring Security 的入门案例 Spring Security 的入门案例 如果是单独引入 spring-security-config 和 spring-security-web 依赖,则需要添加 @EnableWebSecurity注解。

WebSecurityConfigurerAdapter 类是个适配器,如果我们需要自定义配置,那就要继承它。比如基于内存的认证,当然添加认证的方式还有很多,但是万变不离其宗,都是往 Security中添加一个 UserDetails 对象 ,配置方式如下:


    @Bean
    PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("cxyxj")
                .password("123").roles("admin","user")
                .and()
                .withUser("security")
                .password("security").roles("user");
    }
  1. 自定义 SecurityConfig 继承自 WebSecurityConfigurerAdapter,重写 configure(AuthenticationManagerBuilder auth) 方法。AuthenticationManagerBuilder用于创建AuthenticationManager认证管理器。 比如:内存身份验证,LDAP身份验证,JDBC的身份验证,还可以可以添加UserDetailsService以及添加AuthenticationProvider

  2. 提供了一个 PasswordEncoder 的实例,类型为 NoOpPasswordEncoder 实例,表示不给密码进行加密 。

  3. 在 configure 方法中, inMemoryAuthentication 表示开启在内存中定义用户,withUser 定义用户名,password 定义用户密码,roles 是用户角色。

在使用该方式之前,先把 UserDetailsServiceImpl 类中的代码先全部注释。因为两个类中都注入了 PasswordEncoder 实例。当然,你也可以将代码修改。

当然也可以重写WebSecurityConfigurerAdapter#userDetailsService()方法或者 WebSecurityConfigurerAdapter#userDetailsServiceBean()方法,并通过 @Bean 交给 Spring 管理


@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication()
            .withUser("cxyxj")
            .password("123").roles("admin","user")
            .and()
            .withUser("security")
            .password("security").roles("user");

    // 这句话要加
    auth.userDetailsService(userDetailsService());
}

@Bean
@Override
protected UserDetailsService userDetailsService() {
    return username -> new User("hhhhh", "12345",
            AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}

注意:基于UserDetailsService接口配置用户名密码时,这种方式需要注意一点,如果你同时继承了 WebSecurityConfigurerAdapter 类并重写了 configure(AuthenticationManagerBuilder auth)方法,则需要手动设置 UserDetailsService的来源 auth.userDetailsService(userService)


  • 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。
转载自:https://juejin.cn/post/7087461417540026382
评论
请登录