导入依赖

本次使用SpringBoot版本为2.5.4

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-security</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>org.springframework.security</groupId>
  7. <artifactId>spring-security-test</artifactId>
  8. <scope>test</scope>
  9. </dependency>

请求接口

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

启动结果

security在没有任何配置的情况下会自动给你生成一个默认的临时密码用于登录

image.png

访问我们编写的/hello接口时会自动跳转到security的login接口要求登录认证才可以继续访问
http://localhost:29898/login
image.png

默认的用户名是user,密码就是控制台默认生成的临时密码

security的默认登录页面和接口都是/login

配置用户名密码

配置文件配置

spring:
  security:
    user:
      name: admin
      password: 12345

该项配置表示使用登录的用户名和密码
再次登陆时可以使用配置的用户名密码即可
但是使用了配置文件之后,登录完毕之后会直接返回/,而不是你一开始打算访问的接口

配置类配置

需要配置密码加密,spring security不同于shiro,提供了多种密码加密方案,不同于shiro,security不需要自己处理密码加盐
默认的加密方式为BcryptPasswordEncoder

配置类编写
只需要继承【WebSecurityConfigurerAdapter】,实现三个config方法即可,其中配置类的配置项会覆盖掉配置文件中的配置项
例如:用户名密码
AuthenticationManagerBuilder方法主要配置security的授权认证相关

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder() {
        //不需要加密
        return NoOpPasswordEncoder.getInstance();
        //return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin11")
            .password("12345")
            .roles("admin");
    }
}

自定义登陆页面

默认的表单太丑,我们可以进行自定义页面
将页面放在static目录下

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>security-login</title>

    <link rel="stylesheet" href="css/font-awesome-4.7.0/css/font-awesome.min.css">
    <link rel="stylesheet" href="css/style.css">

</head>
<body>


<div class="materialContainer">
    <div class="box">
        <div class="title">security-登录</div>
        <form action="/login.html" method="post">
            <div class="input">
                <label for="name">用户名</label>
                <input type="text" name="username" id="name">
                <span class="spin"></span>
            </div>
            <div class="input">
                <label for="pass">密码</label>
                <input type="password" name="password" id="pass">
                <span class="spin"></span>
            </div>
            <div class="button login">
                <button type="submit">
                    <span>登录</span>
                    <i class="fa fa-check"></i>
                </button>
            </div>
        </form>
        <a href="javascript:" class="pass-forgot">忘记密码?</a>
    </div>

    <div class="overbox">
        <div class="material-button alt-2">
            <span class="shape"></span>
        </div>
        <div class="title">security-注册</div>
        <div class="input">
            <label for="regname">用户名</label>
            <input type="text" name="regname" id="regname">
            <span class="spin"></span>
        </div>
        <div class="input">
            <label for="regpass">密码</label>
            <input type="password" name="regpass" id="regpass">
            <span class="spin"></span>
        </div>
        <div class="input">
            <label for="reregpass">确认密码</label>
            <input type="password" name="reregpass" id="reregpass">
            <span class="spin"></span>
        </div>
        <div class="button">
            <button>
                <span>注册</span>
            </button>
        </div>
    </div>

</div>

<script src="js/jquery.min.js"></script>
<script src="js/index.js"></script>

</body>
</html>

image.png

配置登录相关的页面和接口

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
     @Bean
    PasswordEncoder passwordEncoder() {
        //不需要加密
        return NoOpPasswordEncoder.getInstance();
        //return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("admin11").password("12345").roles("admin");
    }


    @Override
    public void configure(WebSecurity web) throws Exception {
       super.configure(web);
    }

      @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and() //所有请求都需要认证才能访问
                .formLogin().loginPage("/login.html")//配置登陆页面路径,如果不配置登录接口.loginProcessingUrl(),那么登录接口也是配置的登录页所配置的
                .permitAll()//和登录相关的页面全部都放行
                .and()
                .csrf().disable()//关闭csrf
        ;
    }
}

configure(HttpSecurity http) 方法定义了哪些 URL 路径应该受到保护,哪些不应该受到保护。
.permitAll()定义为全部权限,不需要任何鉴权即可查看

配置放行静态文件

