SpringSecurity是Spring家族中的一个重量级安全管理框架,实际上,在Springboot出现之前,SpringSecurity就已经发展了很多年了。Springboot为springSecurity提供了自动化配置方案。可以零配置使用SpringSecurity。

1.项目的创建

在Springboot中使用SpringSecurity非常容易,创建项目的时候引入Security的依赖即可:




springboot 动态密码 spring boot admin默认密码_用户名


pom文件中的SpringSecurity依赖


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


只要加入依赖,项目所有的接口都会被保护起来

2.体验Springsecurity

创建一个HelloController:


@RestControlle
public class HelloController{
 @GetMapping("/hello")
    public String hello() {
        return "hello";
    }
}


启动后访问接口 "/hello" 页面显示


springboot 动态密码 spring boot admin默认密码_spring_02


需要登录才能访问。当用户向浏览器发送访问“/hello”接口的请求时,服务端会返回302响应码,让客户重定向到“/login”页面,用户在“/login”页面登录,登录成功后会自动跳转到“/hello”接口。

SpringSecurity支持两种不同的认证方式:

  1. 可以通过form表单认证
  2. 可以通过HttpBasic认证

3.用户名密码的配置

默认情况下,登录的用户名“user”,密码是项目启动时随机生成的字符串,可以从启动的控制台中看到默认的密码:

这个随机生成的密码,每次启动的时候都会发生改变。对登录的用户名/密码进行配置,有三种不同的方法

  • 在 application.properties 中进行配置
  • 通过 Java 代码配置在内存中
  • 通过 Java 从数据库中加载

配置文件配置用户名/密码

直接在application.properties文件中配置用户的基本信息


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


配置完成后,重启项目,就可以使用这个配置的用户名/密码进行登录

java配置用户名/密码

可以在java代码中配置用户名和密码,首先要创建一个配置类,继承自WebSecurityConfigurerAdapter 类,如下:


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

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

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //下面这两行配置表示在内存中配置了两个用户
        auth.inMemoryAuthentication()
              .withUser("zhulaosan").password("$2a$10$OR3VSksVAmCzc.7WeaRPR.t0wyCsIj24k0Bne8iKWV1o.V9wsP8Xe").roles("admin")
                .and()
                .withUser("朱老三").password("$2a$10$OR3VSksVAmCzc.7WeaRPR.t0wyCsIj24k0Bne8iKWV1o.V9wsP8Xe").roles("user");   
 }


在configure方法中配置了两个用户,用户密码都是加密后的字符串(明文都是123),从Spring5开始,强制要求密码加密,如果 不想加密,可以使用使用过期的PasswordEncoder的实例NoOpPasswordEncoder,不建议这么做,不安全。

4.登录配置

对于登录接口,登录成功后的响应,登录失败后的响应,我们都可以在 WebSecurityConfigurerAdapter 的实现类中进行配置。例如下面这样:


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("zhulaosan").password("123").roles("admin")
                .and()
                .withUser("朱老三").password("123").roles("user");
    }
//登录post请求
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()//开启登录配置
                .antMatchers("/admin/**").hasAnyRole("admin")//表示访问 /admin这个接口,需要具备 admin 这个角色
  //            .antMatchers("user/**").hasAnyRole("admin","user")//表示访问 /user这个接口,需要具备 admin和user 这个角色
                .antMatchers("/user/**").access("hasAnyRole('user','admin')")
                .anyRequest().authenticated()//表示剩余的其他接口,登录之后就能访问
                .and()
                .formLogin()
                .loginProcessingUrl("/doLogin")//表单登录接口
                //定义登录页面,未登录时,访问一个需要登录之后才能访问的接口,会自动跳转到该页面
                .loginPage("/login")
                //定义登录时,用户名的 key,默认为 username
                .usernameParameter("uname")
                //定义登录时,用户密码的 key,默认为 password
                .passwordParameter("passwd")
               //登录成功的处理器
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        Map<String,Object>map=new HashMap<>();
                        map.put("status",200);
                        map.put("msg", authentication.getPrincipal());//获取到登录成功的用户对象
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                 //登录失败的处理器
                .failureHandler(new AuthenticationFailureHandler() {
                    @Override
                    public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        Map<String, Object> map = new HashMap<>();
                        map.put("status",401);
                        if (e instanceof LockedException){
                            map.put("msg","账户被锁定,登录失败");
                        }else if(e instanceof BadCredentialsException){
                            map.put("msg","用户名密码输入出错,登录失败");
                        }else if (e instanceof DisabledException){
                            map.put("msg","账户被禁用,登录失败");
                        }else if (e instanceof CredentialsExpiredException) {
                            map.put("msg", "密码过期,登录失败!");
                        } else {
                            map.put("msg", "登录失败!");
                        }
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                .permitAll()//和表单登录相关的接口统统都直接通过
                .and()
                //注销用户的处理器
                .logout()
                .logoutUrl("/logout")//注销的接口get方法
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    @Override
                    public void onLogoutSuccess(HttpServletRequest req , HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        resp.setContentType("application/json;charset=utf-8");
                        PrintWriter out = resp.getWriter();
                        Map<String,Object>map=new HashMap<>();
                        map.put("status",200);
                        map.put("msg", "注销登录成功");//获取到登录成功的用户对象
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();

                    }
                })
                .and()
                .csrf().disable();     //关闭scr攻击
    }


登录失败的Exception在AccountStatusException中可以查看


springboot 动态密码 spring boot admin默认密码_用户名_03


5.忽略拦截

如果某一个请求地址不需要拦截的话,有两种方式实现:

  • 设置该地址匿名访问
  • 直接过滤掉该地址,即该地址不走 Spring Security 过滤器链

推荐使用第二种方案,配置如下:


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/vercode");
    }
}


6.配置多个 Security

配置多个Security时MultiHttpSecurityConfig可以不继承WebSecurityConfigurerAdapter类


@Configuratio
public class MultiHttpSecurityConfig {

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

    @Autowired
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("zhulaosan").password("$2a$10$G3kVAJHvmRrr6sOj.j4xpO2Dsxl5EG8rHycPHFWyi9UMIhtdSH15u").roles("admin")
                .and()
                .withUser("朱老三").password("$2a$10$kWjG2GxWhm/2tN2ZBpi7bexXjUneIKFxIAaMYJzY7WcziZLCD4PZS").roles("user");
    }

    @Configuration
    @Order(1)
    public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter{
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/admin/**").authorizeRequests().anyRequest().hasAnyRole("admin");
        }
    }

    @Configuration
    public static class OtherSecurityConfig extends WebSecurityConfigurerAdapter{
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().anyRequest().authenticated()
                    .and()
                    .formLogin()
                    .loginProcessingUrl("/doLogin")
                    .permitAll()
                    .and()
                    .csrf().disable();
        }
    }
}