0.什么是Spring Security

一个能够为基于Spring的企业应用系统提供声明式的安全訪问控制解决方式的安全框架(简单说是对访问权限进行控制嘛),应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。 spring security的主要核心功能为 认证和授权,所有的架构也是基于这两个核心功能去实现的。

1、项目创建

导入Spring Web依赖和Spring Security依赖

2、编写一个HelloController的类



@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
        return "hello security!";
    }
}



3、启动项目,在浏览器地址栏输入http://localhost:8080/hello,会出现如下页面,这是加了Spring Security依赖后要进行登录验证,usernamem默认是user,密码再springboot启动日志下面有一段字符串,也可以自己配置登录用户名和密码,可以通过配置文件配置,也可以通过配置类进行配置




SpringSecurity 密码错误次数 springsecurity默认用户名密码_ide


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


#配置登录密码
spring.security.user.password=123
#配置登录用户名
spring.security.user.name=lqg
#配置角色名称
spring.security.user.roles=admin


5、Java 配置用户名/密码

也可以在 Java 代码中配置用户名密码,首先需要我们创建一个 Spring Security 的配置类,集成自 WebSecurityConfigurerAdapter 类,如下:


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //告诉系统我的密码不加密,输入账号和密码就可以登录上来
    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("javalqg").password("123").roles("admin")
                .and()
                .withUser("凌枫").password("456").roles("user");

    }
}


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

Spring Security 中提供了 BCryptPasswordEncoder 密码编码工具,可以非常方便的实现密码的加密加盐,相同明文加密出来的结果总是不同,这样就不需要用户去额外保存的字段了,这一点比 Shiro 要方便很多。

6.登录配置

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


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //告诉系统我的密码不加密,输入账号和密码就可以登录上来
    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("javalqg").password("123").roles("admin")
                .and()
                .withUser("凌枫").password("456").roles("user");

    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //开启配置
        http.authorizeRequests()
                //如果路径规则符合/admin/**,就会具备admin角色
                .antMatchers("/admin/**").hasRole("admin")
                .antMatchers("/user/**").access("hasAnyRole('user','admin')")
                //如果路径规则符合/user/**,就会具备多个角色
                .antMatchers("/user/**").hasAnyRole("admin","user")
                //剩下的其他请求都是登录之后就能访问的
                .anyRequest().authenticated()
                .and()
                //表单登录
                .formLogin()
                //处理表单登录的url路径
                .loginProcessingUrl("/doLogin")
                //默认看到的登录页面,如果是前后端分离的话,就不用配置登录页面
                .loginPage("/login")
                //修改默认登录的username
                .usernameParameter("uname")
                //修改默认登录的password
                .passwordParameter("passwd")
                //登录成功的处理器  authentication:保存登录成功的用户信息
                .successHandler(new AuthenticationSuccessHandler() {
                    /**登录成功的处理*/
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
                        //设置响应的数据类型
                        resp.setContentType("application/json;charset=utf-8");
                        //拿到witer
                        PrintWriter out = resp.getWriter();
                        Map<String,Object> map = new HashMap<>();
                        map.put("status",200);
                        //获取登录成功的用户对象
                        map.put("msg",authentication.getPrincipal());
                        //把map弄成json字符串写出去
                        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");
                        //拿到witer
                        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 AccountExpiredException){
                            map.put("msg","账户过期,登录失败");
                        }else if (e instanceof CredentialsExpiredException){
                            map.put("msg","密码过期,登录失败");
                        }else {
                            map.put("msg","登录失败!");
                        }

                        //把map弄成json字符串写出去
                        out.write(new ObjectMapper().writeValueAsString(map));
                        out.flush();
                        out.close();
                    }
                })
                //跟登录相关的接口就能直接访问
                .permitAll()
                .and()
                //关闭csrf攻击
                .csrf().disable();
    }
}


我们可以在 successHandler 方法中,配置登录成功的回调,如果是前后端分离开发的话,登录成功后返回 JSON 即可,同理,failureHandler 方法中配置登录失败的回调,logoutSuccessHandler 中则配置注销成功的回调。

7.忽略拦截