访问之后会发现样式没有进行加载,继续配置放行样式文件

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder() {
        //不需要加密
        return NoOpPasswordEncoder.getInstance();
        //return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("admin11").password("12345").roles("admin");
    }

    @Override
    public void configure(WebSecurity web) {
        web.ignoring().antMatchers("/js/**", "/css/**", "/images/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and() //所有请求都需要认证才能访问
            .formLogin().loginPage("/login.html")//配置登陆页面路径,如果不配置登录接口.loginProcessingUrl(),那么登录接口也是配置的登录页
            .permitAll()//和登录相关的页面全部都放行
            .and().csrf().disable()//关闭csrf
        ;
    }
}

效果

image.png

但是我们在登录页面中配置的登录接口是以html结尾的
image.png
不利于代码的可观性,那么我们继续编写和配置登录接口

配置登录接口

  @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and() //所有请求都需要认证才能访问
            .formLogin().loginPage("/login.html")//配置登陆页面路径,如果不配置登录接口.loginProcessingUrl(),那么登录接口也是配置的登录页
            .loginProcessingUrl("/doLogin").permitAll()//和登录相关的页面全部都放行
            .and().csrf().disable()//关闭csrf
        ;
    }

这样在表单中的登录接口就必须写成我们配置的接口了

配置用户名和密码参数名

而且对于登录参数的命名也有限制,不配置的情况下用户名和密码的参数一定是username和password

 <div class="box">
        <div class="title">security-登录</div>
        <form action="/doLogin" method="post">
            <div class="input">
                <label for="name">用户名</label>
                <input type="text" name="username" id="name">
                <span class="spin"></span>
            </div>
            <div class="input">
                <label for="pass">密码</label>
                <input type="password" name="password" id="pass">
                <span class="spin"></span>
            </div>
            <div class="button login">
                <button type="submit">
                    <span>登录</span>
                    <i class="fa fa-check"></i>
                </button>
            </div>
        </form>
        <a href="javascript:" class="pass-forgot">忘记密码?</a>
    </div>

但是我们同样可以进行配置参数名

   @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and() //所有请求都需要认证才能访问
            .formLogin().loginPage("/login.html")//配置登陆页面路径,如果不配置登录接口.loginProcessingUrl(),那么登录接口也是配置的登录页
            .loginProcessingUrl("/doLogin")//配置登录接口
            .usernameParameter("name")//配置用户名的参数名称
            .passwordParameter("passwd")//配置密码的参数名称
            .permitAll()//和登录相关的页面全部都放行
            .and().csrf().disable()//关闭csrf
        ;
    }

<div class="box">
        <div class="title">security-登录</div>
        <form action="/doLogin" method="post">
            <div class="input">
                <label for="name">用户名</label>
                <input type="text" name="name" id="name">
                <span class="spin"></span>
            </div>
            <div class="input">
                <label for="pass">密码</label>
                <input type="password" name="passwd" id="pass">
                <span class="spin"></span>
            </div>
            <div class="button login">
                <button type="submit">
                    <span>登录</span>
                    <i class="fa fa-check"></i>
                </button>
            </div>
        </form>
        <a href="javascript:" class="pass-forgot">忘记密码?</a>
    </div>

配置登录回调

成功

  .successForwardUrl("/success")//登录成功后跳转的接口,服务端跳转
  //.defaultSuccessUrl("/success")//配置默认的成功跳转接口,客户端跳转

「注意:实际操作中,defaultSuccessUrl 和 successForwardUrl 只需要配置一个即可。」

失败

.failureForwardUrl("/fail")//登录失败后跳转的接口,服务端跳转
//.failureUrl("/fail")//客户端跳转

「这两个方法在设置的时候也是设置一个即可」
failureForwardUrl 是登录失败之后会发生服务端跳转,failureUrl 则在登录失败之后,客户端会发生重定向。

