Bootstrap

SpringSecurity6.x使用教程

SpringSecurity6.x使用

SpringSecurity版本

SpringSecurity目前支持的版本如下图所示,可以看到5.x的版本过几年就不会再维护了,6.x将成为主流。
在这里插入图片描述

入门

引入依赖

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

编写测试代码

@RestController
@RequestMapping("/test")
public class TestController {


    @GetMapping("/test1")
    public String test1() {
        return "Spring Security Demo";
    }
}

启动项目后,会跳转到下图的登录页
在这里插入图片描述
用户名是user,密码则会打印在控制台,输入用户名和密码以后就可以登录了。

这个页面是在DefaultLoginPageGeneratingFilter的generateLoginPageHtml方法中生成的,而密码则是在UsernamePasswordAuthenticationFilter中生成的。

配置用户和密码

yml配置
spring:
  application:
    name: SpringSecurityDemo
  security:
    user:
      name: admin  #用户名
      password: 888888  #密码
      roles:   #用户具有的角色
        - admin

使用配置类配置

    @Bean
    public UserDetailsService userDetailsService() {
        //使用hash加密
        UserDetails user = User.withUsername("admin")
                .password("{bcrypt}admin")
                .roles("USER")
                .build();
        
        //使用bcrypt加密
        UserDetails root = User.withUsername("root").password(bCryptPasswordEncoder().encode("123456"))
                .roles("admin", "user").build();

        //密码前面需要添加{id},id加密算法的名称,不然会报错,noop是不加密
        UserDetails root1 = User.withUsername("root1").password("{noop}123456")
                .roles("admin", "user").build();

        return new InMemoryUserDetailsManager(user, root, root1);
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

常用的Handler

SpirngSecurity提供了针对各种异常处理的Handler,比如登录错误处理Handler、登录成功处理Handler等,我们只需要继承这些Handler实现自定义的逻辑即可。

登录成功登录失败

登录失败处理逻辑

@Component
public class LoginFailHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("登录失败");
        exception.printStackTrace();
    }
}

登录成功处理逻辑

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        System.out.println("登录成功");
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("登录成功");
    }
}

退出登录

成功退出登录时的处理逻辑

@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        Object principal = authentication.getPrincipal();
        response.setContentType("application/json;charset=utf-8");
        PrintWriter out = response.getWriter();
        out.write(JSON.toJSONString(principal));
        out.flush();
        out.close();
    }

}

授权失败

没有访问权限时的处理逻辑

@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        System.out.println("授权失败处理");
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write("没有访问权限");
        accessDeniedException.printStackTrace();

    }
}

Spring Security配置

如果想实现更多自定义的内容,则需要对SpringSecurity进行配置,比如上面自定义的登录成功Handler需要配置后才能生效。
配置主要内容如下:

  1. 登录配置:设置自定义登录页、登录接口、登录成功失败处理逻辑以及认证成功后跳转路径
  2. 授权配置:主要是设置哪些接口不需要登录认证就能访问,哪些接口需要登录认证后才能访问

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        //表单提交
        httpSecurity.formLogin(formLogin -> formLogin
                //自定义登录页地址
                .loginPage("/login.html")
                .successHandler(loginSuccessHandler)
                .failureHandler(loginFailHandler)
                //登录接口
                .loginProcessingUrl("/test/login")
                //认证成功后跳转的路径
                .defaultSuccessUrl("/index.html"));

        //授权配置
        httpSecurity.authorizeHttpRequests(authorize -> authorize
                //设置哪些路径可以直接访问,不需认证
                .requestMatchers("/login.html", "/test/login", "/swagger-ui/index.html").permitAll()
                //其他的路径都需要认证
                .anyRequest().authenticated());

        //退出登录成功处理器
        httpSecurity.logout(logout -> logout.logoutSuccessHandler(logoutSuccessHandler));

        //授权失败处理配置
        httpSecurity.exceptionHandling(exceptionHandling ->
                exceptionHandling.accessDeniedHandler(accessDeniedHandler));

        //禁用csrf
        httpSecurity.csrf(csrf -> csrf.disable());

        //设置自定义的UserDetailService,如果自定了UserDetailService,则需要设置这个
        httpSecurity.userDetailsService(userDetailsService);

        return httpSecurity.build();
    }

这个配置里需要注意的是loginProcessingUrl配置,loginProcessingUrl的作用是用来拦截前端页面对/login/doLogin这个的请求的,如果拦截到了就会走自己的请求,比如MyUserDetailService的loadUserByUsername方法。

完整代码

login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="/test/login" method="post">
    用户名:<input type="text" name="username"/><br/>
    密码:<input type="password" name="password"/><br/>
    <input type="submit" value="提交"/>
</form>

</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
index html
</body>
</html>

UserDetailService

@Service
public class MyUserDetailService implements UserDetailsService {



    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据username从数据库中获取用户信息,并封装成UserDetails对象,SpringSecurity会根据返回的UserDetails和用户输入的密码进行比对
        String user = "root1";
        String password = "root1";
        return  User.withUsername(user)
                .password(new BCryptPasswordEncoder().encode(password))
                .roles("USER")
                .build();
    }

}

Spring Secruity的配置,使用到的相关Handler在上文已经给出了

@Configuration
@EnableWebSecurity
public class SecurityConfig  {

    @Autowired
    private LoginFailHandler loginFailHandler;

    @Autowired
    private LoginSuccessHandler loginSuccessHandler;

    @Autowired
    private MyAccessDeniedHandler accessDeniedHandler;

    @Autowired
    private UserDetailsService userDetailsService;


    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    @Bean
    public UserDetailsService userDetailsService() {
        //使用hash加密
        UserDetails user = User.withUsername("admin")
                .password("{bcrypt}admin")
                .roles("USER")
                .build();

        //使用bcrypt加密
        UserDetails root = User.withUsername("root").password(bCryptPasswordEncoder().encode("123456"))
                .roles("admin", "user").build();

        //密码前面需要添加{id},id加密算法的名称,不然会报错,noop是不加密
        UserDetails root1 = User.withUsername("root").password("{noop}123456")
                .roles("admin", "user").build();

        return new InMemoryUserDetailsManager(user, root, root1);
    }


    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }



    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        //表单提交
        httpSecurity.formLogin(formLogin -> formLogin
                //自定义登录页地址
                .loginPage("/login.html")
                .successHandler(loginSuccessHandler)
                .failureHandler(loginFailHandler)
                //登录接口
                .loginProcessingUrl("/test/login")
                //认证成功后跳转的路径
                .defaultSuccessUrl("/index.html"));

        //授权配置
        httpSecurity.authorizeHttpRequests(authorize -> authorize
                //设置哪些路径可以直接访问,不需认证
                .requestMatchers("/login.html", "/test/login", "/swagger-ui/index.html").permitAll()
                //其他的路径都需要认证
                .anyRequest().authenticated());

        //退出登录成功处理器
        httpSecurity.logout(logout -> logout.logoutSuccessHandler(logoutSuccessHandler));

        //授权失败处理配置
        httpSecurity.exceptionHandling(exceptionHandling ->
                exceptionHandling.accessDeniedHandler(accessDeniedHandler));

        //禁用csrf
        httpSecurity.csrf(csrf -> csrf.disable());

        //设置自定义的UserDetailService,如果自定了UserDetailService,则需要设置这个
        httpSecurity.userDetailsService(userDetailsService);


        return httpSecurity.build();
    }

}

参考

  1. Spring Security官网
  2. SpringSecurity的 loginProcessingUrl为什么不能用
  3. spring security loginProcessingUrl无效问题
;