当我们登陆成功之后如何进行跳转,登录失败后如何进行跳转

 @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and() //所有请求都需要认证才能访问
            .formLogin().loginPage("/login.html")//配置登陆页面路径,如果不配置登录接口.loginProcessingUrl(),那么登录接口也是配置的登录页
            .loginProcessingUrl("/doLogin")//配置登录接口
            .usernameParameter("name")//配置用户名的参数名称
            .passwordParameter("passwd")//配置密码的参数名称
            .successForwardUrl("/success")//登录成功后跳转的接口,服务端跳转
            //.defaultSuccessUrl("/success")////配置默认的成功跳转接口,链接是客户端跳转,和successForwardUrl只需配置一个即可
            .failureForwardUrl("/fail")//登录失败后跳转的接口,服务端跳转
            //.failureUrl("/fail")//客户端跳转
            .permitAll()//和登录相关的页面全部都放行
            .and()            
            .csrf().disable()//关闭csrf
        ;
    }

controller

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

    @RequestMapping("/success")
    public String success() {
        return "success, welcome!";
    }
    @RequestMapping("/fail")
    public String fail() {
        return "fail, try again!";
    }
}

测试

默认的注销接口为/logout

状态 配置 结果
成功 .successForwardUrl(“/success”)
//登录成功后跳转的接口,服务端跳转
image.png
.defaultSuccessUrl(“/success”)
//配置默认的成功跳转接口,客户端跳转
image.png
失败 .failureForwardUrl(“/fail”)
//登录失败后跳转的接口,服务端跳转
image.png
.failureUrl(“/fail”)
//客户端跳转
image.png

配置注销

配置注销的接口和请求方式

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().authenticated().and() //所有请求都需要认证才能访问
            .formLogin().loginPage("/login.html")//配置登陆页面路径,如果不配置登录接口.loginProcessingUrl(),那么登录接口也是配置的登录页
            .loginProcessingUrl("/doLogin")//配置登录接口
            .usernameParameter("name")//配置用户名的参数名称
            .passwordParameter("passwd")//配置密码的参数名称
            .successForwardUrl("/success")//登录成功后跳转的接口,服务端跳转
            //.defaultSuccessUrl("/success")////配置默认的成功跳转接口,链接是客户端跳转,和successForwardUrl只需配置一个即可
            .failureForwardUrl("/fail")//登录失败后跳转的接口
            .failureUrl("/fail")//客户端跳转
            .permitAll()//和登录相关的页面全部都放行
            .and()
            .logout()
            //.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "POST"))//配置logout的接口和请求方式, 默认是/logout和get请求
            .logoutRequestMatcher(new AntPathRequestMatcher("/logout111", "GET"))//配置logout的接口和请求方式, 默认是/logout和get请求
            .logoutSuccessUrl("/login.html")//注销后跳转的页面
            .deleteCookies()//清除cookie
            .invalidateHttpSession(true)//清除session,默认true
            .clearAuthentication(true)//清除认证, 默认true
            .and()
            .csrf().disable()//关闭csrf
        ;
    }
  1. 默认注销的 URL 是 /logout,是一个 GET 请求,我们可以通过 logoutUrl 方法来修改默认的注销 URL。
  2. logoutRequestMatcher 方法不仅可以修改注销 URL,还可以修改请求方式,实际项目中,这个方法和 logoutUrl 任意设置一个即可。
  3. logoutSuccessUrl 表示注销成功后要跳转的页面。
  4. deleteCookies 用来清除 cookie。
  5. clearAuthentication 和 invalidateHttpSession 分别表示清除认证信息和使 HttpSession 失效,默认可以不用配置,默认就会清除。

附录:

security 三个config方法的区别

三个config方法的区别

  • 前端放行

这种方式不走 Spring Security 过滤器链,在过滤器链中,给请求放行。
总之,前端静态资源放行时,可以直接不走 Spring Security 过滤器链,像下面这样:


@Override
public void configure(WebSecurity web) throws Exception {
    web.ignoring().antMatchers("/css/**","/js/**","/index.html","/img/**","/fonts/**","/favicon.ico");
}
  • 后端接口放行

这种方式走 Spring Security 过滤器链,在过滤器链中,给请求放行。

后端的接口要额外放行,就需要仔细考虑场景了,
不过一般来说,不建议使用上面这种方式,建议下面这种方式,原因前面已经说过了

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

http.authorizeRequests()
        .antMatchers("/hello").permitAll()
        .anyRequest().authenticated()
   }
  • 配置认证所需资源
  • 如认证用户信息实现类,认证逻辑实现类 ```java @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      super.configure(auth);
    
    }

```

参考:security demo